local_box.py 2.8 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273
  1. import subprocess
  2. import atexit
  3. import os
  4. from typing import Tuple, Dict, Optional
  5. from opendevin.sandbox.sandbox import Sandbox, BackgroundCommand
  6. # ===============================================================================
  7. # ** WARNING **
  8. #
  9. # This sandbox should only be used when OpenDevin is running inside a container
  10. #
  11. # Sandboxes are generally isolated so that they cannot affect the host machine.
  12. # This Sandbox implementation does not provide isolation, and can inadvertently
  13. # run dangerous commands on the host machine, potentially rendering the host
  14. # machine unusable.
  15. #
  16. # This sandbox is meant for use with OpenDevin Quickstart
  17. #
  18. # DO NOT USE THIS SANDBOX IN A PRODUCTION ENVIRONMENT
  19. # ===============================================================================
  20. class LocalBox(Sandbox):
  21. def __init__(self, workspace_dir: Optional[str] = None, timeout: int = 120):
  22. self.workspace_dir = workspace_dir or os.getcwd()
  23. self.timeout = timeout
  24. self.background_commands: Dict[int, BackgroundCommand] = {}
  25. self.cur_background_id = 0
  26. atexit.register(self.cleanup)
  27. def execute(self, cmd: str) -> Tuple[int, str]:
  28. try:
  29. completed_process = subprocess.run(
  30. cmd, shell=True, text=True, capture_output=True,
  31. timeout=self.timeout, cwd=self.workspace_dir
  32. )
  33. return completed_process.returncode, completed_process.stdout
  34. except subprocess.TimeoutExpired:
  35. return -1, 'Command timed out'
  36. def execute_in_background(self, cmd: str) -> BackgroundCommand:
  37. process = subprocess.Popen(
  38. cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
  39. text=True, cwd=self.workspace_dir
  40. )
  41. bg_cmd = BackgroundCommand(
  42. id=self.cur_background_id, command=cmd, result=process, pid=process.pid
  43. )
  44. self.background_commands[self.cur_background_id] = bg_cmd
  45. self.cur_background_id += 1
  46. return bg_cmd
  47. def kill_background(self, id: int):
  48. if id not in self.background_commands:
  49. raise ValueError('Invalid background command id')
  50. bg_cmd = self.background_commands[id]
  51. bg_cmd.result.terminate() # terminate the process
  52. bg_cmd.result.wait() # wait for process to terminate
  53. self.background_commands.pop(id)
  54. def read_logs(self, id: int) -> str:
  55. if id not in self.background_commands:
  56. raise ValueError('Invalid background command id')
  57. bg_cmd = self.background_commands[id]
  58. output = bg_cmd.result.stdout.read()
  59. return output.decode('utf-8')
  60. def close(self):
  61. for id, bg_cmd in list(self.background_commands.items()):
  62. self.kill_background(id)
  63. def cleanup(self):
  64. self.close()