runtime.py 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156
  1. from abc import abstractmethod
  2. from typing import Any, Optional
  3. from opendevin.core.config import config
  4. from opendevin.core.exceptions import BrowserInitException
  5. from opendevin.core.logger import opendevin_logger as logger
  6. from opendevin.events import EventSource, EventStream, EventStreamSubscriber
  7. from opendevin.events.action import (
  8. Action,
  9. AgentRecallAction,
  10. BrowseInteractiveAction,
  11. BrowseURLAction,
  12. CmdRunAction,
  13. FileReadAction,
  14. FileWriteAction,
  15. IPythonRunCellAction,
  16. )
  17. from opendevin.events.event import Event
  18. from opendevin.events.observation import (
  19. ErrorObservation,
  20. NullObservation,
  21. Observation,
  22. )
  23. from opendevin.events.serialization.action import ACTION_TYPE_TO_CLASS
  24. from opendevin.runtime import (
  25. DockerSSHBox,
  26. E2BBox,
  27. LocalBox,
  28. Sandbox,
  29. )
  30. from opendevin.runtime.browser.browser_env import BrowserEnv
  31. from opendevin.runtime.plugins import PluginRequirement
  32. from opendevin.runtime.tools import RuntimeTool
  33. from opendevin.storage import FileStore, InMemoryFileStore
  34. def create_sandbox(sid: str = 'default', box_type: str = 'ssh') -> Sandbox:
  35. if box_type == 'local':
  36. return LocalBox()
  37. elif box_type == 'ssh':
  38. return DockerSSHBox(sid=sid)
  39. elif box_type == 'e2b':
  40. return E2BBox()
  41. else:
  42. raise ValueError(f'Invalid sandbox type: {box_type}')
  43. class Runtime:
  44. """
  45. The runtime is how the agent interacts with the external environment.
  46. This includes a bash sandbox, a browser, and filesystem interactions.
  47. sid is the session id, which is used to identify the current user session.
  48. """
  49. sid: str
  50. file_store: FileStore
  51. def __init__(
  52. self,
  53. event_stream: EventStream,
  54. sid: str = 'default',
  55. sandbox: Sandbox | None = None,
  56. ):
  57. self.sid = sid
  58. if sandbox is None:
  59. self.sandbox = create_sandbox(sid, config.sandbox.box_type)
  60. self._is_external_sandbox = False
  61. else:
  62. self.sandbox = sandbox
  63. self._is_external_sandbox = True
  64. self.browser: BrowserEnv | None = None
  65. self.file_store = InMemoryFileStore()
  66. self.event_stream = event_stream
  67. self.event_stream.subscribe(EventStreamSubscriber.RUNTIME, self.on_event)
  68. def close(self):
  69. if not self._is_external_sandbox:
  70. self.sandbox.close()
  71. if self.browser is not None:
  72. self.browser.close()
  73. def init_sandbox_plugins(self, plugins: list[PluginRequirement]) -> None:
  74. self.sandbox.init_plugins(plugins)
  75. def init_runtime_tools(
  76. self,
  77. runtime_tools: list[RuntimeTool],
  78. runtime_tools_config: Optional[dict[RuntimeTool, Any]] = None,
  79. is_async: bool = True,
  80. ) -> None:
  81. # if browser in runtime_tools, init it
  82. if RuntimeTool.BROWSER in runtime_tools:
  83. if runtime_tools_config is None:
  84. runtime_tools_config = {}
  85. browser_env_config = runtime_tools_config.get(RuntimeTool.BROWSER, {})
  86. try:
  87. self.browser = BrowserEnv(is_async=is_async, **browser_env_config)
  88. except BrowserInitException:
  89. logger.warn(
  90. 'Failed to start browser environment, web browsing functionality will not work'
  91. )
  92. async def on_event(self, event: Event) -> None:
  93. if isinstance(event, Action):
  94. observation = await self.run_action(event)
  95. observation._cause = event.id # type: ignore[attr-defined]
  96. source = event.source if event.source else EventSource.AGENT
  97. self.event_stream.add_event(observation, source)
  98. async def run_action(self, action: Action) -> Observation:
  99. """
  100. Run an action and return the resulting observation.
  101. If the action is not runnable in any runtime, a NullObservation is returned.
  102. If the action is not supported by the current runtime, an ErrorObservation is returned.
  103. """
  104. if not action.runnable:
  105. return NullObservation('')
  106. action_type = action.action # type: ignore[attr-defined]
  107. if action_type not in ACTION_TYPE_TO_CLASS:
  108. return ErrorObservation(f'Action {action_type} does not exist.')
  109. if not hasattr(self, action_type):
  110. return ErrorObservation(
  111. f'Action {action_type} is not supported in the current runtime.'
  112. )
  113. observation = await getattr(self, action_type)(action)
  114. observation._parent = action.id # type: ignore[attr-defined]
  115. return observation
  116. @abstractmethod
  117. async def run(self, action: CmdRunAction) -> Observation:
  118. pass
  119. @abstractmethod
  120. async def run_ipython(self, action: IPythonRunCellAction) -> Observation:
  121. pass
  122. @abstractmethod
  123. async def read(self, action: FileReadAction) -> Observation:
  124. pass
  125. @abstractmethod
  126. async def write(self, action: FileWriteAction) -> Observation:
  127. pass
  128. @abstractmethod
  129. async def browse(self, action: BrowseURLAction) -> Observation:
  130. pass
  131. @abstractmethod
  132. async def browse_interactive(self, action: BrowseInteractiveAction) -> Observation:
  133. pass
  134. @abstractmethod
  135. async def recall(self, action: AgentRecallAction) -> Observation:
  136. pass