170 lines
5.2 KiB
Python
170 lines
5.2 KiB
Python
"""E2B API-based sandbox backend."""
|
|
|
|
import time
|
|
from typing import Any
|
|
|
|
from app.services.sandbox.base import BaseSandboxBackend, ExecutionResult, SandboxCapabilities
|
|
from app.services.sandbox.config import SandboxConfig
|
|
from loguru import logger
|
|
|
|
# Lazy import e2b to make it optional
|
|
_e2b = None
|
|
|
|
|
|
def _get_e2b():
|
|
"""Lazy load e2b SDK."""
|
|
global _e2b
|
|
if _e2b is None:
|
|
try:
|
|
import e2b
|
|
_e2b = e2b
|
|
except ImportError:
|
|
raise ImportError(
|
|
"e2b package is required for E2B backend. "
|
|
"Install it with: pip install e2b"
|
|
)
|
|
return _e2b
|
|
|
|
|
|
# Language mapping for E2B
|
|
_LANGUAGE_MAP = {
|
|
"python": "python",
|
|
"bash": "bash",
|
|
"node": "node",
|
|
"javascript": "javascript",
|
|
}
|
|
|
|
|
|
class E2bBackend(BaseSandboxBackend):
|
|
"""E2B cloud-based sandbox backend.
|
|
|
|
E2B (https://e2b.dev/) provides secure, cloud-based code execution
|
|
with built-in isolation and networking.
|
|
"""
|
|
|
|
name = "e2b"
|
|
|
|
def __init__(self, config: SandboxConfig):
|
|
self.config = config
|
|
self._client = None
|
|
|
|
if not config.api_key:
|
|
raise ValueError("E2B API key is required. Set SANDBOX_API_KEY environment variable.")
|
|
|
|
@property
|
|
def client(self):
|
|
"""Get or create E2B client."""
|
|
e2b_lib = _get_e2b()
|
|
if self._client is None:
|
|
self._client = e2b_lib.AsyncSandbox
|
|
return self._client
|
|
|
|
def get_capabilities(self) -> SandboxCapabilities:
|
|
return SandboxCapabilities(
|
|
supported_languages=["python", "bash", "node", "javascript"],
|
|
max_timeout=self.config.max_timeout,
|
|
max_memory_mb=512,
|
|
network_available=True,
|
|
filesystem_available=True,
|
|
)
|
|
|
|
async def health_check(self) -> bool:
|
|
"""Check if E2B service is available."""
|
|
try:
|
|
e2b_lib = _get_e2b()
|
|
# Try to list sandboxes to verify API is accessible
|
|
await e2b_lib.AsyncSandbox.list(api_key=self.config.api_key)
|
|
return True
|
|
except Exception:
|
|
return False
|
|
|
|
async def execute(
|
|
self,
|
|
code: str,
|
|
language: str,
|
|
timeout: int = 30,
|
|
work_dir: str | None = None,
|
|
**kwargs
|
|
) -> ExecutionResult:
|
|
"""Execute code using E2B cloud sandbox."""
|
|
start_time = time.time()
|
|
|
|
# Map language to E2B format
|
|
e2b_language = _LANGUAGE_MAP.get(language, language)
|
|
if language not in _LANGUAGE_MAP:
|
|
return ExecutionResult(
|
|
success=False,
|
|
stdout="",
|
|
stderr="",
|
|
exit_code=1,
|
|
duration_ms=int((time.time() - start_time) * 1000),
|
|
error=f"Unsupported language: {language}"
|
|
)
|
|
|
|
e2b_lib = _get_e2b()
|
|
|
|
try:
|
|
# Create sandbox and run code
|
|
async with await e2b_lib.AsyncSandbox.create(
|
|
api_key=self.config.api_key,
|
|
timeout=timeout,
|
|
) as sandbox:
|
|
# Build the command based on language
|
|
if e2b_language == "python":
|
|
cmd = "python3"
|
|
args = ["-c", code]
|
|
elif e2b_language == "bash":
|
|
cmd = "bash"
|
|
args = ["-c", code]
|
|
elif e2b_language in ("node", "javascript"):
|
|
cmd = "node"
|
|
args = ["-e", code]
|
|
else:
|
|
return ExecutionResult(
|
|
success=False,
|
|
stdout="",
|
|
stderr="",
|
|
exit_code=1,
|
|
duration_ms=int((time.time() - start_time) * 1000),
|
|
error=f"Unsupported language: {language}"
|
|
)
|
|
|
|
# Run the command - use string format for e2b
|
|
cmd_str = f"{cmd} {args[0]} '{args[1]}'"
|
|
result = await sandbox.commands.run(cmd_str)
|
|
|
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
|
|
return ExecutionResult(
|
|
success=result.exit_code == 0,
|
|
stdout=result.stdout or "",
|
|
stderr=result.stderr or "",
|
|
exit_code=result.exit_code or 0,
|
|
duration_ms=duration_ms,
|
|
error=None
|
|
)
|
|
|
|
except Exception as e:
|
|
duration_ms = int((time.time() - start_time) * 1000)
|
|
error_msg = str(e)
|
|
logger.exception(f"[E2B] Execution error")
|
|
|
|
# Handle timeout
|
|
if "timeout" in error_msg.lower():
|
|
return ExecutionResult(
|
|
success=False,
|
|
stdout="",
|
|
stderr="",
|
|
exit_code=124,
|
|
duration_ms=duration_ms,
|
|
error=f"Code execution timed out after {timeout}s"
|
|
)
|
|
|
|
return ExecutionResult(
|
|
success=False,
|
|
stdout="",
|
|
stderr="",
|
|
exit_code=1,
|
|
duration_ms=duration_ms,
|
|
error=f"E2B execution error: {error_msg[:200]}"
|
|
) |