Spaces:
Build error
Build error
| import copy | |
| import os | |
| import tarfile | |
| from glob import glob | |
| from e2b import Sandbox as E2BSandbox | |
| from e2b.exceptions import TimeoutException | |
| from openhands.core.config import SandboxConfig | |
| from openhands.core.logger import openhands_logger as logger | |
| class E2BBox: | |
| closed = False | |
| _cwd: str = '/home/user' | |
| _env: dict[str, str] = {} | |
| is_initial_session: bool = True | |
| def __init__( | |
| self, | |
| config: SandboxConfig, | |
| e2b_api_key: str, | |
| template: str = 'openhands', | |
| ): | |
| self.config = copy.deepcopy(config) | |
| self.initialize_plugins: bool = config.initialize_plugins | |
| self.sandbox = E2BSandbox( | |
| api_key=e2b_api_key, | |
| template=template, | |
| # It's possible to stream stdout and stderr from sandbox and from each process | |
| on_stderr=lambda x: logger.debug(f'E2B sandbox stderr: {x}'), | |
| on_stdout=lambda x: logger.debug(f'E2B sandbox stdout: {x}'), | |
| cwd=self._cwd, # Default workdir inside sandbox | |
| ) | |
| logger.debug(f'Started E2B sandbox with ID "{self.sandbox.id}"') | |
| def filesystem(self): | |
| return self.sandbox.filesystem | |
| def _archive(self, host_src: str, recursive: bool = False): | |
| if recursive: | |
| assert os.path.isdir(host_src), ( | |
| 'Source must be a directory when recursive is True' | |
| ) | |
| files = glob(host_src + '/**/*', recursive=True) | |
| srcname = os.path.basename(host_src) | |
| tar_filename = os.path.join(os.path.dirname(host_src), srcname + '.tar') | |
| with tarfile.open(tar_filename, mode='w') as tar: | |
| for file in files: | |
| tar.add( | |
| file, arcname=os.path.relpath(file, os.path.dirname(host_src)) | |
| ) | |
| else: | |
| assert os.path.isfile(host_src), ( | |
| 'Source must be a file when recursive is False' | |
| ) | |
| srcname = os.path.basename(host_src) | |
| tar_filename = os.path.join(os.path.dirname(host_src), srcname + '.tar') | |
| with tarfile.open(tar_filename, mode='w') as tar: | |
| tar.add(host_src, arcname=srcname) | |
| return tar_filename | |
| def execute(self, cmd: str, timeout: int | None = None) -> tuple[int, str]: | |
| timeout = timeout if timeout is not None else self.config.timeout | |
| process = self.sandbox.process.start(cmd, env_vars=self._env) | |
| try: | |
| process_output = process.wait(timeout=timeout) | |
| except TimeoutException: | |
| logger.debug('Command timed out, killing process...') | |
| process.kill() | |
| return -1, f'Command: "{cmd}" timed out' | |
| logs = [m.line for m in process_output.messages] | |
| logs_str = '\n'.join(logs) | |
| if process.exit_code is None: | |
| return -1, logs_str | |
| assert process_output.exit_code is not None | |
| return process_output.exit_code, logs_str | |
| def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False): | |
| """Copies a local file or directory to the sandbox.""" | |
| tar_filename = self._archive(host_src, recursive) | |
| # Prepend the sandbox destination with our sandbox cwd | |
| sandbox_dest = os.path.join(self._cwd, sandbox_dest.removeprefix('/')) | |
| with open(tar_filename, 'rb') as tar_file: | |
| # Upload the archive to /home/user (default destination that always exists) | |
| uploaded_path = self.sandbox.upload_file(tar_file) | |
| # Check if sandbox_dest exists. If not, create it. | |
| process = self.sandbox.process.start_and_wait(f'test -d {sandbox_dest}') | |
| if process.exit_code != 0: | |
| self.sandbox.filesystem.make_dir(sandbox_dest) | |
| # Extract the archive into the destination and delete the archive | |
| process = self.sandbox.process.start_and_wait( | |
| f'sudo tar -xf {uploaded_path} -C {sandbox_dest} && sudo rm {uploaded_path}' | |
| ) | |
| if process.exit_code != 0: | |
| raise Exception( | |
| f'Failed to extract {uploaded_path} to {sandbox_dest}: {process.stderr}' | |
| ) | |
| # Delete the local archive | |
| os.remove(tar_filename) | |
| def close(self): | |
| self.sandbox.close() | |
| def get_working_directory(self): | |
| return self.sandbox.cwd | |