瀏覽代碼

Add new sandbox type - local (#1029)

RaGe 1 年之前
父節點
當前提交
d27895d018
共有 3 個文件被更改,包括 78 次插入1 次删除
  1. 3 1
      opendevin/controller/command_manager.py
  2. 2 0
      opendevin/sandbox/__init__.py
  3. 73 0
      opendevin/sandbox/local_box.py

+ 3 - 1
opendevin/controller/command_manager.py

@@ -2,7 +2,7 @@ from typing import List
 
 from opendevin import config
 from opendevin.observation import CmdOutputObservation
-from opendevin.sandbox import DockerExecBox, DockerSSHBox, Sandbox
+from opendevin.sandbox import DockerExecBox, DockerSSHBox, Sandbox, LocalBox
 from opendevin.schema import ConfigType
 
 
@@ -22,6 +22,8 @@ class CommandManager:
             self.shell = DockerExecBox(
                 sid=(sid or 'default'), workspace_dir=directory, container_image=container_image
             )
+        elif config.get('SANDBOX_TYPE').lower() == 'local':
+            self.shell = LocalBox(workspace_dir=directory)
         else:
             self.shell = DockerSSHBox(
                 sid=(sid or 'default'), workspace_dir=directory, container_image=container_image

+ 2 - 0
opendevin/sandbox/__init__.py

@@ -1,8 +1,10 @@
 from .sandbox import Sandbox
 from .ssh_box import DockerSSHBox
 from .exec_box import DockerExecBox
+from .local_box import LocalBox
 __all__ = [
     'Sandbox',
     'DockerSSHBox',
     'DockerExecBox',
+    'LocalBox'
 ]

+ 73 - 0
opendevin/sandbox/local_box.py

@@ -0,0 +1,73 @@
+import subprocess
+import atexit
+import os
+from typing import Tuple, Dict, Optional
+from opendevin.sandbox.sandbox import Sandbox, BackgroundCommand
+
+
+# ===============================================================================
+#  ** WARNING **
+#
+#  This sandbox should only be used when OpenDevin is running inside a container
+#
+#  Sandboxes are generally isolated so that they cannot affect the host machine.
+#  This Sandbox implementation does not provide isolation, and can inadvertently
+#  run dangerous commands on the host machine, potentially rendering the host
+#  machine unusable.
+#
+#  This sandbox is meant for use with OpenDevin Quickstart
+#
+#  DO NOT USE THIS SANDBOX IN A PRODUCTION ENVIRONMENT
+# ===============================================================================
+
+class LocalBox(Sandbox):
+    def __init__(self, workspace_dir: Optional[str] = None, timeout: int = 120):
+        self.workspace_dir = workspace_dir or os.getcwd()
+        self.timeout = timeout
+        self.background_commands: Dict[int, BackgroundCommand] = {}
+        self.cur_background_id = 0
+        atexit.register(self.cleanup)
+
+    def execute(self, cmd: str) -> Tuple[int, str]:
+        try:
+            completed_process = subprocess.run(
+                cmd, shell=True, text=True, capture_output=True,
+                timeout=self.timeout, cwd=self.workspace_dir
+            )
+            return completed_process.returncode, completed_process.stdout
+        except subprocess.TimeoutExpired:
+            return -1, 'Command timed out'
+
+    def execute_in_background(self, cmd: str) -> BackgroundCommand:
+        process = subprocess.Popen(
+            cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
+            text=True, cwd=self.workspace_dir
+        )
+        bg_cmd = BackgroundCommand(
+            id=self.cur_background_id, command=cmd, result=process, pid=process.pid
+        )
+        self.background_commands[self.cur_background_id] = bg_cmd
+        self.cur_background_id += 1
+        return bg_cmd
+
+    def kill_background(self, id: int):
+        if id not in self.background_commands:
+            raise ValueError('Invalid background command id')
+        bg_cmd = self.background_commands[id]
+        bg_cmd.result.terminate()  # terminate the process
+        bg_cmd.result.wait()  # wait for process to terminate
+        self.background_commands.pop(id)
+
+    def read_logs(self, id: int) -> str:
+        if id not in self.background_commands:
+            raise ValueError('Invalid background command id')
+        bg_cmd = self.background_commands[id]
+        output = bg_cmd.result.stdout.read()
+        return output.decode('utf-8')
+
+    def close(self):
+        for id, bg_cmd in list(self.background_commands.items()):
+            self.kill_background(id)
+
+    def cleanup(self):
+        self.close()