local_box.py 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172
  1. import subprocess
  2. import atexit
  3. from typing import Tuple, Dict
  4. from opendevin.sandbox.sandbox import Sandbox, BackgroundCommand
  5. from opendevin import config
  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, timeout: int = 120):
  22. self.timeout = timeout
  23. self.background_commands: Dict[int, BackgroundCommand] = {}
  24. self.cur_background_id = 0
  25. atexit.register(self.cleanup)
  26. def execute(self, cmd: str) -> Tuple[int, str]:
  27. try:
  28. completed_process = subprocess.run(
  29. cmd, shell=True, text=True, capture_output=True,
  30. timeout=self.timeout, cwd=config.get('WORKSPACE_BASE')
  31. )
  32. return completed_process.returncode, completed_process.stdout
  33. except subprocess.TimeoutExpired:
  34. return -1, 'Command timed out'
  35. def execute_in_background(self, cmd: str) -> BackgroundCommand:
  36. process = subprocess.Popen(
  37. cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
  38. text=True, cwd=config.get('WORKSPACE_BASE')
  39. )
  40. bg_cmd = BackgroundCommand(
  41. id=self.cur_background_id, command=cmd, result=process, pid=process.pid
  42. )
  43. self.background_commands[self.cur_background_id] = bg_cmd
  44. self.cur_background_id += 1
  45. return bg_cmd
  46. def kill_background(self, id: int):
  47. if id not in self.background_commands:
  48. raise ValueError('Invalid background command id')
  49. bg_cmd = self.background_commands[id]
  50. bg_cmd.result.terminate() # terminate the process
  51. bg_cmd.result.wait() # wait for process to terminate
  52. self.background_commands.pop(id)
  53. def read_logs(self, id: int) -> str:
  54. if id not in self.background_commands:
  55. raise ValueError('Invalid background command id')
  56. bg_cmd = self.background_commands[id]
  57. output = bg_cmd.result.stdout.read()
  58. return output.decode('utf-8')
  59. def close(self):
  60. for id, bg_cmd in list(self.background_commands.items()):
  61. self.kill_background(id)
  62. def cleanup(self):
  63. self.close()