local_box.py 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. import atexit
  2. import os
  3. import subprocess
  4. import sys
  5. from opendevin.core.config import SandboxConfig
  6. from opendevin.core.logger import opendevin_logger as logger
  7. from opendevin.core.schema import CancellableStream
  8. from opendevin.runtime.sandbox import Sandbox
  9. # ===============================================================================
  10. # ** WARNING **
  11. #
  12. # This sandbox should only be used when OpenDevin is running inside a container
  13. #
  14. # Sandboxes are generally isolated so that they cannot affect the host machine.
  15. # This Sandbox implementation does not provide isolation, and can inadvertently
  16. # run dangerous commands on the host machine, potentially rendering the host
  17. # machine unusable.
  18. #
  19. # This sandbox is meant for use with OpenDevin Quickstart
  20. #
  21. # DO NOT USE THIS SANDBOX IN A PRODUCTION ENVIRONMENT
  22. # ===============================================================================
  23. class LocalBox(Sandbox):
  24. def __init__(
  25. self,
  26. config: SandboxConfig,
  27. workspace_base: str,
  28. ):
  29. self.config = config
  30. os.makedirs(workspace_base, exist_ok=True)
  31. self.workspace_base = workspace_base
  32. atexit.register(self.cleanup)
  33. super().__init__(config)
  34. def execute(
  35. self, cmd: str, stream: bool = False, timeout: int | None = None
  36. ) -> tuple[int, str | CancellableStream]:
  37. try:
  38. completed_process = subprocess.run(
  39. cmd,
  40. shell=True,
  41. text=True,
  42. capture_output=True,
  43. timeout=self.config.timeout,
  44. cwd=self.workspace_base,
  45. env=self._env,
  46. )
  47. return completed_process.returncode, completed_process.stdout.strip()
  48. except subprocess.TimeoutExpired:
  49. return -1, 'Command timed out'
  50. def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False):
  51. # mkdir -p sandbox_dest if it doesn't exist
  52. res = subprocess.run(
  53. f'mkdir -p {sandbox_dest}',
  54. shell=True,
  55. text=True,
  56. cwd=self.workspace_base,
  57. env=self._env,
  58. )
  59. if res.returncode != 0:
  60. raise RuntimeError(f'Failed to create directory {sandbox_dest} in sandbox')
  61. if recursive:
  62. res = subprocess.run(
  63. f'cp -r {host_src} {sandbox_dest}',
  64. shell=True,
  65. text=True,
  66. cwd=self.workspace_base,
  67. env=self._env,
  68. )
  69. if res.returncode != 0:
  70. raise RuntimeError(
  71. f'Failed to copy {host_src} to {sandbox_dest} in sandbox'
  72. )
  73. else:
  74. res = subprocess.run(
  75. f'cp {host_src} {sandbox_dest}',
  76. shell=True,
  77. text=True,
  78. cwd=self.workspace_base,
  79. env=self._env,
  80. )
  81. if res.returncode != 0:
  82. raise RuntimeError(
  83. f'Failed to copy {host_src} to {sandbox_dest} in sandbox'
  84. )
  85. def close(self):
  86. pass
  87. def cleanup(self):
  88. self.close()
  89. def get_working_directory(self):
  90. return self.workspace_base
  91. if __name__ == '__main__':
  92. local_box = LocalBox(SandboxConfig(), '/tmp/opendevin')
  93. sys.stdout.flush()
  94. try:
  95. while True:
  96. try:
  97. user_input = input('>>> ')
  98. except EOFError:
  99. logger.info('Exiting...')
  100. break
  101. if user_input.lower() == 'exit':
  102. logger.info('Exiting...')
  103. break
  104. exit_code, output = local_box.execute(user_input)
  105. logger.info('exit code: %d', exit_code)
  106. logger.info(output)
  107. sys.stdout.flush()
  108. except KeyboardInterrupt:
  109. logger.info('Exiting...')
  110. local_box.close()