langchain-e2b 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ """E2B sandbox integration for Deep Agents."""
2
+
3
+ from langchain_e2b.sandbox import E2BSandbox
4
+
5
+ __all__ = ["E2BSandbox"]
@@ -0,0 +1,5 @@
1
+ """Version information for `langchain-e2b`."""
2
+
3
+ # Keep the `x-release-please-version` annotation — release-please uses it to
4
+ # bump `__version__` in sync with `pyproject.toml` on every release PR.
5
+ __version__ = "0.0.1" # x-release-please-version
@@ -0,0 +1,180 @@
1
+ """E2B sandbox backend implementation."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import e2b
6
+ from deepagents.backends.protocol import (
7
+ ExecuteResponse,
8
+ FileDownloadResponse,
9
+ FileUploadResponse,
10
+ )
11
+ from deepagents.backends.sandbox import BaseSandbox
12
+
13
+ DEFAULT_WORKDIR = "/home/user"
14
+ TIMEOUT_EXIT_CODE = 124
15
+
16
+
17
+ def _combine_output(stdout: str | None, stderr: str | None) -> str:
18
+ output = stdout or ""
19
+ if stderr:
20
+ output += "\n" + stderr if output else stderr
21
+ return output
22
+
23
+
24
+ class E2BSandbox(BaseSandbox):
25
+ """Sandbox backend that operates on an existing E2B sandbox."""
26
+
27
+ def __init__(
28
+ self,
29
+ *,
30
+ sandbox: e2b.Sandbox,
31
+ workdir: str = DEFAULT_WORKDIR,
32
+ timeout: int = 30 * 60,
33
+ ) -> None:
34
+ """Create a backend wrapping an existing E2B sandbox.
35
+
36
+ Args:
37
+ sandbox: Existing E2B sandbox instance to wrap.
38
+ workdir: Working directory for command execution.
39
+ timeout: Default command timeout in seconds when `execute()` is
40
+ called without an explicit `timeout`.
41
+ """
42
+ self._sandbox = sandbox
43
+ self._workdir = workdir
44
+ self._default_timeout = timeout
45
+
46
+ @property
47
+ def id(self) -> str:
48
+ """Return the E2B sandbox id."""
49
+ return self._sandbox.sandbox_id
50
+
51
+ def execute(
52
+ self,
53
+ command: str,
54
+ *,
55
+ timeout: int | None = None,
56
+ ) -> ExecuteResponse:
57
+ """Execute a shell command inside the sandbox.
58
+
59
+ Args:
60
+ command: Shell command string to execute.
61
+ timeout: Maximum time in seconds to wait for this command.
62
+
63
+ If None, uses the backend's default timeout.
64
+
65
+ Returns:
66
+ ExecuteResponse containing output, exit code, and truncation flag.
67
+
68
+ Raises:
69
+ ValueError: If `timeout` is negative.
70
+ """
71
+ effective_timeout = timeout if timeout is not None else self._default_timeout
72
+ if effective_timeout < 0:
73
+ msg = f"timeout must be non-negative, got {effective_timeout}"
74
+ raise ValueError(msg)
75
+
76
+ try:
77
+ result = self._sandbox.commands.run(
78
+ command,
79
+ cwd=self._workdir,
80
+ timeout=effective_timeout,
81
+ )
82
+ except e2b.CommandExitException as exc:
83
+ return ExecuteResponse(
84
+ output=_combine_output(exc.stdout, exc.stderr),
85
+ exit_code=exc.exit_code,
86
+ truncated=False,
87
+ )
88
+ except e2b.TimeoutException:
89
+ return ExecuteResponse(
90
+ output=f"Command timed out after {effective_timeout} seconds",
91
+ exit_code=TIMEOUT_EXIT_CODE,
92
+ truncated=False,
93
+ )
94
+ except e2b.SandboxException as exc:
95
+ return ExecuteResponse(
96
+ output=f"Error executing command ({type(exc).__name__}): {exc}",
97
+ exit_code=1,
98
+ truncated=False,
99
+ )
100
+
101
+ return ExecuteResponse(
102
+ output=_combine_output(result.stdout, result.stderr),
103
+ exit_code=result.exit_code,
104
+ truncated=False,
105
+ )
106
+
107
+ def _read_file(self, path: str) -> FileDownloadResponse:
108
+ if not path.startswith("/"):
109
+ return FileDownloadResponse(path=path, content=None, error="invalid_path")
110
+
111
+ try:
112
+ info = self._sandbox.files.get_info(path)
113
+ if info.type == e2b.FileType.DIR:
114
+ return FileDownloadResponse(
115
+ path=path,
116
+ content=None,
117
+ error="is_directory",
118
+ )
119
+ content = bytes(self._sandbox.files.read(path, format="bytes"))
120
+ return FileDownloadResponse(path=path, content=content, error=None)
121
+ except e2b.FileNotFoundException:
122
+ return FileDownloadResponse(path=path, content=None, error="file_not_found")
123
+ except e2b.InvalidArgumentException:
124
+ return FileDownloadResponse(path=path, content=None, error="invalid_path")
125
+ except PermissionError:
126
+ return FileDownloadResponse(
127
+ path=path,
128
+ content=None,
129
+ error="permission_denied",
130
+ )
131
+
132
+ def _write_file(self, path: str, content: bytes) -> FileUploadResponse:
133
+ if not path.startswith("/"):
134
+ return FileUploadResponse(path=path, error="invalid_path")
135
+
136
+ error: str | None = None
137
+ try:
138
+ info = self._sandbox.files.get_info(path)
139
+ if info.type == e2b.FileType.DIR:
140
+ error = "is_directory"
141
+ except e2b.FileNotFoundException:
142
+ pass
143
+ except e2b.InvalidArgumentException:
144
+ error = "invalid_path"
145
+ except PermissionError:
146
+ error = "permission_denied"
147
+
148
+ if error is None:
149
+ try:
150
+ self._sandbox.files.write(path, content)
151
+ except e2b.FileNotFoundException:
152
+ error = "file_not_found"
153
+ except e2b.InvalidArgumentException:
154
+ error = "invalid_path"
155
+ except PermissionError:
156
+ error = "permission_denied"
157
+
158
+ return FileUploadResponse(path=path, error=error)
159
+
160
+ def download_files(self, paths: list[str]) -> list[FileDownloadResponse]:
161
+ """Download files from the sandbox.
162
+
163
+ Args:
164
+ paths: Absolute sandbox file paths to download.
165
+
166
+ Returns:
167
+ Download responses in the same order as `paths`.
168
+ """
169
+ return [self._read_file(path) for path in paths]
170
+
171
+ def upload_files(self, files: list[tuple[str, bytes]]) -> list[FileUploadResponse]:
172
+ """Upload files into the sandbox.
173
+
174
+ Args:
175
+ files: `(path, content)` pairs to write.
176
+
177
+ Returns:
178
+ Upload responses in the same order as `files`.
179
+ """
180
+ return [self._write_file(path, content) for path, content in files]
@@ -0,0 +1,80 @@
1
+ Metadata-Version: 2.4
2
+ Name: langchain-e2b
3
+ Version: 0.0.1
4
+ Summary: E2B sandbox integration for Deep Agents
5
+ Project-URL: Homepage, https://github.com/e2b-dev/langchain-e2b
6
+ Project-URL: Repository, https://github.com/e2b-dev/langchain-e2b
7
+ Project-URL: Documentation, https://github.com/e2b-dev/langchain-e2b#readme
8
+ Project-URL: Issues, https://github.com/e2b-dev/langchain-e2b/issues
9
+ License: MIT
10
+ License-File: LICENSE
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.11
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Programming Language :: Python :: 3.13
17
+ Classifier: Programming Language :: Python :: 3.14
18
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
19
+ Requires-Python: <4.0,>=3.11
20
+ Requires-Dist: deepagents<0.7.0,>=0.6.0
21
+ Requires-Dist: e2b<3.0.0,>=2.25.1
22
+ Description-Content-Type: text/markdown
23
+
24
+ # langchain-e2b
25
+
26
+ [![PyPI - Version](https://img.shields.io/pypi/v/langchain-e2b?label=%20)](https://pypi.org/project/langchain-e2b/#history)
27
+ [![PyPI - License](https://img.shields.io/pypi/l/langchain-e2b)](https://opensource.org/licenses/MIT)
28
+ [![PyPI - Downloads](https://img.shields.io/pepy/dt/langchain-e2b)](https://pypistats.org/packages/langchain-e2b)
29
+
30
+ ## Quick Install
31
+
32
+ After the package is published:
33
+
34
+ ```bash
35
+ pip install langchain-e2b
36
+ ```
37
+
38
+ ```python
39
+ from e2b import Sandbox
40
+
41
+ from langchain_e2b import E2BSandbox
42
+
43
+ sandbox = Sandbox.create()
44
+ backend = E2BSandbox(sandbox=sandbox)
45
+
46
+ try:
47
+ result = backend.execute("echo hello")
48
+ print(result.output)
49
+ finally:
50
+ sandbox.kill()
51
+ ```
52
+
53
+ ## What is this?
54
+
55
+ `langchain-e2b` adapts an existing E2B sandbox to the Deep Agents sandbox
56
+ protocol. It uses the low-level `e2b` SDK so Deep Agents can run shell commands
57
+ and move files through the standard Deep Agents backend interface.
58
+
59
+ This package intentionally does not hide E2B sandbox lifecycle management. Use
60
+ the E2B SDK to create, connect to, configure, and kill sandboxes, then pass the
61
+ connected sandbox object to `E2BSandbox`.
62
+
63
+ ## Contributing
64
+
65
+ Contributions are welcome. Keep the adapter focused on implementing the Deep
66
+ Agents sandbox backend protocol over the official E2B SDK.
67
+
68
+ ## Development
69
+
70
+ ```bash
71
+ uv sync --group test
72
+ make test
73
+ make lint
74
+ ```
75
+
76
+ Integration tests require `E2B_API_KEY`:
77
+
78
+ ```bash
79
+ E2B_API_KEY=... make integration_tests
80
+ ```
@@ -0,0 +1,7 @@
1
+ langchain_e2b/__init__.py,sha256=aBCl_SstOPDdsgMiJc7Zakw6AFxPkdV03eRBQIsIP2U,119
2
+ langchain_e2b/_version.py,sha256=ppf0d_xCU41RiniqB_Ksx2oWJ-Bbiglg-75x4BdUPoY,249
3
+ langchain_e2b/sandbox.py,sha256=kQ2QAFyd9hnmmgxftPAqElUVujKJayil-It8IPXJoHg,5972
4
+ langchain_e2b-0.0.1.dist-info/METADATA,sha256=qcK_KLHiJBd3ABb0EPr95p848ud-v1olFYT1gW35or8,2430
5
+ langchain_e2b-0.0.1.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
6
+ langchain_e2b-0.0.1.dist-info/licenses/LICENSE,sha256=TsZ-TKbmch26hJssqCJhWXyGph7iFLvyFBYAa3stBHg,1067
7
+ langchain_e2b-0.0.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.30.1
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) LangChain, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.