mirror of
https://github.com/crewAIInc/crewAI.git
synced 2026-07-02 21:58:11 +00:00
feat: supporting e2b
This commit is contained in:
@@ -143,6 +143,11 @@ daytona = [
|
||||
"daytona~=0.140.0",
|
||||
]
|
||||
|
||||
e2b = [
|
||||
"e2b~=2.20.0",
|
||||
"e2b-code-interpreter~=2.6.0",
|
||||
]
|
||||
|
||||
|
||||
[tool.uv]
|
||||
exclude-newer = "3 days"
|
||||
|
||||
@@ -71,6 +71,11 @@ from crewai_tools.tools.directory_search_tool.directory_search_tool import (
|
||||
DirectorySearchTool,
|
||||
)
|
||||
from crewai_tools.tools.docx_search_tool.docx_search_tool import DOCXSearchTool
|
||||
from crewai_tools.tools.e2b_sandbox_tool import (
|
||||
E2BExecTool,
|
||||
E2BFileTool,
|
||||
E2BPythonTool,
|
||||
)
|
||||
from crewai_tools.tools.exa_tools.exa_search_tool import EXASearchTool
|
||||
from crewai_tools.tools.file_read_tool.file_read_tool import FileReadTool
|
||||
from crewai_tools.tools.file_writer_tool.file_writer_tool import FileWriterTool
|
||||
@@ -242,6 +247,9 @@ __all__ = [
|
||||
"DaytonaPythonTool",
|
||||
"DirectoryReadTool",
|
||||
"DirectorySearchTool",
|
||||
"E2BExecTool",
|
||||
"E2BFileTool",
|
||||
"E2BPythonTool",
|
||||
"EXASearchTool",
|
||||
"EnterpriseActionTool",
|
||||
"FileCompressorTool",
|
||||
|
||||
@@ -60,6 +60,11 @@ from crewai_tools.tools.directory_search_tool.directory_search_tool import (
|
||||
DirectorySearchTool,
|
||||
)
|
||||
from crewai_tools.tools.docx_search_tool.docx_search_tool import DOCXSearchTool
|
||||
from crewai_tools.tools.e2b_sandbox_tool import (
|
||||
E2BExecTool,
|
||||
E2BFileTool,
|
||||
E2BPythonTool,
|
||||
)
|
||||
from crewai_tools.tools.exa_tools.exa_search_tool import EXASearchTool
|
||||
from crewai_tools.tools.file_read_tool.file_read_tool import FileReadTool
|
||||
from crewai_tools.tools.file_writer_tool.file_writer_tool import FileWriterTool
|
||||
@@ -227,6 +232,9 @@ __all__ = [
|
||||
"DaytonaPythonTool",
|
||||
"DirectoryReadTool",
|
||||
"DirectorySearchTool",
|
||||
"E2BExecTool",
|
||||
"E2BFileTool",
|
||||
"E2BPythonTool",
|
||||
"EXASearchTool",
|
||||
"FileCompressorTool",
|
||||
"FileReadTool",
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
# E2B Sandbox Tools
|
||||
|
||||
Run shell commands, execute Python, and manage files inside an [E2B](https://e2b.dev/) sandbox. E2B provides isolated, ephemeral VMs suitable for agent-driven code execution, with a Jupyter-style code interpreter for rich Python results.
|
||||
|
||||
Three tools are provided so you can pick what the agent actually needs:
|
||||
|
||||
- **`E2BExecTool`** — run a shell command (`sandbox.commands.run`).
|
||||
- **`E2BPythonTool`** — run a Python cell in the E2B code interpreter (`sandbox.run_code`), returning stdout/stderr and rich results (charts, dataframes).
|
||||
- **`E2BFileTool`** — read / write / list / delete files (`sandbox.files.*`).
|
||||
|
||||
## Installation
|
||||
|
||||
```shell
|
||||
uv add "crewai-tools[e2b]"
|
||||
# or
|
||||
pip install "crewai-tools[e2b]"
|
||||
```
|
||||
|
||||
Set the API key:
|
||||
|
||||
```shell
|
||||
export E2B_API_KEY="..."
|
||||
```
|
||||
|
||||
`E2B_DOMAIN` is also respected if set (for self-hosted or non-default deployments).
|
||||
|
||||
## Sandbox lifecycle
|
||||
|
||||
All three tools share the same lifecycle controls from `E2BBaseTool`:
|
||||
|
||||
| Mode | When the sandbox is created | When it is killed |
|
||||
| --- | --- | --- |
|
||||
| **Ephemeral** (default, `persistent=False`) | On every `_run` call | At the end of that same call |
|
||||
| **Persistent** (`persistent=True`) | Lazily on first use | At process exit (via `atexit`), or manually via `tool.close()` |
|
||||
| **Attach** (`sandbox_id="…"`) | Never — the tool attaches to an existing sandbox | Never — the tool will not kill a sandbox it did not create |
|
||||
|
||||
Ephemeral mode is the safe default: nothing leaks if the agent forgets to clean up. Use persistent mode when you want filesystem state or installed packages to carry across steps — this is typical when pairing `E2BFileTool` with `E2BExecTool`.
|
||||
|
||||
E2B sandboxes also auto-expire after an idle timeout. Tune it via `sandbox_timeout` (seconds, default `300`).
|
||||
|
||||
## Examples
|
||||
|
||||
### One-shot Python execution (ephemeral)
|
||||
|
||||
```python
|
||||
from crewai_tools import E2BPythonTool
|
||||
|
||||
tool = E2BPythonTool()
|
||||
result = tool.run(code="print(sum(range(10)))")
|
||||
```
|
||||
|
||||
### Multi-step shell session (persistent)
|
||||
|
||||
```python
|
||||
from crewai_tools import E2BExecTool, E2BFileTool
|
||||
|
||||
exec_tool = E2BExecTool(persistent=True)
|
||||
file_tool = E2BFileTool(persistent=True)
|
||||
|
||||
# Each tool keeps its own persistent sandbox. If you need the *same* sandbox
|
||||
# across two tools, create one tool, grab the sandbox id via
|
||||
# `tool._persistent_sandbox.sandbox_id`, and pass it to the other via
|
||||
# `sandbox_id=...`.
|
||||
```
|
||||
|
||||
### Attach to an existing sandbox
|
||||
|
||||
```python
|
||||
from crewai_tools import E2BExecTool
|
||||
|
||||
tool = E2BExecTool(sandbox_id="sbx_...")
|
||||
```
|
||||
|
||||
### Custom create params
|
||||
|
||||
```python
|
||||
tool = E2BExecTool(
|
||||
persistent=True,
|
||||
template="my-custom-template",
|
||||
sandbox_timeout=600,
|
||||
envs={"MY_FLAG": "1"},
|
||||
metadata={"owner": "crewai-agent"},
|
||||
)
|
||||
```
|
||||
|
||||
## Tool arguments
|
||||
|
||||
### `E2BExecTool`
|
||||
- `command: str` — shell command to run.
|
||||
- `cwd: str | None` — working directory.
|
||||
- `envs: dict[str, str] | None` — extra env vars for this command.
|
||||
- `timeout: float | None` — seconds.
|
||||
|
||||
### `E2BPythonTool`
|
||||
- `code: str` — source to execute.
|
||||
- `language: str | None` — override kernel language (default: Python).
|
||||
- `envs: dict[str, str] | None` — env vars for the run.
|
||||
- `timeout: float | None` — seconds.
|
||||
|
||||
### `E2BFileTool`
|
||||
- `action: "read" | "write" | "append" | "list" | "delete" | "mkdir" | "info" | "exists"`
|
||||
- `path: str` — absolute path inside the sandbox.
|
||||
- `content: str | None` — required for `append`; optional for `write`.
|
||||
- `binary: bool` — if `True`, `content` is base64 on write / returned as base64 on read.
|
||||
- `depth: int` — for `list`, how many levels to recurse (default 1).
|
||||
|
||||
## Security considerations
|
||||
|
||||
These tools hand the LLM arbitrary shell, Python, and filesystem access inside a remote VM. The threat model to keep in mind:
|
||||
|
||||
- **Prompt-injection is a code-execution vector.** If the agent ingests untrusted content (web pages, scraped documents, user-supplied files, emails, search results), a malicious instruction hidden in that content can coerce the agent into issuing commands to `E2BExecTool` / `E2BPythonTool`. Treat any pipeline that feeds untrusted text into an agent that also has these tools as equivalent to remote code execution — the LLM is the attacker's shell.
|
||||
- **Ephemeral mode (the default) is the main blast-radius control.** A fresh sandbox is created per call and killed at the end, so injected commands cannot persist state, exfiltrate long-lived secrets, or build up tooling across turns. Leave `persistent=False` unless you have a concrete reason to change it.
|
||||
- **Avoid this specific combination:**
|
||||
- untrusted content in the agent's context, **plus**
|
||||
- `persistent=True` or an explicit long-lived `sandbox_id`, **plus**
|
||||
- a large `sandbox_timeout` or credentials/secrets seeded into the sandbox via `envs`.
|
||||
|
||||
That stack lets a single injection pivot into a long-running, credentialed shell that survives across turns. If you must run persistently, also keep `sandbox_timeout` short, scope `envs` to the minimum the task needs, and don't feed the same agent untrusted input.
|
||||
- **Don't mount production credentials.** Anything you put into `envs`, `metadata`, or files written to the sandbox is reachable from the LLM. Use per-task scoped keys, not your personal API tokens.
|
||||
- **E2B's VM isolation is the final backstop**, not a license to relax the above — isolation prevents escape to the host, but everything the sandbox can reach (the public internet, any service whose token you dropped in) is still fair game for an injected command.
|
||||
@@ -0,0 +1,12 @@
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_base_tool import E2BBaseTool
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_exec_tool import E2BExecTool
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_file_tool import E2BFileTool
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_python_tool import E2BPythonTool
|
||||
|
||||
|
||||
__all__ = [
|
||||
"E2BBaseTool",
|
||||
"E2BExecTool",
|
||||
"E2BFileTool",
|
||||
"E2BPythonTool",
|
||||
]
|
||||
@@ -0,0 +1,197 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import atexit
|
||||
import logging
|
||||
import os
|
||||
import threading
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from crewai.tools import BaseTool, EnvVar
|
||||
from pydantic import ConfigDict, Field, PrivateAttr, SecretStr
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class E2BBaseTool(BaseTool):
|
||||
"""Shared base for tools that act on an E2B sandbox.
|
||||
|
||||
Lifecycle modes:
|
||||
- persistent=False (default): create a fresh sandbox per `_run` call and
|
||||
kill it when the call returns. Safer and stateless — nothing leaks if
|
||||
the agent forgets cleanup.
|
||||
- persistent=True: lazily create a single sandbox on first use, cache it
|
||||
on the instance, and register an atexit hook to kill it at process
|
||||
exit. Cheaper across many calls and lets files/state carry over.
|
||||
- sandbox_id=<existing>: attach to a sandbox the caller already owns.
|
||||
Never killed by the tool.
|
||||
"""
|
||||
|
||||
model_config = ConfigDict(arbitrary_types_allowed=True)
|
||||
|
||||
package_dependencies: list[str] = Field(default_factory=lambda: ["e2b"])
|
||||
|
||||
api_key: SecretStr | None = Field(
|
||||
default_factory=lambda: (
|
||||
SecretStr(val) if (val := os.getenv("E2B_API_KEY")) else None
|
||||
),
|
||||
description="E2B API key. Falls back to E2B_API_KEY env var.",
|
||||
json_schema_extra={"required": False},
|
||||
repr=False,
|
||||
)
|
||||
domain: str | None = Field(
|
||||
default_factory=lambda: os.getenv("E2B_DOMAIN"),
|
||||
description="E2B API domain override. Falls back to E2B_DOMAIN env var.",
|
||||
json_schema_extra={"required": False},
|
||||
)
|
||||
|
||||
template: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Optional template/snapshot name or id to create the sandbox from. "
|
||||
"Defaults to E2B's base template when omitted."
|
||||
),
|
||||
)
|
||||
persistent: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"If True, reuse one sandbox across all calls to this tool instance "
|
||||
"and kill it at process exit. Default False creates and kills a "
|
||||
"fresh sandbox per call."
|
||||
),
|
||||
)
|
||||
sandbox_id: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Attach to an existing sandbox by id instead of creating a new "
|
||||
"one. The tool will never kill a sandbox it did not create."
|
||||
),
|
||||
)
|
||||
sandbox_timeout: int = Field(
|
||||
default=300,
|
||||
description=(
|
||||
"Idle timeout in seconds after which E2B auto-kills the sandbox. "
|
||||
"Applied at create time and when attaching via sandbox_id."
|
||||
),
|
||||
)
|
||||
envs: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
description="Environment variables to set inside the sandbox at create time.",
|
||||
)
|
||||
metadata: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
description="Metadata key-value pairs to attach to the sandbox at create time.",
|
||||
)
|
||||
|
||||
env_vars: list[EnvVar] = Field(
|
||||
default_factory=lambda: [
|
||||
EnvVar(
|
||||
name="E2B_API_KEY",
|
||||
description="API key for E2B sandbox service",
|
||||
required=False,
|
||||
),
|
||||
EnvVar(
|
||||
name="E2B_DOMAIN",
|
||||
description="E2B API domain (optional)",
|
||||
required=False,
|
||||
),
|
||||
]
|
||||
)
|
||||
|
||||
_persistent_sandbox: Any | None = PrivateAttr(default=None)
|
||||
_lock: threading.Lock = PrivateAttr(default_factory=threading.Lock)
|
||||
_cleanup_registered: bool = PrivateAttr(default=False)
|
||||
|
||||
_sdk_cache: ClassVar[dict[str, Any]] = {}
|
||||
|
||||
@classmethod
|
||||
def _import_sandbox_class(cls) -> Any:
|
||||
"""Return the Sandbox class used by this tool.
|
||||
|
||||
Subclasses override this to swap in a different SDK (e.g. the code
|
||||
interpreter sandbox). The default uses plain `e2b.Sandbox`.
|
||||
"""
|
||||
cached = cls._sdk_cache.get("e2b.Sandbox")
|
||||
if cached is not None:
|
||||
return cached
|
||||
try:
|
||||
from e2b import Sandbox # type: ignore[import-untyped]
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"The 'e2b' package is required for E2B sandbox tools. "
|
||||
"Install it with: uv add e2b (or) pip install e2b"
|
||||
) from exc
|
||||
cls._sdk_cache["e2b.Sandbox"] = Sandbox
|
||||
return Sandbox
|
||||
|
||||
def _connect_kwargs(self) -> dict[str, Any]:
|
||||
kwargs: dict[str, Any] = {}
|
||||
if self.api_key is not None:
|
||||
kwargs["api_key"] = self.api_key.get_secret_value()
|
||||
if self.domain:
|
||||
kwargs["domain"] = self.domain
|
||||
if self.sandbox_timeout is not None:
|
||||
kwargs["timeout"] = self.sandbox_timeout
|
||||
return kwargs
|
||||
|
||||
def _create_kwargs(self) -> dict[str, Any]:
|
||||
kwargs: dict[str, Any] = self._connect_kwargs()
|
||||
if self.template is not None:
|
||||
kwargs["template"] = self.template
|
||||
if self.envs is not None:
|
||||
kwargs["envs"] = self.envs
|
||||
if self.metadata is not None:
|
||||
kwargs["metadata"] = self.metadata
|
||||
return kwargs
|
||||
|
||||
def _acquire_sandbox(self) -> tuple[Any, bool]:
|
||||
"""Return (sandbox, should_kill_after_use)."""
|
||||
sandbox_cls = self._import_sandbox_class()
|
||||
|
||||
if self.sandbox_id:
|
||||
return (
|
||||
sandbox_cls.connect(self.sandbox_id, **self._connect_kwargs()),
|
||||
False,
|
||||
)
|
||||
|
||||
if self.persistent:
|
||||
with self._lock:
|
||||
if self._persistent_sandbox is None:
|
||||
self._persistent_sandbox = sandbox_cls.create(
|
||||
**self._create_kwargs()
|
||||
)
|
||||
if not self._cleanup_registered:
|
||||
atexit.register(self.close)
|
||||
self._cleanup_registered = True
|
||||
return self._persistent_sandbox, False
|
||||
|
||||
sandbox = sandbox_cls.create(**self._create_kwargs())
|
||||
return sandbox, True
|
||||
|
||||
def _release_sandbox(self, sandbox: Any, should_kill: bool) -> None:
|
||||
if not should_kill:
|
||||
return
|
||||
try:
|
||||
sandbox.kill()
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"Best-effort sandbox cleanup failed after ephemeral use; "
|
||||
"the sandbox may need manual termination.",
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
def close(self) -> None:
|
||||
"""Kill the cached persistent sandbox if one exists."""
|
||||
with self._lock:
|
||||
sandbox = self._persistent_sandbox
|
||||
self._persistent_sandbox = None
|
||||
if sandbox is None:
|
||||
return
|
||||
try:
|
||||
sandbox.kill()
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"Best-effort persistent sandbox cleanup failed at close(); "
|
||||
"the sandbox may need manual termination.",
|
||||
exc_info=True,
|
||||
)
|
||||
@@ -0,0 +1,62 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from builtins import type as type_
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_base_tool import E2BBaseTool
|
||||
|
||||
|
||||
class E2BExecToolSchema(BaseModel):
|
||||
command: str = Field(..., description="Shell command to execute in the sandbox.")
|
||||
cwd: str | None = Field(
|
||||
default=None,
|
||||
description="Working directory to run the command in. Defaults to the sandbox home dir.",
|
||||
)
|
||||
envs: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
description="Optional environment variables to set for this command.",
|
||||
)
|
||||
timeout: float | None = Field(
|
||||
default=None,
|
||||
description="Maximum seconds to wait for the command to finish.",
|
||||
)
|
||||
|
||||
|
||||
class E2BExecTool(E2BBaseTool):
|
||||
"""Run a shell command inside an E2B sandbox."""
|
||||
|
||||
name: str = "E2B Sandbox Exec"
|
||||
description: str = (
|
||||
"Execute a shell command inside an E2B sandbox and return the exit "
|
||||
"code, stdout, and stderr. Use this to run builds, package installs, "
|
||||
"git operations, or any one-off shell command."
|
||||
)
|
||||
args_schema: type_[BaseModel] = E2BExecToolSchema
|
||||
|
||||
def _run(
|
||||
self,
|
||||
command: str,
|
||||
cwd: str | None = None,
|
||||
envs: dict[str, str] | None = None,
|
||||
timeout: float | None = None,
|
||||
) -> Any:
|
||||
sandbox, should_kill = self._acquire_sandbox()
|
||||
try:
|
||||
run_kwargs: dict[str, Any] = {}
|
||||
if cwd is not None:
|
||||
run_kwargs["cwd"] = cwd
|
||||
if envs is not None:
|
||||
run_kwargs["envs"] = envs
|
||||
if timeout is not None:
|
||||
run_kwargs["timeout"] = timeout
|
||||
result = sandbox.commands.run(command, **run_kwargs)
|
||||
return {
|
||||
"exit_code": getattr(result, "exit_code", None),
|
||||
"stdout": getattr(result, "stdout", None),
|
||||
"stderr": getattr(result, "stderr", None),
|
||||
"error": getattr(result, "error", None),
|
||||
}
|
||||
finally:
|
||||
self._release_sandbox(sandbox, should_kill)
|
||||
@@ -0,0 +1,220 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
from builtins import type as type_
|
||||
import logging
|
||||
import posixpath
|
||||
from typing import Any, Literal
|
||||
|
||||
from pydantic import BaseModel, Field, model_validator
|
||||
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_base_tool import E2BBaseTool
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
FileAction = Literal[
|
||||
"read", "write", "append", "list", "delete", "mkdir", "info", "exists"
|
||||
]
|
||||
|
||||
|
||||
class E2BFileToolSchema(BaseModel):
|
||||
action: FileAction = Field(
|
||||
...,
|
||||
description=(
|
||||
"The filesystem action to perform: 'read' (returns file contents), "
|
||||
"'write' (create or replace a file with content), 'append' (append "
|
||||
"content to an existing file — use this for writing large files in "
|
||||
"chunks to avoid hitting tool-call size limits), 'list' (lists a "
|
||||
"directory), 'delete' (removes a file/dir), 'mkdir' (creates a "
|
||||
"directory), 'info' (returns file metadata), 'exists' (returns a "
|
||||
"boolean for whether the path exists)."
|
||||
),
|
||||
)
|
||||
path: str = Field(..., description="Absolute path inside the sandbox.")
|
||||
content: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Content to write or append. If omitted for 'write', an empty file "
|
||||
"is created. For files larger than a few KB, prefer one 'write' "
|
||||
"with empty content followed by multiple 'append' calls of ~4KB "
|
||||
"each to stay within tool-call payload limits."
|
||||
),
|
||||
)
|
||||
binary: bool = Field(
|
||||
default=False,
|
||||
description=(
|
||||
"For 'write'/'append': treat content as base64 and upload raw "
|
||||
"bytes. For 'read': return contents as base64 instead of decoded "
|
||||
"utf-8."
|
||||
),
|
||||
)
|
||||
depth: int = Field(
|
||||
default=1,
|
||||
description="For action='list': how many levels deep to recurse (default 1).",
|
||||
)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_action_args(self) -> E2BFileToolSchema:
|
||||
if self.action == "append" and self.content is None:
|
||||
raise ValueError(
|
||||
"action='append' requires 'content'. Pass the chunk to append "
|
||||
"in the 'content' field."
|
||||
)
|
||||
return self
|
||||
|
||||
|
||||
class E2BFileTool(E2BBaseTool):
|
||||
"""Read, write, and manage files inside an E2B sandbox.
|
||||
|
||||
Notes:
|
||||
- Most useful with `persistent=True` or an explicit `sandbox_id`. With
|
||||
the default ephemeral mode, files disappear when this tool call
|
||||
finishes.
|
||||
"""
|
||||
|
||||
name: str = "E2B Sandbox Files"
|
||||
description: str = (
|
||||
"Perform filesystem operations inside an E2B sandbox: read a file, "
|
||||
"write content to a path, append content to an existing file, list a "
|
||||
"directory, delete a path, make a directory, fetch file metadata, or "
|
||||
"check whether a path exists. For files larger than a few KB, create "
|
||||
"the file with action='write' and empty content, then send the body "
|
||||
"via multiple 'append' calls of ~4KB each to stay within tool-call "
|
||||
"payload limits."
|
||||
)
|
||||
args_schema: type_[BaseModel] = E2BFileToolSchema
|
||||
|
||||
def _run(
|
||||
self,
|
||||
action: FileAction,
|
||||
path: str,
|
||||
content: str | None = None,
|
||||
binary: bool = False,
|
||||
depth: int = 1,
|
||||
) -> Any:
|
||||
sandbox, should_kill = self._acquire_sandbox()
|
||||
try:
|
||||
if action == "read":
|
||||
return self._read(sandbox, path, binary=binary)
|
||||
if action == "write":
|
||||
return self._write(sandbox, path, content or "", binary=binary)
|
||||
if action == "append":
|
||||
return self._append(sandbox, path, content or "", binary=binary)
|
||||
if action == "list":
|
||||
return self._list(sandbox, path, depth=depth)
|
||||
if action == "delete":
|
||||
sandbox.files.remove(path)
|
||||
return {"status": "deleted", "path": path}
|
||||
if action == "mkdir":
|
||||
created = sandbox.files.make_dir(path)
|
||||
return {"status": "created", "path": path, "created": bool(created)}
|
||||
if action == "info":
|
||||
return self._info(sandbox, path)
|
||||
if action == "exists":
|
||||
return {"path": path, "exists": bool(sandbox.files.exists(path))}
|
||||
raise ValueError(f"Unknown action: {action}")
|
||||
finally:
|
||||
self._release_sandbox(sandbox, should_kill)
|
||||
|
||||
def _read(self, sandbox: Any, path: str, *, binary: bool) -> dict[str, Any]:
|
||||
if binary:
|
||||
data: bytes = sandbox.files.read(path, format="bytes")
|
||||
return {
|
||||
"path": path,
|
||||
"encoding": "base64",
|
||||
"content": base64.b64encode(data).decode("ascii"),
|
||||
}
|
||||
try:
|
||||
content: str = sandbox.files.read(path)
|
||||
return {"path": path, "encoding": "utf-8", "content": content}
|
||||
except UnicodeDecodeError:
|
||||
data = sandbox.files.read(path, format="bytes")
|
||||
return {
|
||||
"path": path,
|
||||
"encoding": "base64",
|
||||
"content": base64.b64encode(data).decode("ascii"),
|
||||
"note": "File was not valid utf-8; returned as base64.",
|
||||
}
|
||||
|
||||
def _write(
|
||||
self, sandbox: Any, path: str, content: str, *, binary: bool
|
||||
) -> dict[str, Any]:
|
||||
payload: str | bytes = base64.b64decode(content) if binary else content
|
||||
self._ensure_parent_dir(sandbox, path)
|
||||
sandbox.files.write(path, payload)
|
||||
size = (
|
||||
len(payload)
|
||||
if isinstance(payload, (bytes, bytearray))
|
||||
else len(payload.encode("utf-8"))
|
||||
)
|
||||
return {"status": "written", "path": path, "bytes": size}
|
||||
|
||||
def _append(
|
||||
self, sandbox: Any, path: str, content: str, *, binary: bool
|
||||
) -> dict[str, Any]:
|
||||
chunk: bytes = base64.b64decode(content) if binary else content.encode("utf-8")
|
||||
self._ensure_parent_dir(sandbox, path)
|
||||
try:
|
||||
existing: bytes = sandbox.files.read(path, format="bytes")
|
||||
except Exception:
|
||||
existing = b""
|
||||
payload = existing + chunk
|
||||
sandbox.files.write(path, payload)
|
||||
return {
|
||||
"status": "appended",
|
||||
"path": path,
|
||||
"appended_bytes": len(chunk),
|
||||
"total_bytes": len(payload),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _ensure_parent_dir(sandbox: Any, path: str) -> None:
|
||||
parent = posixpath.dirname(path)
|
||||
if not parent or parent in ("/", "."):
|
||||
return
|
||||
try:
|
||||
sandbox.files.make_dir(parent)
|
||||
except Exception:
|
||||
logger.debug(
|
||||
"Best-effort parent-directory create failed for %s; "
|
||||
"assuming it already exists and proceeding with the write.",
|
||||
parent,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
def _list(self, sandbox: Any, path: str, *, depth: int) -> dict[str, Any]:
|
||||
entries = sandbox.files.list(path, depth=depth)
|
||||
return {
|
||||
"path": path,
|
||||
"entries": [self._entry_to_dict(e) for e in entries],
|
||||
}
|
||||
|
||||
def _info(self, sandbox: Any, path: str) -> dict[str, Any]:
|
||||
return self._entry_to_dict(sandbox.files.get_info(path))
|
||||
|
||||
@staticmethod
|
||||
def _entry_to_dict(entry: Any) -> dict[str, Any]:
|
||||
fields = (
|
||||
"name",
|
||||
"path",
|
||||
"type",
|
||||
"size",
|
||||
"mode",
|
||||
"permissions",
|
||||
"owner",
|
||||
"group",
|
||||
"modified_time",
|
||||
"symlink_target",
|
||||
)
|
||||
result: dict[str, Any] = {}
|
||||
for field in fields:
|
||||
value = getattr(entry, field, None)
|
||||
if value is not None and field == "modified_time":
|
||||
result[field] = (
|
||||
value.isoformat() if hasattr(value, "isoformat") else str(value)
|
||||
)
|
||||
else:
|
||||
result[field] = value
|
||||
return result
|
||||
@@ -0,0 +1,133 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from builtins import type as type_
|
||||
from typing import Any, ClassVar
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from crewai_tools.tools.e2b_sandbox_tool.e2b_base_tool import E2BBaseTool
|
||||
|
||||
|
||||
class E2BPythonToolSchema(BaseModel):
|
||||
code: str = Field(
|
||||
...,
|
||||
description="Python source to execute inside the sandbox.",
|
||||
)
|
||||
language: str | None = Field(
|
||||
default=None,
|
||||
description=(
|
||||
"Override the execution language (e.g. 'python', 'r', 'javascript'). "
|
||||
"Defaults to Python when omitted."
|
||||
),
|
||||
)
|
||||
envs: dict[str, str] | None = Field(
|
||||
default=None,
|
||||
description="Optional environment variables for the run.",
|
||||
)
|
||||
timeout: float | None = Field(
|
||||
default=None,
|
||||
description="Maximum seconds to wait for the code to finish.",
|
||||
)
|
||||
|
||||
|
||||
class E2BPythonTool(E2BBaseTool):
|
||||
"""Run Python code inside an E2B code interpreter sandbox.
|
||||
|
||||
Uses `e2b_code_interpreter`, which runs cells in a persistent Jupyter-style
|
||||
kernel so state (imports, variables) carries across calls when
|
||||
`persistent=True`.
|
||||
"""
|
||||
|
||||
name: str = "E2B Sandbox Python"
|
||||
description: str = (
|
||||
"Execute a block of Python code inside an E2B code interpreter sandbox "
|
||||
"and return captured stdout, stderr, the final expression value, and "
|
||||
"any rich results (charts, dataframes). Use this for data processing, "
|
||||
"quick scripts, or analysis that should run in an isolated environment."
|
||||
)
|
||||
args_schema: type_[BaseModel] = E2BPythonToolSchema
|
||||
|
||||
package_dependencies: list[str] = Field(
|
||||
default_factory=lambda: ["e2b_code_interpreter"],
|
||||
)
|
||||
|
||||
_ci_cache: ClassVar[dict[str, Any]] = {}
|
||||
|
||||
@classmethod
|
||||
def _import_sandbox_class(cls) -> Any:
|
||||
cached = cls._ci_cache.get("Sandbox")
|
||||
if cached is not None:
|
||||
return cached
|
||||
try:
|
||||
from e2b_code_interpreter import Sandbox # type: ignore[import-untyped]
|
||||
except ImportError as exc:
|
||||
raise ImportError(
|
||||
"The 'e2b_code_interpreter' package is required for the E2B "
|
||||
"Python tool. Install it with: "
|
||||
"uv add e2b-code-interpreter (or) "
|
||||
"pip install e2b-code-interpreter"
|
||||
) from exc
|
||||
cls._ci_cache["Sandbox"] = Sandbox
|
||||
return Sandbox
|
||||
|
||||
def _run(
|
||||
self,
|
||||
code: str,
|
||||
language: str | None = None,
|
||||
envs: dict[str, str] | None = None,
|
||||
timeout: float | None = None,
|
||||
) -> Any:
|
||||
sandbox, should_kill = self._acquire_sandbox()
|
||||
try:
|
||||
run_kwargs: dict[str, Any] = {}
|
||||
if language is not None:
|
||||
run_kwargs["language"] = language
|
||||
if envs is not None:
|
||||
run_kwargs["envs"] = envs
|
||||
if timeout is not None:
|
||||
run_kwargs["timeout"] = timeout
|
||||
execution = sandbox.run_code(code, **run_kwargs)
|
||||
return self._serialize_execution(execution)
|
||||
finally:
|
||||
self._release_sandbox(sandbox, should_kill)
|
||||
|
||||
@staticmethod
|
||||
def _serialize_execution(execution: Any) -> dict[str, Any]:
|
||||
logs = getattr(execution, "logs", None)
|
||||
error = getattr(execution, "error", None)
|
||||
results = getattr(execution, "results", None) or []
|
||||
return {
|
||||
"text": getattr(execution, "text", None),
|
||||
"stdout": list(getattr(logs, "stdout", []) or []) if logs else [],
|
||||
"stderr": list(getattr(logs, "stderr", []) or []) if logs else [],
|
||||
"error": (
|
||||
{
|
||||
"name": getattr(error, "name", None),
|
||||
"value": getattr(error, "value", None),
|
||||
"traceback": getattr(error, "traceback", None),
|
||||
}
|
||||
if error
|
||||
else None
|
||||
),
|
||||
"results": [E2BPythonTool._serialize_result(r) for r in results],
|
||||
"execution_count": getattr(execution, "execution_count", None),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _serialize_result(result: Any) -> dict[str, Any]:
|
||||
fields = (
|
||||
"text",
|
||||
"html",
|
||||
"markdown",
|
||||
"svg",
|
||||
"png",
|
||||
"jpeg",
|
||||
"pdf",
|
||||
"latex",
|
||||
"json",
|
||||
"javascript",
|
||||
"data",
|
||||
"is_main_result",
|
||||
"extra",
|
||||
)
|
||||
return {field: getattr(result, field, None) for field in fields}
|
||||
@@ -8734,6 +8734,668 @@
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Execute a shell command inside an E2B sandbox and return the exit code, stdout, and stderr. Use this to run builds, package installs, git operations, or any one-off shell command.",
|
||||
"env_vars": [
|
||||
{
|
||||
"default": null,
|
||||
"description": "API key for E2B sandbox service",
|
||||
"name": "E2B_API_KEY",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"description": "E2B API domain (optional)",
|
||||
"name": "E2B_DOMAIN",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"humanized_name": "E2B Sandbox Exec",
|
||||
"init_params_schema": {
|
||||
"$defs": {
|
||||
"EnvVar": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Default"
|
||||
},
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"default": true,
|
||||
"title": "Required",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"title": "EnvVar",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Run a shell command inside an E2B sandbox.",
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "password",
|
||||
"type": "string",
|
||||
"writeOnly": true
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "E2B API key. Falls back to E2B_API_KEY env var.",
|
||||
"required": false,
|
||||
"title": "Api Key"
|
||||
},
|
||||
"domain": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "E2B API domain override. Falls back to E2B_DOMAIN env var.",
|
||||
"required": false,
|
||||
"title": "Domain"
|
||||
},
|
||||
"envs": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set inside the sandbox at create time.",
|
||||
"title": "Envs"
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Metadata key-value pairs to attach to the sandbox at create time.",
|
||||
"title": "Metadata"
|
||||
},
|
||||
"persistent": {
|
||||
"default": false,
|
||||
"description": "If True, reuse one sandbox across all calls to this tool instance and kill it at process exit. Default False creates and kills a fresh sandbox per call.",
|
||||
"title": "Persistent",
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Attach to an existing sandbox by id instead of creating a new one. The tool will never kill a sandbox it did not create.",
|
||||
"title": "Sandbox Id"
|
||||
},
|
||||
"sandbox_timeout": {
|
||||
"default": 300,
|
||||
"description": "Idle timeout in seconds after which E2B auto-kills the sandbox. Applied at create time and when attaching via sandbox_id.",
|
||||
"title": "Sandbox Timeout",
|
||||
"type": "integer"
|
||||
},
|
||||
"template": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Optional template/snapshot name or id to create the sandbox from. Defaults to E2B's base template when omitted.",
|
||||
"title": "Template"
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"title": "E2BExecTool",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "E2BExecTool",
|
||||
"package_dependencies": [
|
||||
"e2b"
|
||||
],
|
||||
"run_params_schema": {
|
||||
"properties": {
|
||||
"command": {
|
||||
"description": "Shell command to execute in the sandbox.",
|
||||
"title": "Command",
|
||||
"type": "string"
|
||||
},
|
||||
"cwd": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Working directory to run the command in. Defaults to the sandbox home dir.",
|
||||
"title": "Cwd"
|
||||
},
|
||||
"envs": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Optional environment variables to set for this command.",
|
||||
"title": "Envs"
|
||||
},
|
||||
"timeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Maximum seconds to wait for the command to finish.",
|
||||
"title": "Timeout"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"command"
|
||||
],
|
||||
"title": "E2BExecToolSchema",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Perform filesystem operations inside an E2B sandbox: read a file, write content to a path, append content to an existing file, list a directory, delete a path, make a directory, fetch file metadata, or check whether a path exists. For files larger than a few KB, create the file with action='write' and empty content, then send the body via multiple 'append' calls of ~4KB each to stay within tool-call payload limits.",
|
||||
"env_vars": [
|
||||
{
|
||||
"default": null,
|
||||
"description": "API key for E2B sandbox service",
|
||||
"name": "E2B_API_KEY",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"description": "E2B API domain (optional)",
|
||||
"name": "E2B_DOMAIN",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"humanized_name": "E2B Sandbox Files",
|
||||
"init_params_schema": {
|
||||
"$defs": {
|
||||
"EnvVar": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Default"
|
||||
},
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"default": true,
|
||||
"title": "Required",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"title": "EnvVar",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Read, write, and manage files inside an E2B sandbox.\n\nNotes:\n - Most useful with `persistent=True` or an explicit `sandbox_id`. With\n the default ephemeral mode, files disappear when this tool call\n finishes.",
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "password",
|
||||
"type": "string",
|
||||
"writeOnly": true
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "E2B API key. Falls back to E2B_API_KEY env var.",
|
||||
"required": false,
|
||||
"title": "Api Key"
|
||||
},
|
||||
"domain": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "E2B API domain override. Falls back to E2B_DOMAIN env var.",
|
||||
"required": false,
|
||||
"title": "Domain"
|
||||
},
|
||||
"envs": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set inside the sandbox at create time.",
|
||||
"title": "Envs"
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Metadata key-value pairs to attach to the sandbox at create time.",
|
||||
"title": "Metadata"
|
||||
},
|
||||
"persistent": {
|
||||
"default": false,
|
||||
"description": "If True, reuse one sandbox across all calls to this tool instance and kill it at process exit. Default False creates and kills a fresh sandbox per call.",
|
||||
"title": "Persistent",
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Attach to an existing sandbox by id instead of creating a new one. The tool will never kill a sandbox it did not create.",
|
||||
"title": "Sandbox Id"
|
||||
},
|
||||
"sandbox_timeout": {
|
||||
"default": 300,
|
||||
"description": "Idle timeout in seconds after which E2B auto-kills the sandbox. Applied at create time and when attaching via sandbox_id.",
|
||||
"title": "Sandbox Timeout",
|
||||
"type": "integer"
|
||||
},
|
||||
"template": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Optional template/snapshot name or id to create the sandbox from. Defaults to E2B's base template when omitted.",
|
||||
"title": "Template"
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"title": "E2BFileTool",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "E2BFileTool",
|
||||
"package_dependencies": [
|
||||
"e2b"
|
||||
],
|
||||
"run_params_schema": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"description": "The filesystem action to perform: 'read' (returns file contents), 'write' (create or replace a file with content), 'append' (append content to an existing file \u2014 use this for writing large files in chunks to avoid hitting tool-call size limits), 'list' (lists a directory), 'delete' (removes a file/dir), 'mkdir' (creates a directory), 'info' (returns file metadata), 'exists' (returns a boolean for whether the path exists).",
|
||||
"enum": [
|
||||
"read",
|
||||
"write",
|
||||
"append",
|
||||
"list",
|
||||
"delete",
|
||||
"mkdir",
|
||||
"info",
|
||||
"exists"
|
||||
],
|
||||
"title": "Action",
|
||||
"type": "string"
|
||||
},
|
||||
"binary": {
|
||||
"default": false,
|
||||
"description": "For 'write'/'append': treat content as base64 and upload raw bytes. For 'read': return contents as base64 instead of decoded utf-8.",
|
||||
"title": "Binary",
|
||||
"type": "boolean"
|
||||
},
|
||||
"content": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Content to write or append. If omitted for 'write', an empty file is created. For files larger than a few KB, prefer one 'write' with empty content followed by multiple 'append' calls of ~4KB each to stay within tool-call payload limits.",
|
||||
"title": "Content"
|
||||
},
|
||||
"depth": {
|
||||
"default": 1,
|
||||
"description": "For action='list': how many levels deep to recurse (default 1).",
|
||||
"title": "Depth",
|
||||
"type": "integer"
|
||||
},
|
||||
"path": {
|
||||
"description": "Absolute path inside the sandbox.",
|
||||
"title": "Path",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"path"
|
||||
],
|
||||
"title": "E2BFileToolSchema",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Execute a block of Python code inside an E2B code interpreter sandbox and return captured stdout, stderr, the final expression value, and any rich results (charts, dataframes). Use this for data processing, quick scripts, or analysis that should run in an isolated environment.",
|
||||
"env_vars": [
|
||||
{
|
||||
"default": null,
|
||||
"description": "API key for E2B sandbox service",
|
||||
"name": "E2B_API_KEY",
|
||||
"required": false
|
||||
},
|
||||
{
|
||||
"default": null,
|
||||
"description": "E2B API domain (optional)",
|
||||
"name": "E2B_DOMAIN",
|
||||
"required": false
|
||||
}
|
||||
],
|
||||
"humanized_name": "E2B Sandbox Python",
|
||||
"init_params_schema": {
|
||||
"$defs": {
|
||||
"EnvVar": {
|
||||
"properties": {
|
||||
"default": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"title": "Default"
|
||||
},
|
||||
"description": {
|
||||
"title": "Description",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"title": "Name",
|
||||
"type": "string"
|
||||
},
|
||||
"required": {
|
||||
"default": true,
|
||||
"title": "Required",
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"description"
|
||||
],
|
||||
"title": "EnvVar",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
"description": "Run Python code inside an E2B code interpreter sandbox.\n\nUses `e2b_code_interpreter`, which runs cells in a persistent Jupyter-style\nkernel so state (imports, variables) carries across calls when\n`persistent=True`.",
|
||||
"properties": {
|
||||
"api_key": {
|
||||
"anyOf": [
|
||||
{
|
||||
"format": "password",
|
||||
"type": "string",
|
||||
"writeOnly": true
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "E2B API key. Falls back to E2B_API_KEY env var.",
|
||||
"required": false,
|
||||
"title": "Api Key"
|
||||
},
|
||||
"domain": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"description": "E2B API domain override. Falls back to E2B_DOMAIN env var.",
|
||||
"required": false,
|
||||
"title": "Domain"
|
||||
},
|
||||
"envs": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Environment variables to set inside the sandbox at create time.",
|
||||
"title": "Envs"
|
||||
},
|
||||
"metadata": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Metadata key-value pairs to attach to the sandbox at create time.",
|
||||
"title": "Metadata"
|
||||
},
|
||||
"persistent": {
|
||||
"default": false,
|
||||
"description": "If True, reuse one sandbox across all calls to this tool instance and kill it at process exit. Default False creates and kills a fresh sandbox per call.",
|
||||
"title": "Persistent",
|
||||
"type": "boolean"
|
||||
},
|
||||
"sandbox_id": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Attach to an existing sandbox by id instead of creating a new one. The tool will never kill a sandbox it did not create.",
|
||||
"title": "Sandbox Id"
|
||||
},
|
||||
"sandbox_timeout": {
|
||||
"default": 300,
|
||||
"description": "Idle timeout in seconds after which E2B auto-kills the sandbox. Applied at create time and when attaching via sandbox_id.",
|
||||
"title": "Sandbox Timeout",
|
||||
"type": "integer"
|
||||
},
|
||||
"template": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Optional template/snapshot name or id to create the sandbox from. Defaults to E2B's base template when omitted.",
|
||||
"title": "Template"
|
||||
}
|
||||
},
|
||||
"required": [],
|
||||
"title": "E2BPythonTool",
|
||||
"type": "object"
|
||||
},
|
||||
"name": "E2BPythonTool",
|
||||
"package_dependencies": [
|
||||
"e2b_code_interpreter"
|
||||
],
|
||||
"run_params_schema": {
|
||||
"properties": {
|
||||
"code": {
|
||||
"description": "Python source to execute inside the sandbox.",
|
||||
"title": "Code",
|
||||
"type": "string"
|
||||
},
|
||||
"envs": {
|
||||
"anyOf": [
|
||||
{
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Optional environment variables for the run.",
|
||||
"title": "Envs"
|
||||
},
|
||||
"language": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Override the execution language (e.g. 'python', 'r', 'javascript'). Defaults to Python when omitted.",
|
||||
"title": "Language"
|
||||
},
|
||||
"timeout": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "null"
|
||||
}
|
||||
],
|
||||
"default": null,
|
||||
"description": "Maximum seconds to wait for the code to finish.",
|
||||
"title": "Timeout"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"code"
|
||||
],
|
||||
"title": "E2BPythonToolSchema",
|
||||
"type": "object"
|
||||
}
|
||||
},
|
||||
{
|
||||
"description": "Search the internet using Exa",
|
||||
"env_vars": [
|
||||
|
||||
75
uv.lock
generated
75
uv.lock
generated
@@ -13,7 +13,7 @@ resolution-markers = [
|
||||
]
|
||||
|
||||
[options]
|
||||
exclude-newer = "2026-04-22T16:00:00Z"
|
||||
exclude-newer = "2026-04-23T07:00:00Z"
|
||||
|
||||
[manifest]
|
||||
members = [
|
||||
@@ -700,6 +700,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/32/76/cab7af7f16c0b09347f2ebe7ffda7101132f786acb767666dce43055faab/botocore_stubs-1.42.41-py3-none-any.whl", hash = "sha256:9423110fb0e391834bd2ed44ae5f879d8cb370a444703d966d30842ce2bcb5f0", size = 66759, upload-time = "2026-02-03T20:46:13.02Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bracex"
|
||||
version = "2.6"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/63/9a/fec38644694abfaaeca2798b58e276a8e61de49e2e37494ace423395febc/bracex-2.6.tar.gz", hash = "sha256:98f1347cd77e22ee8d967a30ad4e310b233f7754dbf31ff3fceb76145ba47dc7", size = 26642, upload-time = "2025-06-22T19:12:31.254Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/9d/2a/9186535ce58db529927f6cf5990a849aa9e052eea3e2cfefe20b9e1802da/bracex-2.6-py3-none-any.whl", hash = "sha256:0b0049264e7340b3ec782b5cb99beb325f36c3782a32e36e876452fd49a09952", size = 11508, upload-time = "2025-06-22T19:12:29.781Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "browserbase"
|
||||
version = "1.8.0"
|
||||
@@ -1489,6 +1498,10 @@ databricks-sdk = [
|
||||
daytona = [
|
||||
{ name = "daytona" },
|
||||
]
|
||||
e2b = [
|
||||
{ name = "e2b" },
|
||||
{ name = "e2b-code-interpreter" },
|
||||
]
|
||||
exa-py = [
|
||||
{ name = "exa-py" },
|
||||
]
|
||||
@@ -1590,6 +1603,8 @@ requires-dist = [
|
||||
{ name = "cryptography", marker = "extra == 'snowflake'", specifier = ">=43.0.3" },
|
||||
{ name = "databricks-sdk", marker = "extra == 'databricks-sdk'", specifier = ">=0.46.0" },
|
||||
{ name = "daytona", marker = "extra == 'daytona'", specifier = "~=0.140.0" },
|
||||
{ name = "e2b", marker = "extra == 'e2b'", specifier = "~=2.20.0" },
|
||||
{ name = "e2b-code-interpreter", marker = "extra == 'e2b'", specifier = "~=2.6.0" },
|
||||
{ name = "exa-py", marker = "extra == 'exa-py'", specifier = ">=1.8.7" },
|
||||
{ name = "firecrawl-py", marker = "extra == 'firecrawl-py'", specifier = ">=1.8.0" },
|
||||
{ name = "gitpython", marker = "extra == 'github'", specifier = ">=3.1.41,<4" },
|
||||
@@ -1632,7 +1647,7 @@ requires-dist = [
|
||||
{ name = "weaviate-client", marker = "extra == 'weaviate-client'", specifier = ">=4.10.2" },
|
||||
{ name = "youtube-transcript-api", specifier = "~=1.2.2" },
|
||||
]
|
||||
provides-extras = ["apify", "beautifulsoup4", "bedrock", "browserbase", "composio-core", "contextual", "couchbase", "databricks-sdk", "daytona", "exa-py", "firecrawl-py", "github", "hyperbrowser", "linkup-sdk", "mcp", "mongodb", "multion", "mysql", "oxylabs", "patronus", "postgresql", "qdrant-client", "rag", "scrapegraph-py", "scrapfly-sdk", "selenium", "serpapi", "singlestore", "snowflake", "spider-client", "sqlalchemy", "stagehand", "tavily-python", "weaviate-client", "xml"]
|
||||
provides-extras = ["apify", "beautifulsoup4", "bedrock", "browserbase", "composio-core", "contextual", "couchbase", "databricks-sdk", "daytona", "e2b", "exa-py", "firecrawl-py", "github", "hyperbrowser", "linkup-sdk", "mcp", "mongodb", "multion", "mysql", "oxylabs", "patronus", "postgresql", "qdrant-client", "rag", "scrapegraph-py", "scrapfly-sdk", "selenium", "serpapi", "singlestore", "snowflake", "spider-client", "sqlalchemy", "stagehand", "tavily-python", "weaviate-client", "xml"]
|
||||
|
||||
[[package]]
|
||||
name = "cryptography"
|
||||
@@ -1975,6 +1990,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/ba/5a/18ad964b0086c6e62e2e7500f7edc89e3faa45033c71c1893d34eed2b2de/dnspython-2.8.0-py3-none-any.whl", hash = "sha256:01d9bbc4a2d76bf0db7c1f729812ded6d912bd318d3b1cf81d30c0f845dbf3af", size = 331094, upload-time = "2025-09-07T18:57:58.071Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dockerfile-parse"
|
||||
version = "2.0.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/92/df/929ee0b5d2c8bd8d713c45e71b94ab57c7e11e322130724d54f469b2cd48/dockerfile-parse-2.0.1.tar.gz", hash = "sha256:3184ccdc513221983e503ac00e1aa504a2aa8f84e5de673c46b0b6eee99ec7bc", size = 24556, upload-time = "2023-07-18T13:36:07.897Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/7a/6c/79cd5bc1b880d8c1a9a5550aa8dacd57353fa3bb2457227e1fb47383eb49/dockerfile_parse-2.0.1-py2.py3-none-any.whl", hash = "sha256:bdffd126d2eb26acf1066acb54cb2e336682e1d72b974a40894fac76a4df17f6", size = 14845, upload-time = "2023-07-18T13:36:06.052Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "docling"
|
||||
version = "2.84.0"
|
||||
@@ -2125,6 +2149,41 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b0/0d/9feae160378a3553fa9a339b0e9c1a048e147a4127210e286ef18b730f03/durationpy-0.10-py3-none-any.whl", hash = "sha256:3b41e1b601234296b4fb368338fdcd3e13e0b4fb5b67345948f4f2bf9868b286", size = 3922, upload-time = "2025-05-17T13:52:36.463Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e2b"
|
||||
version = "2.20.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "dockerfile-parse" },
|
||||
{ name = "httpcore" },
|
||||
{ name = "httpx" },
|
||||
{ name = "packaging" },
|
||||
{ name = "protobuf" },
|
||||
{ name = "python-dateutil" },
|
||||
{ name = "rich" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "wcmatch" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/8c/87/e9b3bd252a4fe2b3fd6967ff985c7a5a15a31b2d5b8c37e50afb18797b17/e2b-2.20.0.tar.gz", hash = "sha256:52b3a00ac7015bbdce84913b2a57664d2def33d5a4069e34fa2354de31759173", size = 156575, upload-time = "2026-04-02T19:20:32.375Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/c2/ce/e402e2ecebe40ed9af20cddb862386f2ce20336e35c0dea257812129020e/e2b-2.20.0-py3-none-any.whl", hash = "sha256:66f6edcf6b742ca180f3aadcff7966fda86d68430fa6b2becdfa0fcc72224988", size = 296483, upload-time = "2026-04-02T19:20:30.573Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "e2b-code-interpreter"
|
||||
version = "2.6.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "attrs" },
|
||||
{ name = "e2b" },
|
||||
{ name = "httpx" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/cf/dd/f90b56d1597abfcdabdc018ac184fa714066be93d24b97edc2bf0671d483/e2b_code_interpreter-2.6.0.tar.gz", hash = "sha256:67e66531e5cf65c9df6e82aa0bdb1e73223a1ab205f10d47c027eb2ea09b73f9", size = 10683, upload-time = "2026-03-23T17:01:07.327Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6b/79/f70d50604584df66064892f3fca7ab57b10ad40c826fd003be53a4cd5fa5/e2b_code_interpreter-2.6.0-py3-none-any.whl", hash = "sha256:a15f1d155566aef98cf2ccc0f8d9b07d15e07582d6cc8a128bc97de371bd617c", size = 13715, upload-time = "2026-03-23T17:01:06.111Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "effdet"
|
||||
version = "0.4.1"
|
||||
@@ -9407,6 +9466,18 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/6e/d4/ed38dd3b1767193de971e694aa544356e63353c33a85d948166b5ff58b9e/watchfiles-1.1.1-pp311-pypy311_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3e6f39af2eab0118338902798b5aa6664f46ff66bc0280de76fca67a7f262a49", size = 457546, upload-time = "2025-10-14T15:06:13.372Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcmatch"
|
||||
version = "10.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "bracex" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/79/3e/c0bdc27cf06f4e47680bd5803a07cb3dfd17de84cde92dd217dcb9e05253/wcmatch-10.1.tar.gz", hash = "sha256:f11f94208c8c8484a16f4f48638a85d771d9513f4ab3f37595978801cb9465af", size = 117421, upload-time = "2025-06-22T19:14:02.49Z" }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/eb/d8/0d1d2e9d3fabcf5d6840362adcf05f8cf3cd06a73358140c3a97189238ae/wcmatch-10.1-py3-none-any.whl", hash = "sha256:5848ace7dbb0476e5e55ab63c6bbd529745089343427caa5537f230cc01beb8a", size = 39854, upload-time = "2025-06-22T19:14:00.978Z" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wcwidth"
|
||||
version = "0.6.0"
|
||||
|
||||
Reference in New Issue
Block a user