plan.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. from typing import List
  2. from opendevin.logger import opendevin_logger as logger
  3. from opendevin.exceptions import PlanInvalidStateError
  4. OPEN_STATE = 'open'
  5. COMPLETED_STATE = 'completed'
  6. ABANDONED_STATE = 'abandoned'
  7. IN_PROGRESS_STATE = 'in_progress'
  8. VERIFIED_STATE = 'verified'
  9. STATES = [OPEN_STATE, COMPLETED_STATE,
  10. ABANDONED_STATE, IN_PROGRESS_STATE, VERIFIED_STATE]
  11. class Task:
  12. id: str
  13. goal: str
  14. parent: 'Task | None'
  15. subtasks: List['Task']
  16. def __init__(self, parent: 'Task | None', goal: str, state: str = OPEN_STATE, subtasks: List = []):
  17. """Initializes a new instance of the Task class.
  18. Args:
  19. parent: The parent task, or None if it is the root task.
  20. goal: The goal of the task.
  21. state: The initial state of the task.
  22. subtasks: A list of subtasks associated with this task.
  23. """
  24. if parent is None:
  25. self.id = '0'
  26. else:
  27. self.id = parent.id + '.' + str(len(parent.subtasks))
  28. self.parent = parent
  29. self.goal = goal
  30. self.subtasks = []
  31. for subtask in (subtasks or []):
  32. if isinstance(subtask, Task):
  33. self.subtasks.append(subtask)
  34. else:
  35. goal = subtask.get('goal')
  36. state = subtask.get('state')
  37. subtasks = subtask.get('subtasks')
  38. self.subtasks.append(Task(self, goal, state, subtasks))
  39. self.state = OPEN_STATE
  40. def to_string(self, indent=''):
  41. """Returns a string representation of the task and its subtasks.
  42. Args:
  43. indent: The indentation string for formatting the output.
  44. Returns:
  45. A string representation of the task and its subtasks.
  46. """
  47. emoji = ''
  48. if self.state == VERIFIED_STATE:
  49. emoji = '✅'
  50. elif self.state == COMPLETED_STATE:
  51. emoji = '🟢'
  52. elif self.state == ABANDONED_STATE:
  53. emoji = '❌'
  54. elif self.state == IN_PROGRESS_STATE:
  55. emoji = '💪'
  56. elif self.state == OPEN_STATE:
  57. emoji = '🔵'
  58. result = indent + emoji + ' ' + self.id + ' ' + self.goal + '\n'
  59. for subtask in self.subtasks:
  60. result += subtask.to_string(indent + ' ')
  61. return result
  62. def to_dict(self):
  63. """Returns a dictionary representation of the task.
  64. Returns:
  65. A dictionary containing the task's attributes.
  66. """
  67. return {
  68. 'id': self.id,
  69. 'goal': self.goal,
  70. 'state': self.state,
  71. 'subtasks': [t.to_dict() for t in self.subtasks]
  72. }
  73. def set_state(self, state):
  74. """Sets the state of the task and its subtasks.
  75. Args: state: The new state of the task.
  76. Raises:
  77. PlanInvalidStateError: If the provided state is invalid.
  78. """
  79. if state not in STATES:
  80. logger.error('Invalid state: %s', state)
  81. raise PlanInvalidStateError(state)
  82. self.state = state
  83. if state == COMPLETED_STATE or state == ABANDONED_STATE or state == VERIFIED_STATE:
  84. for subtask in self.subtasks:
  85. if subtask.state != ABANDONED_STATE:
  86. subtask.set_state(state)
  87. elif state == IN_PROGRESS_STATE:
  88. if self.parent is not None:
  89. self.parent.set_state(state)
  90. def get_current_task(self) -> 'Task | None':
  91. """Retrieves the current task in progress.
  92. Returns:
  93. The current task in progress, or None if no task is in progress.
  94. """
  95. for subtask in self.subtasks:
  96. if subtask.state == IN_PROGRESS_STATE:
  97. return subtask.get_current_task()
  98. if self.state == IN_PROGRESS_STATE:
  99. return self
  100. return None
  101. class Plan:
  102. """Represents a plan consisting of tasks.
  103. Attributes:
  104. main_goal: The main goal of the plan.
  105. task: The root task of the plan.
  106. """
  107. main_goal: str
  108. task: Task
  109. def __init__(self, task: str):
  110. """Initializes a new instance of the Plan class.
  111. Args:
  112. task: The main goal of the plan.
  113. """
  114. self.main_goal = task
  115. self.task = Task(parent=None, goal=task, subtasks=[])
  116. def __str__(self):
  117. """Returns a string representation of the plan.
  118. Returns:
  119. A string representation of the plan.
  120. """
  121. return self.task.to_string()
  122. def get_task_by_id(self, id: str) -> Task:
  123. """Retrieves a task by its ID.
  124. Args:
  125. id: The ID of the task.
  126. Returns:
  127. The task with the specified ID.
  128. Raises:
  129. ValueError: If the provided task ID is invalid or does not exist.
  130. """
  131. try:
  132. parts = [int(p) for p in id.split('.')]
  133. except ValueError:
  134. raise ValueError('Invalid task id, non-integer:' + id)
  135. if parts[0] != 0:
  136. raise ValueError('Invalid task id, must start with 0:' + id)
  137. parts = parts[1:]
  138. task = self.task
  139. for part in parts:
  140. if part >= len(task.subtasks):
  141. raise ValueError('Task does not exist:' + id)
  142. task = task.subtasks[part]
  143. return task
  144. def add_subtask(self, parent_id: str, goal: str, subtasks: List = []):
  145. """Adds a subtask to a parent task.
  146. Args:
  147. parent_id: The ID of the parent task.
  148. goal: The goal of the subtask.
  149. subtasks: A list of subtasks associated with the new subtask.
  150. """
  151. parent = self.get_task_by_id(parent_id)
  152. child = Task(parent=parent, goal=goal, subtasks=subtasks)
  153. parent.subtasks.append(child)
  154. def set_subtask_state(self, id: str, state: str):
  155. """Sets the state of a subtask.
  156. Args:
  157. id: The ID of the subtask.
  158. state: The new state of the subtask.
  159. """
  160. task = self.get_task_by_id(id)
  161. task.set_state(state)
  162. def get_current_task(self):
  163. """Retrieves the current task in progress.
  164. Returns:
  165. The current task in progress, or None if no task is in progress.
  166. """
  167. return self.task.get_current_task()