runtime.py 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193
  1. from typing import Any, Optional
  2. from opendevin.core.config import config
  3. from opendevin.core.exceptions import BrowserInitException
  4. from opendevin.core.logger import opendevin_logger as logger
  5. from opendevin.events.action import (
  6. BrowseInteractiveAction,
  7. BrowseURLAction,
  8. CmdRunAction,
  9. FileReadAction,
  10. FileWriteAction,
  11. IPythonRunCellAction,
  12. )
  13. from opendevin.events.observation import (
  14. CmdOutputObservation,
  15. ErrorObservation,
  16. IPythonRunCellObservation,
  17. Observation,
  18. )
  19. from opendevin.events.stream import EventStream
  20. from opendevin.runtime import (
  21. DockerSSHBox,
  22. E2BBox,
  23. LocalBox,
  24. Sandbox,
  25. )
  26. from opendevin.runtime.browser.browser_env import BrowserEnv
  27. from opendevin.runtime.plugins import PluginRequirement
  28. from opendevin.runtime.runtime import Runtime
  29. from opendevin.runtime.tools import RuntimeTool
  30. from opendevin.storage.local import LocalFileStore
  31. from ..browser import browse
  32. from .files import read_file, write_file
  33. def create_sandbox(sid: str = 'default', box_type: str = 'ssh') -> Sandbox:
  34. if box_type == 'local':
  35. return LocalBox()
  36. elif box_type == 'ssh':
  37. return DockerSSHBox(sid=sid)
  38. elif box_type == 'e2b':
  39. return E2BBox()
  40. else:
  41. raise ValueError(f'Invalid sandbox type: {box_type}')
  42. class ServerRuntime(Runtime):
  43. def __init__(
  44. self,
  45. event_stream: EventStream,
  46. sid: str = 'default',
  47. sandbox: Sandbox | None = None,
  48. ):
  49. super().__init__(event_stream, sid)
  50. self.file_store = LocalFileStore(config.workspace_base)
  51. if sandbox is None:
  52. self.sandbox = create_sandbox(sid, config.sandbox.box_type)
  53. self._is_external_sandbox = False
  54. else:
  55. self.sandbox = sandbox
  56. self._is_external_sandbox = True
  57. self.browser: BrowserEnv | None = None
  58. async def ainit(self) -> None:
  59. pass
  60. async def close(self):
  61. if not self._is_external_sandbox:
  62. self.sandbox.close()
  63. if self.browser is not None:
  64. self.browser.close()
  65. def init_sandbox_plugins(self, plugins: list[PluginRequirement]) -> None:
  66. self.sandbox.init_plugins(plugins)
  67. def init_runtime_tools(
  68. self,
  69. runtime_tools: list[RuntimeTool],
  70. runtime_tools_config: Optional[dict[RuntimeTool, Any]] = None,
  71. is_async: bool = True,
  72. ) -> None:
  73. # if browser in runtime_tools, init it
  74. if RuntimeTool.BROWSER in runtime_tools:
  75. if runtime_tools_config is None:
  76. runtime_tools_config = {}
  77. browser_env_config = runtime_tools_config.get(RuntimeTool.BROWSER, {})
  78. try:
  79. self.browser = BrowserEnv(is_async=is_async, **browser_env_config)
  80. except BrowserInitException:
  81. logger.warn(
  82. 'Failed to start browser environment, web browsing functionality will not work'
  83. )
  84. async def run(self, action: CmdRunAction) -> Observation:
  85. return self._run_command(action.command)
  86. async def run_ipython(self, action: IPythonRunCellAction) -> Observation:
  87. obs = self._run_command(
  88. ("cat > /tmp/opendevin_jupyter_temp.py <<'EOL'\n" f'{action.code}\n' 'EOL'),
  89. )
  90. # run the code
  91. obs = self._run_command('cat /tmp/opendevin_jupyter_temp.py | execute_cli')
  92. output = obs.content
  93. if 'pip install' in action.code:
  94. print(output)
  95. package_names = action.code.split(' ', 2)[-1]
  96. is_single_package = ' ' not in package_names
  97. if 'Successfully installed' in output:
  98. restart_kernel = 'import IPython\nIPython.Application.instance().kernel.do_shutdown(True)'
  99. if (
  100. 'Note: you may need to restart the kernel to use updated packages.'
  101. in output
  102. ):
  103. self._run_command(
  104. (
  105. "cat > /tmp/opendevin_jupyter_temp.py <<'EOL'\n"
  106. f'{restart_kernel}\n'
  107. 'EOL'
  108. )
  109. )
  110. obs = self._run_command(
  111. 'cat /tmp/opendevin_jupyter_temp.py | execute_cli'
  112. )
  113. output = '[Package installed successfully]'
  114. if "{'status': 'ok', 'restart': True}" != obs.content.strip():
  115. print(obs.content)
  116. output += (
  117. '\n[But failed to restart the kernel to load the package]'
  118. )
  119. else:
  120. output += (
  121. '\n[Kernel restarted successfully to load the package]'
  122. )
  123. # re-init the kernel after restart
  124. if action.kernel_init_code:
  125. obs = self._run_command(
  126. (
  127. f"cat > /tmp/opendevin_jupyter_init.py <<'EOL'\n"
  128. f'{action.kernel_init_code}\n'
  129. 'EOL'
  130. ),
  131. )
  132. obs = self._run_command(
  133. 'cat /tmp/opendevin_jupyter_init.py | execute_cli',
  134. )
  135. elif (
  136. is_single_package
  137. and f'Requirement already satisfied: {package_names}' in output
  138. ):
  139. output = '[Package already installed]'
  140. return IPythonRunCellObservation(content=output, code=action.code)
  141. async def read(self, action: FileReadAction) -> Observation:
  142. # TODO: use self.file_store
  143. working_dir = self.sandbox.get_working_directory()
  144. return await read_file(action.path, working_dir, action.start, action.end)
  145. async def write(self, action: FileWriteAction) -> Observation:
  146. # TODO: use self.file_store
  147. working_dir = self.sandbox.get_working_directory()
  148. return await write_file(
  149. action.path, working_dir, action.content, action.start, action.end
  150. )
  151. async def browse(self, action: BrowseURLAction) -> Observation:
  152. return await browse(action, self.browser)
  153. async def browse_interactive(self, action: BrowseInteractiveAction) -> Observation:
  154. return await browse(action, self.browser)
  155. def _run_command(self, command: str) -> Observation:
  156. try:
  157. exit_code, output = self.sandbox.execute(command)
  158. if 'pip install' in command:
  159. package_names = command.split(' ', 2)[-1]
  160. is_single_package = ' ' not in package_names
  161. print(output)
  162. if 'Successfully installed' in output:
  163. output = '[Package installed successfully]'
  164. elif (
  165. is_single_package
  166. and f'Requirement already satisfied: {package_names}' in output
  167. ):
  168. output = '[Package already installed]'
  169. return CmdOutputObservation(
  170. command_id=-1, content=str(output), command=command, exit_code=exit_code
  171. )
  172. except UnicodeDecodeError:
  173. return ErrorObservation('Command output could not be decoded as utf-8')