mirror of
https://github.com/OpenHands/OpenHands.git
synced 2026-03-22 13:47:19 +08:00
Integrate copy_to in E2BBox (#1298)
* initialize plugin definition * initialize plugin definition * simplify mixin * further improve plugin mixin * add cache dir for pip * support clean up cache * add script for setup jupyter and execution server * integrate JupyterRequirement to ssh_box * source bashrc at the end of plugin load * add execute_cli that accept code via stdin * make JUPYTER_EXEC_SERVER_PORT configurable via env var * increase background cmd sleep time * Update opendevin/sandbox/plugins/mixin.py Co-authored-by: Robert Brennan <accounts@rbren.io> * add mixin to base class * make jupyter requirement a dataclass * source plugins only when >0 requirements * add `sandbox_plugins` for each agent & have controller take care of it * update build.sh to make logs available in /opendevin/logs * switch to use config for lib and cache dir * fix permission issue with /workspace * use python to implement execute_cli to avoid stdin escape issue * wait until jupyter is avaialble * support plugin via copying instead of mounting * Fix linter issue --------- Co-authored-by: Xingyao Wang <xingyao6@illinois.edu> Co-authored-by: Robert Brennan <accounts@rbren.io>
This commit is contained in:
@@ -1,3 +1,6 @@
|
||||
import os
|
||||
import tarfile
|
||||
from glob import glob
|
||||
from typing import Dict, Tuple
|
||||
from e2b import Sandbox as E2BSandbox
|
||||
from e2b.sandbox.exception import (
|
||||
@@ -15,6 +18,7 @@ class E2BBox(Sandbox):
|
||||
closed = False
|
||||
cur_background_id = 0
|
||||
background_commands: Dict[int, Process] = {}
|
||||
_cwd: str = '/home/user'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
@@ -27,7 +31,7 @@ class E2BBox(Sandbox):
|
||||
# It's possible to stream stdout and stderr from sandbox and from each process
|
||||
on_stderr=lambda x: logger.info(f'E2B sandbox stderr: {x}'),
|
||||
on_stdout=lambda x: logger.info(f'E2B sandbox stdout: {x}'),
|
||||
cwd='/home/user', # Default workdir inside sandbox
|
||||
cwd=self._cwd, # Default workdir inside sandbox
|
||||
)
|
||||
self.timeout = timeout
|
||||
logger.info(f'Started E2B sandbox with ID "{self.sandbox.id}"')
|
||||
@@ -36,6 +40,23 @@ class E2BBox(Sandbox):
|
||||
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
|
||||
|
||||
# TODO: This won't work if we didn't wait for the background process to finish
|
||||
def read_logs(self, process_id: int) -> str:
|
||||
proc = self.background_commands.get(process_id)
|
||||
@@ -62,8 +83,28 @@ class E2BBox(Sandbox):
|
||||
return process_output.exit_code, logs_str
|
||||
|
||||
def copy_to(self, host_src: str, sandbox_dest: str, recursive: bool = False):
|
||||
# FIXME
|
||||
raise NotImplementedError('Copying files to E2B sandbox is not implemented yet')
|
||||
"""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.lstrip('/'))
|
||||
|
||||
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 execute_in_background(self, cmd: str) -> Process:
|
||||
process = self.sandbox.process.start(cmd)
|
||||
|
||||
Reference in New Issue
Block a user