@e2b/cli@2.15.1
993 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
4fcf7cb150 |
feat: sync API specs from infra and belt with Copybara (#1564)
The specs in `spec/` were copied from their source repos by hand and had
drifted ~2,400 lines behind infra, so they are now imported with
Copybara (`copy.bara.sky`, run in a pinned Docker image by
`scripts/fetch-spec.sh`): `make codegen` re-fetches them at the commits
pinned in `spec/infra-ref` and `spec/belt-ref` before generating, and
the generated-files CI check fails if the tracked copies don't match the
pins. Regenerating from the current pins picks up the accumulated spec
changes in the generated JS/Python clients (renamed request schemas,
`SandboxNetworkConfig`, `SandboxIam` workload identity,
`FILE_TYPE_SYMLINK`, access-token auth deprecation, volume path-metadata
tweaks). The one handwritten SDK change follows from that: the public
`FileType` enums gain a `SYMLINK` member (JS and both Python surfaces)
so entries envd reports as symlinks show up in `files.list()` and
`getInfo()`/`get_info()` instead of being silently skipped as unknown
types. The custom `spec/remove_extra_tags.py` tag-filtering script is
replaced by Redocly CLI's `filter-in` decorator (`redocly.yaml`), which
produces identical generated JS output; a `filter-out` decorator
additionally drops any operation or component schema the upstream specs
mark `x-not-implemented: true` (currently the SOCKS5
`SandboxEgressProxyConfig`/`egressProxy` surface, which infra flagged as
spec-only); each SDK's bundle now goes to its own gitignored
`spec/openapi_generated.<api>.yml` instead of both pipelines overwriting
one shared file; Python client models now list fields in spec order
instead of alphabetical (mechanical reordering only — construct models
with keyword args). Spec fetches try whatever GitHub token is available
and fall back to the tracked copies with a warning (the public infra
specs also fetch anonymously); in CI a short-lived belt-scoped token is
minted from the org-wide Autofixer GitHub App (no new secrets), so fork
PRs simply fall back for the belt spec; the CI workflows also cache the
Copybara image alongside the codegen image, and the previously ignored
`CODEGEN_IMAGE` env is honored by the Makefile.
## Usage
```sh
# update the specs: bump a pin, then regenerate
echo <infra-commit-sha> > spec/infra-ref
make codegen
# fetch a single spec without regenerating
pnpm fetch:api-spec # spec/openapi.yml from infra
pnpm fetch:envd-spec # spec/envd/ from infra
pnpm fetch:volume-spec # spec/openapi-volumecontent.yml from belt
# try the latest spec without touching the pin
E2B_INFRA_REF=main pnpm fetch:api-spec
# change which endpoint tags an SDK exposes
$EDITOR redocly.yaml && make codegen
```
```ts
// symlinks are now visible in the filesystem API (JS; same shape in Python)
const entries = await sandbox.files.list('/home/user')
const link = entries.find((e) => e.type === FileType.SYMLINK)
console.log(link?.symlinkTarget)
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
3f46d56026 |
fix(sdk): select stack-trace frames by SDK boundary instead of fixed depth (#1599)
## Description Template build stack traces were captured by walking a fixed number of frames (`STACK_TRACE_DEPTH` plus `±1` arithmetic at ~15 call sites), which broke whenever the frame count between `new Error()` and user code shifted — TS class-field initializer frames (#1539) and Bun's tail-call frame elision were both this bug. This PR makes two related changes: 1. **Boundary-based frame selection.** The caller's frame is now the first one whose file lies outside the SDK package, making extra transpiler frames and elided delegating frames irrelevant. In the JS SDK, frame parsing is delegated to `error-stack-parser-es` (ESM-only, so it's a devDependency inlined into both dist formats via tsdown `noExternal` — the engines range includes Node versions without `require(esm)`); the Python SDK equivalently walks `f_back` until `co_filename` leaves the `e2b` package root, in the shared builder used by both sync and async. If no user frame is identifiable (e.g. the SDK is bundled into the caller's own file), capture degrades to no trace rather than a wrong frame. 2. **Dead machinery removed.** Because boundary capture resolves through SDK-internal delegation (`remove()` → `runCmd()`, `fromDockerfile()` → parser) to the user's call site on its own, the suppress/override collection machinery (`runInNewStackTraceContext`, `runInStackTraceOverrideContext`, the enabled/override flags, and their Python equivalents) became redundant and is removed — superseding the approach in #1596. Error `.stack` synthesis (keeping the `Name: message` header and the throw site on `cause`) was prototyped here and backed out — it will come as a follow-up PR. ## Usage No API changes — build errors now point at the user's call site regardless of runtime or transpiler: ```ts const template = Template() .fromBaseImage() .runCmd('./does-not-exist') // ← build failures point exactly here await Template.build(template, 'my-template') ``` ## Testing - JS: `unit` + `template` vitest projects green against the real API (incl. 27 per-method stacktrace tests pinning exact call-site line/columns, `bunInstall` now covered); edge-compat bundle test and CLI build verified; built CJS/ESM dists smoke-tested with `require()`/`import()`. - Python: all 184 template tests green (shared + sync + async, incl. both `test_stacktrace.py` suites, `bun_install` now covered); `ruff` and `ty` clean. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
00253c39cc |
feat(python-sdk): migrate envd RPC to the official connectrpc client (#1558)
Replaces the vendored `e2b_connect` client and the custom Go `protoc-gen-connect-python` plugin with the official Connect RPC client for Python ([`connectrpc`](https://github.com/connectrpc/connect-py), transport: `pyqwest`/Rust hyper), and switches the envd messages from Google's `protobuf` runtime to Buf's [`protobuf-py`](https://github.com/bufbuild/protobuf-py) (which `connectrpc` already requires) — the SDK no longer depends on the conflict-prone `protobuf` package at all, and the protoc binary drops out of the codegen image. The wire format (same protos, same JSON) is unchanged. Closing a command or watch stream early now sends `RST_STREAM`, fixing abandoned streams leaking on the shared HTTP/2 connection, and peer resets surface as typed `ConnectError`s. The plumbing mirrors the `e2b.api` layout: shared pieces (a JSON codec that ignores unknown response fields, proxy narrowing, pool tuning) live in `e2b/envd/client_shared.py`, the flavor-specific pyqwest transports (wrapped in pyqwest's retry middleware, see the retry note below) and `create_rpc_client` factories in `e2b/envd/client_sync/` and `e2b/envd/client_async/`, and the default-header/logging interceptors in `e2b/envd/interceptors.py`; `e2b/envd/rpc.py` maps `connectrpc` error codes onto the existing SDK exceptions, so the public API is unchanged (`sandbox.commands.run(...)`, `files.watch_dir(...)`, etc. work exactly as before). The REST API and file upload/download keep using `httpx`. The `proxy` connection option now applies to sandbox RPC calls too — [pyqwest 0.7.0](https://github.com/curioswitch/pyqwest/releases/tag/v0.7.0) added an httpx-style `proxy` parameter to its transports, so commands, PTY, and filesystem watch traffic follow the same proxy as the REST API and file transfers (an earlier revision of this PR could only fall back to `http_proxy`/`https_proxy` env vars for RPC): ```python sandbox = Sandbox.create(proxy="http://user:pass@localhost:8030") # REST *and* RPC (commands, PTY, watch) traffic goes through the proxy result = sandbox.commands.run("echo through-the-proxy") ``` Notes: - `e2b_connect` is no longer shipped in the wheel; code importing it directly should switch to `connectrpc` (`ConnectError`, `Code`) — SDK exception types are unchanged. - The generated `e2b.envd.*.*_pb2` modules are replaced by `protobuf-py` equivalents (`process_pb`, `filesystem_pb`) with a different message API (`Oneof` objects, `has_field`); these are internal modules — `e2b-code-interpreter` and `e2b-desktop` were verified not to import them. - RPC transports are cached per proxy URL. `httpx.URL` and `httpx.Proxy` proxies keep working for RPC calls when they reduce to a proxy URL (`httpx.Proxy` auth is folded back into the URL userinfo); `httpx.Proxy` extras that pyqwest can't express — custom headers, an `ssl_context` — raise `InvalidArgumentException` rather than being silently dropped. - Plain (non-Connect-encoded) HTTP error responses — an edge proxy or gateway answering for envd — keep the vendored client's status mapping even when they carry a JSON body that isn't a valid Connect error (e.g. a gateway's `{"code": 429}` raises `RateLimitException`, not a misleading sandbox-timeout); only JSON bodies with a valid Connect `code` string are left to connectrpc to parse. An envd response that fails to decode surfaces as a `SandboxException` with a clear message — the SDK's JSON codec raises a typed `ConnectError(INTERNAL)` at the source (connectrpc re-raises codec-raised `ConnectError`s unchanged), rather than the error being reconstructed from `__cause__` heuristics in the exception mapper. - pyqwest 0.7.0 explicit transports default to an **empty TLS root store** (0.6.2 used reqwest's defaults), so the envd transports pass `tls_include_system_certs=True`; the dependency floor is `pyqwest>=0.7.0` accordingly. - Connection retries (`E2B_CONNECTION_RETRIES`, default 3) use pyqwest's transport-level retry middleware (`pyqwest.middleware.retry`), narrowed to retry only the builtin `ConnectionError` — raised solely while establishing the connection, before the request could have reached envd — with exponential backoff. A retry can therefore never replay a delivered request, for unary and streaming RPCs alike; the previous stack's replay of unary calls whose connection dropped mid-request is dropped deliberately, since it could re-execute a delivered call (e.g. `SendInput`). Pinned by unit tests plus end-to-end tests driving the generated stubs through the middleware (`tests/test_envd_retry_transport.py`). - For async streaming calls (`commands.run`/`connect`, PTY, `watch_dir`), `request_timeout` bounds opening the stream — the wait until envd confirms with a start event, matching the JS SDK's `requestTimeoutMs` — raising `TimeoutException` and cancelling the HTTP/2 stream when exceeded (pinned frame-level in `tests/test_envd_stream_reset.py`). The running stream stays bounded by the command/watch `timeout`. The sync SDK cannot interrupt its blocking wait, so `request_timeout` is not applied to sync stream setup — both setup and the running stream are bounded by `timeout` (unlimited when `0`). - The RPC logging interceptor was upstreamed to pyqwest as a logging middleware ([curioswitch/pyqwest#192](https://github.com/curioswitch/pyqwest/pull/192)); the SDK keeps its own `LoggingInterceptor` until that merges and ships in a release the SDK can depend on. - `pyqwest` ships binary wheels for manylinux/musllinux (x86_64, aarch64), macOS arm64 + x86_64 (Intel wheels landed in 0.7.0), Windows x64, and PyPy. - The `RST_STREAM`-on-early-close behavior is pinned by frame-level regression tests (`tests/test_envd_stream_reset.py`): a plaintext HTTP/2 server records the frames the real generated clients (with the SDK's codec and interceptors) send — early close via `disconnect()`, close through the logging interceptor, and abandoning the stream must all send `RST_STREAM(CANCEL)`; normal completion must send none (sync + async). - `E2B_MAX_CONNECTIONS` no longer applies to sandbox RPC traffic: reqwest's pool bounds only idle connections per host (`E2B_KEEPALIVE_EXPIRY`, `E2B_MAX_KEEPALIVE_CONNECTIONS`), not the total number of open connections. It still applies to the REST API and file transfers. - The sync sandbox modules build one RPC client each and share it across threads — the connectrpc sync client is stateless per call over the process-global transport (verified with a 16-thread frame-level test); only the httpx envd API clients stay per-thread with their transports. - Also fixes numeric env-var parsing (`E2B_KEEPALIVE_EXPIRY`, `E2B_MAX_KEEPALIVE_CONNECTIONS`, `E2B_MAX_CONNECTIONS`, `E2B_CONNECTION_RETRIES`): an empty-string value now falls back to the default instead of raising `ValueError` at import time. |
||
|
|
50de0af442 | [skip ci] Release new versions | ||
|
|
95e4dc2832 |
feat(sdk): add sandbox fork to JS and Python SDKs (#1554)
## Summary
Adds SDK support for the new `POST /sandboxes/{sandboxID}/fork` endpoint
(e2b-dev/infra#3202): checkpoint a running sandbox in place (briefly
paused, snapshotted with full memory state, and resumed — its ID and
expiration stay untouched) and boot `count` new sandboxes from that
snapshot.
- **spec**: adds `SandboxForkRequest` / `SandboxForkResult` schemas and
the `/sandboxes/{sandboxID}/fork` path (mirroring the infra spec); JS
and Python API clients regenerated via `make codegen`.
- **js-sdk**: `sandbox.fork(opts)` instance method and
`Sandbox.fork(sandboxId, opts)` static method. Returns
`Promise<Array<Sandbox | Error>>` — one entry per requested fork, each
either a connected `Sandbox` instance or an `Error` describing why that
fork failed to start (`Promise.allSettled`-style, matching the per-fork
results of the API). Per-fork error codes go through the same code→class
mapping as other API errors (extracted from `handleApiError` into
`apiErrorFromCode`), so e.g. a per-fork 429 (sandbox limit) surfaces as
`RateLimitError`. `SandboxForkOpts` extends the full `ConnectionOpts`
(like `SandboxConnectOpts`), so `proxy`, `logger`, `apiUrl`, etc. work
with fork-by-ID. `timeoutMs` defaults to 5 minutes like
`create`/`connect`; `count` defaults to 1 and is validated client-side
(`InvalidArgumentError` for `count < 1`); a whole-request 404 maps to
`SandboxNotFoundError` (the source sandbox is the missing resource —
same semantics as `pause`/`connect`/`setTimeout`), carrying the API
error message when present; per-fork 404 error codes map to generic
`NotFoundError` (the missing resource is fork-internal, e.g. the
snapshot).
- **python-sdk**: `sandbox.fork(timeout=..., count=...)` /
`Sandbox.fork(sandbox_id, ...)` and the `AsyncSandbox` equivalents (same
`@class_method_variant` instance/static pattern as `connect`/`pause`),
returning `List[Union[Sandbox, Exception]]`. Per-fork errors map through
the shared `api_exception_from_code` (extracted from
`handle_api_exception`). `timeout` is in seconds per Python SDK
convention; an explicit `timeout=0` is preserved. Whole-request 404
raises `SandboxNotFoundException`; per-fork 404 codes map to generic
`NotFoundException`.
- **changesets**: minor bumps for `e2b` and `@e2b/python-sdk`.
## Usage
JS:
```ts
const sandbox = await Sandbox.create()
const [fork1, fork2] = await sandbox.fork({ count: 2, timeoutMs: 60_000 })
if (fork1 instanceof Sandbox) {
await fork1.commands.run('echo "hello from fork"')
}
// or by ID
const forks = await Sandbox.fork(sandbox.sandboxId, { count: 2 })
```
Python (sync / async):
```python
sandbox = Sandbox.create()
fork1, fork2 = sandbox.fork(count=2, timeout=60)
if isinstance(fork1, Sandbox):
fork1.commands.run('echo "hello from fork"')
# or by ID
forks = Sandbox.fork(sandbox.sandbox_id, count=2)
```
```python
sandbox = await AsyncSandbox.create()
fork1, fork2 = await sandbox.fork(count=2)
```
## Notes
- The JS option is named `timeoutMs` (milliseconds) to match
`SandboxOpts.timeoutMs` / `SandboxConnectOpts.timeoutMs`; the API
receives seconds via `timeoutToSeconds` as elsewhere.
- Failed forks are returned as error **values** in the array rather than
rejected promises, so a partial failure doesn't throw away the
successful forks and there are no unhandled-rejection hazards. A
per-fork error message includes the API error code only when the API
returned one.
## Test plan
- [x] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck` pass at
the repo root (`ty` diagnostics identical to baseline)
- [x] Offline tests pass: `count < 1` → `InvalidArgumentError` /
`InvalidArgumentException` in JS, Python sync, and Python async;
`handleApiError` suite passes after the `apiErrorFromCode` extraction
(plus a behavior-parity check of the Python `handle_api_exception`
refactor)
- [ ] Integration tests (single fork with FS state inheritance +
independence, multi-fork with unique IDs, fork-by-ID, fork of killed
sandbox → `SandboxNotFoundError`) are written but currently fail against
prod with 404 because the fork endpoint (e2b-dev/infra#3202) is not
deployed yet — they should pass once it lands.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
8c87016a57 | [skip ci] Release new versions | ||
|
|
2c77fc00bb |
feat(sdk): add name filter to snapshot list (#1523)
Adds an optional `name` filter to `Sandbox.listSnapshots()` / `Sandbox.list_snapshots()`, mirroring the infra snapshots list endpoint ([e2b-dev/infra#3184](https://github.com/e2b-dev/infra/pull/3184)). The filter accepts a snapshot name or ID, optionally tag-qualified (e.g. `"my-snapshot"`, `"my-team/my-snapshot"` or `"my-snapshot:v1"`); unknown names return an empty list. It's a flat top-level option alongside the existing `sandboxId` filter (non-breaking) and can be combined with it — the backend applies both with AND, matching the `metadata`+`state` behavior of `Sandbox.list()`. Applied equivalently across the OpenAPI spec, generated clients, and the JS + Python sync/async SDKs, with tests and a changeset. ## Usage ```ts // JS/TS const paginator = Sandbox.listSnapshots({ name: 'my-snapshot' }) const snapshots = await paginator.nextItems() // combine filters (snapshots from a sandbox matching a name) Sandbox.listSnapshots({ sandboxId: 'sandbox-id', name: 'my-snapshot' }) ``` ```python # Python (sync) paginator = Sandbox.list_snapshots(name="my-snapshot") snapshots = paginator.next_items() # Python (async) paginator = AsyncSandbox.list_snapshots(name="my-snapshot") snapshots = await paginator.next_items() ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
78a91ab72f | [skip ci] Release new versions | ||
|
|
7474d904a2 |
fix(python-sdk): correct inverted no_install_recommends docstring (#1533)
Promotes the merged #1532 (by @anxkhn) from the staging branch `fix/no-install-recommends-docstring` into `main`. `TemplateBuilder.apt_install()` documents its `no_install_recommends` parameter as "Whether to install recommended packages", but the generated command does the opposite. In `packages/python-sdk/e2b/template/main.py` the command adds apt-get's `--no-install-recommends` flag when the argument is `True`: ```python f"... apt-get install -y {'--no-install-recommends ' if no_install_recommends else ''}..." ``` `--no-install-recommends` tells apt to *skip* recommended packages, so `no_install_recommends=True` skips them rather than installing them. A user who follows the docstring gets the inverse of the documented behavior. The parameter name and apt-get's own semantics confirm the code is correct and the docstring was wrong; this rewords the docstring line to match the real behavior. The `--no-install-recommends` flag was introduced in #983; the docstring has been inverted since then. This is Python-only. The JS twin `aptInstall` applies the same flag but has no per-parameter JSDoc for `noInstallRecommends` (it appears only inside an `@example`), so there is nothing contradictory to fix on the JS side. There is a single Python definition (no sync/async mirror for the template builder). No behavior change; documentation-only, plus a `@e2b/python-sdk: patch` changeset. ### Usage ```python from e2b import Template template = Template().from_image("ubuntu:22.04") # Install recommended packages as well (apt-get default): template.apt_install("vim") # Skip recommended packages (adds apt-get's --no-install-recommends): template.apt_install("vim", no_install_recommends=True) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Signed-off-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> Co-authored-by: Anas Khan <anxkhn28@gmail.com> |
||
|
|
99e536f6eb |
fix(python-sdk): stop leaking per-call proxy pools in volume content clients (#1534)
The Python volume content client factories passed both `proxy` and the shared cached `transport` to httpx, so with a proxy configured (e.g. `Volume.connect(volume_id, proxy="http://user:pass@127.0.0.1:8080")`), every volume operation mounted a fresh, never-closed proxy transport that bypassed the cached connection pool. The client-level `proxy` argument is now dropped — the proxy is already baked into the cached transport, so proxied requests keep working but reuse one pooled transport per thread/event loop. The volume transports also gained connect-level retries (`E2B_CONNECTION_RETRIES`, default 3), matching the core API and envd transports. Includes a changeset for a `@e2b/python-sdk` patch release. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
dbc6bfa161 | [skip ci] Release new versions | ||
|
|
09e12b3f65 |
feat(sdk): set-once integration attribution via ConnectionConfig.setIntegration (#1524)
Replaces the per-call `integration` connection option with a set-once, process-wide setter — `ConnectionConfig.setIntegration()` in JS and `ConnectionConfig.set_integration()` in Python — so integrations wrapping the SDK tag themselves once at startup and every request carries the identifier in the `User-Agent` header, with no threading through individual SDK calls. The setter is internal and hidden from generated docs; the `integration` option is removed from `ConnectionConfigOpts` (kept as a deprecated alias of `ConnectionOpts`) and from the Python constructor, and the round-trip machinery from #1459 is no longer needed since rebuilt configs read the process-wide value. User-Agent handling now follows a single rule in both SDKs via one shared helper per SDK: an explicitly provided `User-Agent` always wins, otherwise the SDK sends its own tagged with the current integration — and SDK-built values are recomputed whenever a config is rebuilt, so clearing or changing the integration propagates. Tests cover attribution, clearing, config rebuilds, and custom User-Agent precedence in both SDKs, with changesets for `e2b` and `@e2b/python-sdk` (minor). CLI attribution using this setter will follow in a separate PR. Usage (internal integrations only): ```ts import { ConnectionConfig } from 'e2b' ConnectionConfig.setIntegration('e2b-code-interpreter/0.1.0') // once at startup ``` ```python from e2b import ConnectionConfig ConnectionConfig.set_integration("e2b-code-interpreter/0.1.0") # once at startup ``` A caller-supplied `User-Agent` (via `headers`/`apiHeaders`) is preserved in both SDKs: ```ts const sbx = await Sandbox.create({ apiHeaders: { 'User-Agent': 'my-app/1.0' } }) // requests carry: my-app/1.0 ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
07041ccffc |
test: skip live volume tests unless ENABLE_VOLUME_TESTS is set (#1526)
Live volume tests create real volumes against the API; this gates them
behind an `ENABLE_VOLUME_TESTS` env var so they skip by default. In the
JS SDK, the `volumeTest` fixture is chained with
`.skipIf(process.env.ENABLE_VOLUME_TESTS === undefined)`, skipping all
of `tests/volume/file.test.ts`. In the Python SDK, the `volume` and
`async_volume` fixtures call `pytest.skip` when the env var is unset,
gating `tests/{sync/volume_sync,async/volume_async}/test_file.py`.
Mocked and unit volume tests (msw-based `volume.test.ts`,
`test_volume.py`, `test_volume_content.py`, `test_volume_client.py`,
`test_volume_connection_config.py`) still run unconditionally. To run
the live tests: `ENABLE_VOLUME_TESTS=1 pnpm run test` or
`ENABLE_VOLUME_TESTS=1 poetry run pytest`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0feb926937 | [skip ci] Release new versions | ||
|
|
5d84a8e7d2 |
chore(deps-dev): bump black from 23.7.0 to 26.3.1 in /packages/python-sdk in the uv group across 1 directory (#1530)
Bumps the uv group with 1 update in the /packages/python-sdk directory: [black](https://github.com/psf/black). Updates `black` from 23.7.0 to 26.3.1 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/psf/black/releases">black's releases</a>.</em></p> <blockquote> <h2>26.3.1</h2> <h3>Stable style</h3> <ul> <li>Prevent Jupyter notebook magic masking collisions from corrupting cells by using exact-length placeholders for short magics and aborting if a placeholder can no longer be unmasked safely (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3>Configuration</h3> <ul> <li>Always hash cache filename components derived from <code>--python-cell-magics</code> so custom magic names cannot affect cache paths (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3><em>Blackd</em></h3> <ul> <li>Disable browser-originated requests by default, add configurable origin allowlisting and request body limits, and bound executor submissions to improve backpressure (<a href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li> </ul> <h2>26.3.0</h2> <h3>Stable style</h3> <ul> <li>Don't double-decode input, causing non-UTF-8 files to be corrupted (<a href="https://redirect.github.com/psf/black/issues/4964">#4964</a>)</li> <li>Fix crash on standalone comment in lambda default arguments (<a href="https://redirect.github.com/psf/black/issues/4993">#4993</a>)</li> <li>Preserve parentheses when <code># type: ignore</code> comments would be merged with other comments on the same line, preventing AST equivalence failures (<a href="https://redirect.github.com/psf/black/issues/4888">#4888</a>)</li> </ul> <h3>Preview style</h3> <ul> <li>Fix bug where <code>if</code> guards in <code>case</code> blocks were incorrectly split when the pattern had a trailing comma (<a href="https://redirect.github.com/psf/black/issues/4884">#4884</a>)</li> <li>Fix <code>string_processing</code> crashing on unassigned long string literals with trailing commas (one-item tuples) (<a href="https://redirect.github.com/psf/black/issues/4929">#4929</a>)</li> <li>Simplify implementation of the power operator "hugging" logic (<a href="https://redirect.github.com/psf/black/issues/4918">#4918</a>)</li> </ul> <h3>Packaging</h3> <ul> <li>Fix shutdown errors in PyInstaller builds on macOS by disabling multiprocessing in frozen environments (<a href="https://redirect.github.com/psf/black/issues/4930">#4930</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Introduce winloop for windows as an alternative to uvloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Remove deprecated function <code>uvloop.install()</code> in favor of <code>uvloop.new_event_loop()</code> (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Rename <code>maybe_install_uvloop</code> function to <code>maybe_use_uvloop</code> to simplify loop installation and creation of either a uvloop/winloop evenloop or default eventloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> </ul> <h3>Output</h3> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Changelog</summary> <p><em>Sourced from <a href="https://github.com/psf/black/blob/main/CHANGES.md">black's changelog</a>.</em></p> <blockquote> <h2>Version 26.3.1</h2> <h3>Stable style</h3> <ul> <li>Prevent Jupyter notebook magic masking collisions from corrupting cells by using exact-length placeholders for short magics and aborting if a placeholder can no longer be unmasked safely (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3>Configuration</h3> <ul> <li>Always hash cache filename components derived from <code>--python-cell-magics</code> so custom magic names cannot affect cache paths (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> </ul> <h3><em>Blackd</em></h3> <ul> <li>Disable browser-originated requests by default, add configurable origin allowlisting and request body limits, and bound executor submissions to improve backpressure (<a href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li> </ul> <h2>Version 26.3.0</h2> <h3>Stable style</h3> <ul> <li>Don't double-decode input, causing non-UTF-8 files to be corrupted (<a href="https://redirect.github.com/psf/black/issues/4964">#4964</a>)</li> <li>Fix crash on standalone comment in lambda default arguments (<a href="https://redirect.github.com/psf/black/issues/4993">#4993</a>)</li> <li>Preserve parentheses when <code># type: ignore</code> comments would be merged with other comments on the same line, preventing AST equivalence failures (<a href="https://redirect.github.com/psf/black/issues/4888">#4888</a>)</li> </ul> <h3>Preview style</h3> <ul> <li>Fix bug where <code>if</code> guards in <code>case</code> blocks were incorrectly split when the pattern had a trailing comma (<a href="https://redirect.github.com/psf/black/issues/4884">#4884</a>)</li> <li>Fix <code>string_processing</code> crashing on unassigned long string literals with trailing commas (one-item tuples) (<a href="https://redirect.github.com/psf/black/issues/4929">#4929</a>)</li> <li>Simplify implementation of the power operator "hugging" logic (<a href="https://redirect.github.com/psf/black/issues/4918">#4918</a>)</li> </ul> <h3>Packaging</h3> <ul> <li>Fix shutdown errors in PyInstaller builds on macOS by disabling multiprocessing in frozen environments (<a href="https://redirect.github.com/psf/black/issues/4930">#4930</a>)</li> </ul> <h3>Performance</h3> <ul> <li>Introduce winloop for windows as an alternative to uvloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Remove deprecated function <code>uvloop.install()</code> in favor of <code>uvloop.new_event_loop()</code> (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> <li>Rename <code>maybe_install_uvloop</code> function to <code>maybe_use_uvloop</code> to simplify loop installation and creation of either a uvloop/winloop eventloop or default eventloop (<a href="https://redirect.github.com/psf/black/issues/4996">#4996</a>)</li> </ul> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/psf/black/commit/c6755bb741b6481d6b3d3bb563c83fa060db96c9"><code>c6755bb</code></a> Prepare release 26.3.1 (<a href="https://redirect.github.com/psf/black/issues/5046">#5046</a>)</li> <li><a href="https://github.com/psf/black/commit/69973fd6950985fbeb1090d96da717dc4d8380b0"><code>69973fd</code></a> Harden blackd browser-facing request handling (<a href="https://redirect.github.com/psf/black/issues/5039">#5039</a>)</li> <li><a href="https://github.com/psf/black/commit/4937fe6cf241139ddbfc16b0bdbb5b422798909d"><code>4937fe6</code></a> Fix some shenanigans with the cache file and IPython (<a href="https://redirect.github.com/psf/black/issues/5038">#5038</a>)</li> <li><a href="https://github.com/psf/black/commit/2e641d174469c505d5ae905e75d4c769597e681f"><code>2e641d1</code></a> docs: remove outdated Black Playground references (<a href="https://redirect.github.com/psf/black/issues/5044">#5044</a>)</li> <li><a href="https://github.com/psf/black/commit/c014b22a2d5e0632587b47b81151658bddfa0b88"><code>c014b22</code></a> Remove unused internal code (<a href="https://redirect.github.com/psf/black/issues/5041">#5041</a>)</li> <li><a href="https://github.com/psf/black/commit/0dae20b2d009f2f03de8696d06b0c947d3abafc9"><code>0dae20b</code></a> Add new changelog (<a href="https://redirect.github.com/psf/black/issues/5036">#5036</a>)</li> <li><a href="https://github.com/psf/black/commit/c5c1cbddd92cecb554ac2a77a24139dd76831030"><code>c5c1cbd</code></a> Minor release patches (<a href="https://redirect.github.com/psf/black/issues/5035">#5035</a>)</li> <li><a href="https://github.com/psf/black/commit/7e5a828c37d71b6a6666e28eed444816def6a8f4"><code>7e5a828</code></a> docs: clarify relationship between Black style and PEP 8 (<a href="https://redirect.github.com/psf/black/issues/5025">#5025</a>)</li> <li><a href="https://github.com/psf/black/commit/69705deb8776e7c5e585668da106d1abe2cb8d77"><code>69705de</code></a> docs: add clearer pyproject configuration guidance (<a href="https://redirect.github.com/psf/black/issues/5026">#5026</a>)</li> <li><a href="https://github.com/psf/black/commit/35ea67920b7f6ac8e09be1c47278752b1e827f76"><code>35ea679</code></a> Prepare release 26.3.0 (<a href="https://redirect.github.com/psf/black/issues/5032">#5032</a>)</li> <li>Additional commits viewable in <a href="https://github.com/psf/black/compare/23.7.0...26.3.1">compare view</a></li> </ul> </details> <br /> [](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores) Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`. [//]: # (dependabot-automerge-start) [//]: # (dependabot-automerge-end) --- <details> <summary>Dependabot commands and options</summary> <br /> You can trigger Dependabot actions by commenting on this PR: - `@dependabot rebase` will rebase this PR - `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it - `@dependabot show <dependency name> ignore conditions` will show all of the ignore conditions of the specified dependency - `@dependabot ignore <dependency name> major version` will close this group update PR and stop Dependabot creating any more for the specific dependency's major version (unless you unignore this specific dependency's major version or upgrade to it yourself) - `@dependabot ignore <dependency name> minor version` will close this group update PR and stop Dependabot creating any more for the specific dependency's minor version (unless you unignore this specific dependency's minor version or upgrade to it yourself) - `@dependabot ignore <dependency name>` will close this group update PR and stop Dependabot creating any more for the specific dependency (unless you unignore this specific dependency or upgrade to it yourself) - `@dependabot unignore <dependency name>` will remove all of the ignore conditions of the specified dependency - `@dependabot unignore <dependency name> <ignore condition>` will remove the ignore condition of the specified dependency and ignore conditions You can disable automated security fix PRs for this repo from the [Security Alerts page](https://github.com/e2b-dev/E2B/network/alerts). </details> --------- Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Mish Ushakov <10400064+mishushakov@users.noreply.github.com> Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
be4eb5fd96 |
chore(python-sdk): migrate from Poetry to uv (#1513)
Migrates the Python SDK's packaging and CI from Poetry to [uv](https://docs.astral.sh/uv/): `pyproject.toml` is converted to PEP 621 metadata using uv's native `uv_build` backend (verified to produce a byte-equivalent wheel containing both `e2b` and `e2b_connect`), `poetry.lock` is replaced with `uv.lock`, and the `Makefile`, `package.json` scripts, `.tool-versions`, `CLAUDE.md`, and all six GitHub workflows now use `uv` (`astral-sh/setup-uv` + `uv sync`/`build`/`version`/`publish`). It also drops the now-redundant explicit sync steps (since `uv run` auto-syncs) and removes the orphaned `pydoc-markdown` dev dependency, whose only consumer was deleted long ago — trimming 58 packages from the dev lockfile. ## Usage ```sh cd packages/python-sdk uv sync # install deps (replaces `poetry install`) uv run pytest # run tests uv build # build the wheel/sdist make lint # ruff (run via `uv run`) ``` No user-facing SDK change — packaging/tooling only — so no changeset is included; the published package contents are unchanged. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
a6b1cf4bcf |
fix(python-sdk): strip colon-separated SGR escape codes in build logs (#1522)
### What Cherry-picks the fix from #1519. `strip_ansi_escape_codes` in the Python SDK only matched semicolon-separated CSI parameters, so colon-separated SGR sequences leaked literal escape garbage into template build-log messages. This widens the parameter class from `;` to `[;:]` so colon-separated sequences are stripped too, matching the JS SDK's `stripAnsi`. Modern terminals emit colon-separated SGR sequences: - 256-color: `\x1b[38:5:82m` - truecolor: `\x1b[38:2::255:0:0m` - curly underline: `\x1b[4:3m` The two SDKs share one source (chalk/ansi-regex) and the JS twin was already updated to support colons (`packages/js-sdk/src/utils.ts:95`, comment: "supports ; and :"); the Python port lagged behind. `strip_ansi_escape_codes` is consumed by `LogEntry.__post_init__` (`packages/python-sdk/e2b/template/logger.py`), so the leftover escape bytes showed up in Python build logs only. ### The one-line fix ```python # packages/python-sdk/e2b/template/utils.py:319 - r"(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))", + r"(?:(?:\d{1,4}(?:[;:]\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))", ``` ### Usage example (before / after) ```python from e2b.template.utils import strip_ansi_escape_codes # 256-color, colon-separated strip_ansi_escape_codes("\x1b[38:5:82mX\x1b[0m") # before: ":5:82mX" after: "X" # truecolor, colon-separated strip_ansi_escape_codes("\x1b[38:2::255:0:0mRED\x1b[0m") # before: ":2::255:0:0mRED" after: "RED" # semicolon variants already worked and still do strip_ansi_escape_codes("\x1b[38;5;82mX\x1b[0m") # "X" (unchanged) ``` ### Tests Unit tests at `packages/python-sdk/tests/shared/template/utils/test_strip_ansi_escape_codes.py` (no API key / sandbox): colon-256, colon-truecolor, curly-underline, plus basic/semicolon regressions. All 7 pass locally. ### Changeset `.changeset/python-strip-ansi-colon.md` (patch on `@e2b/python-sdk`). ### Notes Original PR: #1519 (by @anxkhn). Opened against a fresh branch off `main` per request, rather than merging #1519 directly. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Anas Khan <83116240+anxkhn@users.noreply.github.com> |
||
|
|
2b7dd17f10 |
feat(sdk): add gzip option to template copy layer (#1482)
Adds a `gzip` option to the template `.copy()` / `copyItems` layer that
controls whether copied files are gzipped before upload, threaded from
the copy call through the build-time tar stream in both the JS SDK and
the sync/async Python SDKs. It is enabled by default to preserve
existing behavior, so passing `gzip: false` (`gzip=False`) uploads an
uncompressed tar — useful for already-compressed payloads where gzip
adds CPU cost without shrinking the upload. The option name matches
node-tar's own `gzip` option and the existing sandbox filesystem `gzip`
kwarg. Gzip is deliberately excluded from the file cache hash, so
toggling it does not bust the build cache. Tests in both SDKs were
updated for the new argument and extended with `gzip: false` cases
asserting the archive is not gzipped yet still extracts, and a changeset
(`minor` for both packages) is included.
> [!NOTE]
> The server that extracts these uploaded archives lives in another repo
and must auto-detect compression (peek the gzip `0x1f 0x8b` magic)
rather than assuming gzip; confirm it handles plain tars before release.
## Usage
```ts
// JS/TS
template.copy('model.bin', '/app/', { gzip: false })
template.copyItems([{ src: 'a.bin', dest: '/app/', gzip: false }])
```
```python
# Python (sync & async)
template.copy('model.bin', '/app/', gzip=False)
template.copy_items([{ 'src': 'a.bin', 'dest': '/app/', 'gzip': False }])
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
c385566c29 |
fix(python-sdk): correct Sandbox.list() docstring (also lists paused) (#1511)
Integration branch PR for #1500. Merges the docstring fix into `main`. Once #1500 is merged into `python-sdk-list-docstring-base`, this PR will carry those changes into `main`. --------- Co-authored-by: Leinux <tristone13th@outlook.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
f160f08c7b |
Keep integration attribution on connection config (#1459)
moves integration attirbution to more private thing to avoid confusing people with first class kwargs |
||
|
|
bb45f185f1 |
Introduce generic paginator base class for JS and Python SDKs (#1491)
Extracts the cursor-based pagination state machine into a reusable base
class — `Paginator` in the JS SDK's `utils`, `PaginatorBase` in
`e2b/utils.py` — that owns `hasNext`/`nextToken` and the `x-next-token`
header handling, and migrates the sandbox and snapshot paginators onto
it. Each concrete paginator now just implements `nextItems`/`next_items`
to fetch its own page, so future list endpoints (templates, builds,
etc.) can add pagination by subclassing without reimplementing the
bookkeeping. Applied equivalently to the JS SDK and both Python sync and
async implementations, with unit tests covering the shared base. There
are no public API changes — `Sandbox.list()` / `listSnapshots()` and the
existing paginator types behave identically.
## Usage (unchanged)
```ts
const paginator = Sandbox.list()
while (paginator.hasNext) {
const sandboxes = await paginator.nextItems()
console.log(sandboxes)
}
```
```python
paginator = Sandbox.list()
while paginator.has_next:
sandboxes = paginator.next_items()
print(sandboxes)
```
|
||
|
|
bb1696871b |
Stream template build-context upload from disk instead of buffering in memory (#1435)
## Summary Template builds previously buffered the entire gzipped build-context tar archive in memory before uploading it. This PR spools the archive to a temporary file and streams it from disk during upload — in the JS SDK and both sync and async Python SDKs — so memory usage no longer scales with the size of the build context. The upload keeps an explicit `Content-Length` header (taken from the spooled file's size), which S3 presigned PUT URLs require — they reject `Transfer-Encoding: chunked` with `501 NotImplemented` (#1243). ## Changes - **JS** (`packages/js-sdk/src/template/`): `tarFileStream`/`tarFileStreamUpload` are replaced by `tarFileToStream`, which writes the archive to a temp file and returns a self-cleaning read stream plus its `size`. The spooled temp file deletes itself once the stream is closed (consumed, errored, or destroyed) via the stream's `close` event — mirroring the Python SDK's `tar_file_stream`. `buildApi` streams this body with `duplex: 'half'` and an explicit `Content-Length` from `size`; if `fetch` throws before consuming the body, it destroys the stream to trigger the same cleanup. There is no separate cleanup callback, so a cleanup failure can no longer mask the upload result. - **Python** (`packages/python-sdk/e2b/template/utils.py`, `template_async/build_api.py`, `template_sync/build_api.py`): `tar_file_stream` now writes to a `tempfile.TemporaryFile` instead of `io.BytesIO` and returns the file object positioned at the start; the upload streams from it with an explicit `Content-Length` and closes it (deleting the temp file) when done. - Tests updated for the new return shapes (JS `tarFileToStream.test.ts`, `uploadFile.test.ts`; Python upload/tar tests), including assertions that the spooled archive is removed on both the consume and destroy paths. ## Usage No API changes — `Template.build()` / template builds behave the same, just without holding the build context in memory: ```ts await Template.build(template, { alias: 'my-template' }) ``` ```python Template.build(template, alias="my-template") ``` Split out of #1433, which covers streaming for sandbox/volume file uploads and downloads. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
8b8a224f8b |
feat(python-sdk): add logger option for request/debug logging (#1409)
Adds a `logger` option (a standard library `logging.Logger`) to
`Sandbox.create`/`AsyncSandbox.create` and the static
`Sandbox.connect(sandbox_id, ...)`, wired into the API client, the envd
client, the volume content client, and the RPC (ConnectRPC) path. The
logger is stored on the sandbox and propagates to all of its later
operations — including control-plane calls like
`kill`/`pause`/`set_timeout`/`get_info` (via `get_api_params`) — so
logging keeps working after construction; mirroring the JS SDK, `logger`
is a construction-time option and not a public per-request parameter
those methods accept from the caller, and nothing is logged unless a
logger is supplied. The stdlib `logging.Logger` is used directly as the
adapter (no ported JS `Logger` interface), and log levels match JS:
requests at `INFO`, successful API and unary RPC responses at `INFO`,
streamed RPC messages at `DEBUG`, failed API responses (status >= 400)
at `ERROR`. The always-on module-level (`e2b.*`) request logging at the
transport layer was removed in favor of this opt-in client-layer
logging, and volume content operations continue to accept `logger` per
call via `VolumeApiParams` to match the JS Volume API. Includes a
changeset and unit tests in `tests/test_logging_option.py`.
## Usage
```python
import logging
from e2b import Sandbox
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("my-app.e2b")
sbx = Sandbox.create(logger=logger)
sbx.commands.run("echo hello") # RPC logged via `logger`
sbx.set_timeout(60) # control-plane call also logged via `logger`
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Matt Brockman <matt.brockman@e2b.dev>
|
||
|
|
ec260376dc | [skip ci] Release new versions | ||
|
|
de0c401626 | fix(sdk): correct filesystem watch handle callback and timeout behavior (#1480) | ||
|
|
7e7e9514df |
feat(sdk): filesystem-only auto-pause via lifecycle.onTimeout object form (#1471)
## Filesystem-only auto-pause (`onTimeout` object form)
Adds an object form to the sandbox **lifecycle** `onTimeout`
(`on_timeout` in Python) that controls the snapshot kind taken when a
sandbox auto-pauses on timeout, via `keepMemory` (`keep_memory`).
`onTimeout` now accepts either the existing bare action (`'pause'` /
`'kill'`) or the object form `{ action, keepMemory }`. When `keepMemory`
is `false` (with `action: 'pause'`), a timeout auto-pause takes a
**filesystem-only** snapshot (no memory) instead of a full memory one,
so the sandbox cold-boots (reboots) from disk on resume — losing running
processes and open connections. Defaults to `true` (full memory
snapshot), so existing callers are unaffected. **The bare string form is
unchanged.**
It's the create-time / auto-pause counterpart to the explicit
`pause(keepMemory=false)` from #1465: same `keepMemory` naming, mapped
onto the `autoPauseMemory` create field.
### Type safety
The object form is a **discriminated union** on `action`: `keepMemory`
is only valid with `action: 'pause'`. Pairing it with `action: 'kill'`
is a **compile-time type error** (TS) / static error (`ty`), and is
additionally rejected at runtime (`InvalidArgumentError` /
`InvalidArgumentException`) for untyped callers.
### Behavior & validation
- `keepMemory` only applies to a `pause` action.
- **Incompatible with auto-resume** — auto-resume wakes a paused sandbox
on inbound traffic by restoring its memory snapshot in place; a
filesystem-only snapshot has no memory to restore (resuming cold-boots
it), so it must be resumed explicitly via `connect()`. Combining
`keepMemory: false` with `autoResume` is rejected client-side.
### Usage
```ts
// JS/TS — filesystem-only auto-pause on timeout
const sbx = await Sandbox.create({
lifecycle: { onTimeout: { action: 'pause', keepMemory: false } },
})
// bare string form still works (full memory snapshot)
const sbx2 = await Sandbox.create({ lifecycle: { onTimeout: 'pause' } })
```
```python
# Python
sbx = Sandbox.create(
lifecycle={"on_timeout": {"action": "pause", "keep_memory": False}}
)
```
### Changes
- `spec/openapi.yml`: `autoPauseMemory` on the create body (+
regenerated JS/Python clients).
- JS `SandboxOnTimeout` discriminated union (`'pause' | 'kill' | {
action: 'pause'; keepMemory? } | { action: 'kill' }`) and the Python
`SandboxOnTimeoutPause` / `SandboxOnTimeoutKill` TypedDicts, wired
through `createSandbox` / `_create_sandbox` (sync + async) to
`autoPauseMemory`, with the client-side guards.
- Tests: payload serialization + validation (offline, incl. the `action:
'kill'` type/runtime guard) and live cold-boot e2e in both SDKs;
changeset (`e2b` + `@e2b/python-sdk`, minor).
### Backend dependency
The live e2e tests exercise the real auto-pause→cold-boot path and
require the infra-side `autoPauseMemory` support (e2b-dev/infra#3055),
now merged and deployed.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
cb5a3870b6 |
feat(sdk): filesystem-only snapshots (pause memory:false) (#1465)
## Summary
Adds an optional **`memory`** flag to `pause` in both the JS and Python
SDKs. When `memory` is `false`, the pause captures **only the
filesystem** (no memory snapshot); resuming such a snapshot **cold-boots
(reboots)** the sandbox from disk — losing in-memory state, running
processes, and open connections. Defaults to `true` (full memory
snapshot), so existing callers are unaffected.
This is the SDK surface for the filesystem-only snapshot feature on the
infra side.
## Usage
```ts
// JS / TS
const sbx = await Sandbox.create()
await sbx.pause({ memory: false }) // filesystem-only snapshot
const resumed = await sbx.connect() // resumes by cold-booting from disk
```
```python
# Python (sync)
sbx = Sandbox()
sbx.pause(memory=False) # filesystem-only snapshot
resumed = sbx.connect() # resumes by cold-booting from disk
# Python (async)
sbx = await AsyncSandbox.create()
await sbx.pause(memory=False)
resumed = await sbx.connect()
```
`memory` defaults to `true` — `pause()` / `pause({})` behave exactly as
before.
## What changed
- **spec**: optional `memory: boolean` (default `true`) on `POST
/sandboxes/{sandboxID}/pause` (`SandboxPauseRequest`); both API clients
regenerated via `make codegen`.
- **JS**: `Sandbox.pause` / `betaPause` accept `{ memory }` →
`SandboxApi.pause` sends the request body.
- **Python**: `pause(memory=...)` / `beta_pause` → `_cls_pause` (sync +
async) sends `SandboxPauseRequest(memory=...)`.
- **Tests**: filesystem-only pause+resume reboots the guest while the
filesystem survives — JS (`tests/sandbox/snapshot.test.ts`) and Python
sync + async. All pass against a local stack; `format` / `lint` /
`typecheck` clean.
- **Changeset**: `minor` for `e2b` and `@e2b/python-sdk`.
## Note (related infra observation, not addressed here)
While testing, a filesystem-only **resume cold-boots into a different
default exec context** (`root` / `/root`) than a memory resume (`user` /
`/home/user`). The filesystem itself is fully intact; tests use absolute
paths to be robust to this. Worth confirming on the infra reboot path
whether the template's default user should be restored after a cold
boot.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Signed-off-by: Babis Chalios <babis.chalios@e2b.dev>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
|
||
|
|
31a93bed0c | [skip ci] Release new versions | ||
|
|
2a98cce8c7 |
fix(js-sdk): stop CommandHandle.disconnect() leaking the output subscription (#1474)
## Description
This PR fixes two related issues in the command handle's event handling.
### 1. JS `CommandHandle.disconnect()` leaked the output subscription
`disconnect()` was fire-and-forget — it only triggered the transport
abort and relied entirely on HTTP/2 abort propagation to stop events,
which is unreliable under keepalive: `onStdout`/`onStderr`/`onPty` could
keep firing for output produced after `disconnect()` returned.
`disconnect()` now sets a cooperative `disconnected` flag and aborts the
transport. The flag is checked before every callback dispatch in the
event loop, so once `disconnect()` returns no callback fires for output
that arrives (or was buffered) after the call — even if the underlying
abort hasn't torn the stream down yet. It does **not** wait for the
event handler to drain, so it returns promptly even for an idle command
(e.g. `sleep`) whose stream produces no further output, never blocks on
an in-flight callback, and does not deadlock when awaited from inside a
callback.
The async Python SDK was already correct here (`disconnect()` cancels
the event-handling task), and the sync Python SDK has no background
subscription (events are consumed only while the caller iterates). The
added Python tests confirm both.
### 2. Exit code was lost when a disconnected consumer stopped on a
flushed `end`-event chunk
When the `end` event flushes trailing decoder bytes (an incomplete
multibyte sequence → replacement character) and the consumer stops
iterating on the first flushed chunk, the generator was aborted before
the result was assigned, so `wait()` failed as if the process never
produced a result. The `end` handler now records the result **before**
yielding the flushed chunks, across the JS, async Python, and sync
Python SDKs.
## Usage
```js
const handle = await sandbox.commands.run(daemon, { background: true, stdin: true, onStdout })
await sandbox.commands.sendStdin(handle.pid, 'turn1\n')
await handle.disconnect() // resolves promptly; onStdout will not fire again
await sandbox.commands.sendStdin(handle.pid, 'turn2\n') // turn2 output never reaches onStdout
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
21af1f8a15 |
fix(python): avoid quadratic stdout/stderr accumulation in command ha… (#1472)
merged from https://github.com/e2b-dev/E2B/pull/1457 ## Problem `AsyncCommandHandle` / `CommandHandle` accumulate streamed output with `self._stdout += out` per chunk. Because `self._stdout` is an instance attribute (`STORE_ATTR`), CPython's in-place string-concatenation optimization — which only applies to local `STORE_FAST` targets — doesn't apply, so each append re-copies the entire buffer. For commands that emit large volumes of output this becomes O(n²) in total bytes, and in async contexts it stalls the event loop for hundreds of ms per chunk near the tail. ## Fix Buffer decoded chunks in a `list[str]` and `"".join()` them on read. This restores linear-time accumulation and keeps streaming responsive, with no change to the resulting `stdout`/`stderr` values or the public API. ## Notes - Applies the same change to both the sync and async command handles. - Pure internal change; the incremental UTF-8 decoding behavior is preserved. - Changeset included (`@e2b/python-sdk`, patch). Co-authored-by: davidzeng-pplx <david.zeng@perplexity.ai> |
||
|
|
7d4d620fa8 | [skip ci] Release new versions | ||
|
|
c1415f3ec7 |
Stream volume file uploads and downloads instead of buffering in memory (#1453)
Follow-up to #1433. Builds on the shared streaming infrastructure introduced there (`FILE_TIMEOUT_MS`, request-controller/stream-cleanup helpers in `connectionConfig`, `io_utils` chunk iterators, the `runtime` guard) and applies the same streaming model to volumes. > [!NOTE] > Based on `mishushakov/stream-write-file-upload` (#1433). Merge that PR first; this PR's diff will then retarget to `main` automatically. ## What changed - **`Volume.writeFile()` / `Volume.write_file()`** — stream the request body instead of buffering it in memory. - JS: `ReadableStream` data is streamed outside the browser (half-duplex); browsers still buffer since they can't stream request bodies. - Python: file-like objects are streamed in chunks (async wraps them in an async iterator; sync passes them to httpx directly, text-mode IO is encoded chunk-by-chunk). - **`Volume.readFile(format="stream")` / `read_file(format="stream")`** — the request timeout now bounds only the initial handshake, not the body read, matching the sandbox `files.read` stream path. A dropped connection during the handshake surfaces the same typed, health-checked error; JS supports `signal` to cancel an in-flight stream and cancels unconsumed bodies on error so the pooled connection is released. ## Usage JS — stream a file straight to a volume without buffering: ```ts import { createReadStream } from 'node:fs' import { Readable } from 'node:stream' const stream = Readable.toWeb(createReadStream('large-input.bin')) await volume.writeFile('/data/large-input.bin', stream) // read back as a stream; the body lives until consumed/cancelled const out = await volume.readFile('/data/large-input.bin', { format: 'stream' }) for await (const chunk of out) { // process chunk } ``` Python — stream a file-like object: ```python with open("large-input.bin", "rb") as f: volume.write_file("/data/large-input.bin", f) # streamed, not read() into memory for chunk in volume.read_file("/data/large-input.bin", format="stream"): ... # process chunk ``` ## Testing - `pnpm run format`, `pnpm run lint`, `pnpm run typecheck` pass. - Added volume streaming tests (JS `tests/volume/file.test.ts`; Python sync/async `test_file.py` text-stream cases). 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
60feee3cf6 |
Stream SDK file uploads and downloads instead of buffering in memory (#1433)
## Description Removes full in-memory buffering from the SDK **sandbox** file-transfer paths, in both JS and Python (sync + async). **Streamed uploads** — `Sandbox.files.write` / `write_files` streams `ReadableStream` (JS, outside the browser) and file-like (Python) input to the sandbox with chunk-by-chunk gzip compression, instead of buffering the whole body in memory. `useOctetStream`/`use_octet_stream` now defaults to auto-detect — octet-stream when any entry is streamable (so streamed uploads aren't silently buffered), `multipart/form-data` otherwise; browsers always use `multipart/form-data` since streaming request bodies aren't supported there. A streamed upload is bounded by a per-chunk timeout on the wire (Python's per-write `httpx` timeout, default the request timeout); a stalled upload the wire can't observe is bounded server-side. On Python's `AsyncSandbox`, the blocking file reads and gzip compression of a streamed upload now run in a worker thread so a large upload doesn't stall the event loop. **Streamed downloads** — `Sandbox.files.read(format="stream")` now streams the response body from the sandbox instead of downloading it into memory before iterating (Python sync + async), and the 60s request timeout no longer kills the stream while it's being consumed: - The request timeout now bounds only the initial handshake. - The body is bounded by a per-chunk **idle-read timeout** on the wire — a per-`read()` option (`streamIdleTimeoutMs` in JS, `stream_idle_timeout` in Python; default the request timeout — 60s — `0`/`None` to disable). It's armed only while waiting on a network read and cleared the moment a chunk arrives, so it aborts only when the server stops sending mid-stream; a slow or paused consumer never trips it (a held-but-unread stream is reclaimed server-side, not by this timer). - A dropped connection during the handshake surfaces the same typed, health-checked error as non-stream reads. In JS, `signal` can still cancel an in-flight stream. - The stream holds its pooled connection until it is consumed to the end, cancelled/closed, errors, or the idle timeout fires — consume it fully, use the context manager, or close it. (This replaces the earlier GC-finalizer net.) Python returns a `FileStreamReader`/`AsyncFileStreamReader` supporting deterministic cleanup via `close()`/`aclose()` and (async) context-manager use; both still satisfy `Iterator[bytes]`/`AsyncIterator[bytes]`, so existing iteration is unchanged. **Empty files** — JS `Sandbox.files.read()` with `blob` or `stream` format now returns a format-correct empty value (empty `Blob` / empty `ReadableStream`) for empty files instead of `""`. > [!NOTE] > The equivalent **volume** streaming changes (`Volume.writeFile`/`write_file`, `Volume.readFile`/`read_file` streams) live in a follow-up PR, #1453, which is based on this branch. ## Usage ```ts // JS: upload a large file without holding it in memory const file = createReadStream('large.bin') await sandbox.files.write('large.bin', Readable.toWeb(file), { gzip: true }) // JS: consume a download for longer than 60s without it being killed const stream = await sandbox.files.read('large.bin', { format: 'stream' }) for await (const chunk of stream) { /* ... */ } // JS: tune (or disable) the per-chunk idle-read timeout for a read const stream = await sandbox.files.read('large.bin', { format: 'stream', streamIdleTimeoutMs: 120_000, // 0 to disable }) // JS: empty files now return format-correct empty values const blob = await sandbox.files.read('empty.txt', { format: 'blob' }) // Blob (size 0), not '' ``` ```python # Python: streamed upload and download with open("large.bin", "rb") as f: sandbox.files.write("large.bin", f, gzip=True) for chunk in sandbox.files.read("large.bin", format="stream"): ... # Python: deterministic cleanup when not reading the stream to the end with sandbox.files.read("large.bin", format="stream") as stream: first_chunk = next(iter(stream)) # connection released on block exit # Python: tune (or disable) the per-chunk idle-read timeout for a read for chunk in sandbox.files.read( "large.bin", format="stream", stream_idle_timeout=120.0 # None to disable ): ... ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
3cb6ca5f92 |
[skip ci] Release new versions (sync repo with published packages) (#1468)
## Why The Release run [27844631141](https://github.com/e2b-dev/E2B/actions/runs/27844631141/job/82412481993) **published all packages successfully** but then failed at the final *"Commit new versions"* step — the version-bump commit-back to \`main\` was rejected as non-fast-forward (another PR landed on \`main\` during the release window). As a result the registries are ahead of the repo: | Package | Published | Repo (main) before this PR | |---|---|---| | \`e2b\` (JS) | 2.30.4 (npm) | 2.30.3 | | \`@e2b/cli\` | 2.12.2 (npm) | 2.12.1 | | \`e2b\` (Python) | 2.29.4 (PyPI) | 2.29.3 | The changeset \`fix-logo-pypi-npm.md\` was also never consumed and is still on \`main\`. ## What this PR does Replays exactly what the failed *"Commit new versions"* step would have committed — i.e. \`pnpm run version\` (changeset version + \`postVersion\` poetry sync) + lockfile update: - Bumps \`e2b\` → 2.30.4, \`@e2b/cli\` → 2.12.2, \`@e2b/python-sdk\` → 2.29.4 (matching what's already published) - Deletes the consumed changeset \`fix-logo-pypi-npm.md\` - Updates \`pnpm-lock.yaml\` (CLI's \`e2b\` dep → 2.30.4) No new packages are published by merging this — it only syncs the repo to the registries. **Do not re-run the Release workflow** for this changeset; the versions already exist on npm/PyPI. |
||
|
|
8171a03765 |
fix(python-sdk): let api_headers Authorization win over deprecated access_token (#1464)
The Python SDK's `ApiClient` applied the deprecated `access_token`
*after* merging `config.headers` (which includes `api_headers`), so a
custom `Authorization` passed via `api_headers` was silently overwritten
— the opposite of the JS SDK, where a custom `Authorization` wins. This
moves the deprecated access token before `config.headers` so
`api_headers` now takes precedence, matching JS. No change when only
`access_token` is set.
```python
# Custom Authorization via api_headers now wins over the deprecated access_token
ConnectionConfig(api_key="e2b_...", access_token="old", api_headers={"Authorization": "Bearer custom"})
# -> Authorization: Bearer custom
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
726ced6ec5 |
docs: fix duplicate logo on NPM/PyPI by switching to <picture> element (#1466)
## Summary Fixes the duplicate logo issue on NPM and PyPI caused by #1462. The `#gh-light-mode-only` / `#gh-dark-mode-only` URL fragments are GitHub-specific — NPM and PyPI ignore them and render both `<img>` tags. Switches all three package READMEs (CLI, JS SDK, Python SDK) to `<picture>` elements: ```html <picture> <source media="(prefers-color-scheme: dark)" srcset=".../logo-white.png"> <source media="(prefers-color-scheme: light)" srcset=".../logo-black.png"> <img alt="E2B Logo" src=".../logo-black.png" width="200"> </picture> ``` - **GitHub**: `<picture>` + `prefers-color-scheme` handles theme switching - **NPM/PyPI**: `<picture>` not supported, falls back to the single `<img>` (black logo) Link to Devin session: https://app.devin.ai/sessions/4983f23d23934d2c9a51733f5f9920f3 Requested by: @mlejva --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: vasek <vasek.mlejnsky@gmail.com> |
||
|
|
2c48e927c2 | [skip ci] Release new versions | ||
|
|
0a5d52478c |
docs: update package logos with theme-aware dark/light variants (#1462)
## Summary Replace the old `logo-circle.png` in the CLI, JS SDK, and Python SDK READMEs with the new E2B wordmark logos that adapt to GitHub's theme setting. Each package README now uses a `<picture>` element: ```html <picture> <source media="(prefers-color-scheme: dark)" srcset=".../logo-white.png"> <source media="(prefers-color-scheme: light)" srcset=".../logo-black.png"> <img alt="E2B Logo" src=".../logo-black.png" width="200"> </picture> ``` - **Light theme** → black logo (`logo-black.png`) - **Dark theme** → white logo (`logo-white.png`) - **NPM/PyPI** (no `<picture>` support) → falls back to the black logo via the `<img>` tag New logo assets added to `readme-assets/`: `logo-black.png`, `logo-white.png`. Includes a patch changeset for `@e2b/cli`, `e2b` (JS SDK), and `@e2b/python-sdk`. Link to Devin session: https://app.devin.ai/sessions/4983f23d23934d2c9a51733f5f9920f3 Requested by: @mlejva --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Co-authored-by: vasek <vasek.mlejnsky@gmail.com> |
||
|
|
f3e7f33973 |
refactor(sdks): tidy SDK auth and deprecate ConnectionConfig access token (#1452)
## Summary The access token was only ever used by the CLI, never by any SDK operation — sandbox, template, and volume calls all authenticate with the API key. This cleans up the auth plumbing and **deprecates** (rather than removes) the access token on `ConnectionConfig`, so there's no breaking change for direct SDK consumers. ## Changes - **Deprecated** the `accessToken` (JS) / `access_token` (Python) option on `ConnectionConfig`. It still works exactly as before — when set (or via `E2B_ACCESS_TOKEN`) the `Authorization: Bearer` header is still sent — but `apiHeaders` is now the recommended way to pass custom auth. - **Clear error when the API key is missing**, pointing to the API Keys tab (`https://e2b.dev/dashboard?tab=keys`). In JS this is gated by a `requireApiKey` option (default `true`) so callers that authenticate differently — like the CLI hitting `/teams` with an access token — can opt out; in Python the API key is always required. - Removed the unused access-token toggle from the API clients: `requireAccessToken` (JS) / `require_access_token` (Python). No caller ever set it to a non-default value, so behavior is unchanged. - The CLI now passes the access token to the `/teams` endpoint via `apiHeaders` instead of the deprecated option, and opts out of the API-key requirement on its own clients. - Decoupled the sandbox-scoped envd access token from `ConnectionConfig`: `EnvdApiClient` now owns its own `envdAccessToken` field and sets the `X-Access-Token` header itself, removing a redundant manually-set header. ## Recommended usage ```ts // Deprecated new ConnectionConfig({ accessToken: 'my-token' }) // Preferred new ConnectionConfig({ apiHeaders: { Authorization: 'Bearer my-token' } }) ``` ```python # Deprecated ConnectionConfig(access_token="my-token") # Preferred ConnectionConfig(api_headers={"Authorization": "Bearer my-token"}) ``` ## Verification `pnpm run typecheck`, `pnpm run lint`, Python `make typecheck`, and the unit tests all pass — including new tests for the API-key requirement (and its opt-out) in both SDKs. Confirmed the `Authorization: Bearer` header is still sent for both the deprecated option and `E2B_ACCESS_TOKEN`. --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
90724836a1 | [skip ci] Release new versions | ||
|
|
4619f8ca11 |
cicd/add wait for status for public traffic network tests (#1456)
when running server in sandbox, sometimes slow to start (>3s) so need to wait for status instead |
||
|
|
75e27420a2 |
cicd/fix test_commit_creates_commit timeout (#1455)
does a bunch of actions and times out sometimes |
||
|
|
432c0913c8 |
Add integration user agent composibility (#1454)
user agent is now composable, improving attribution |
||
|
|
706c553295 |
fix(sdks): fix template build bugs and consolidate shell quoting (#1442)
Fixes seven template-build correctness bugs across the JS and Python (sync + async) SDKs, plus a small shell-quoting cleanup. Each fix has unit/regression coverage, and real end-to-end builds were run against the API in all three SDK variants. **Template fixes** - `getAllFilesInPath` now sorts by full path so the files hash no longer depends on filesystem traversal order (the JS `sort()` was a no-op on glob `Path` objects). - `waitForPort` anchors the port match so port 80 no longer matches 8080. - The readycmd helpers (`waitForURL`/`waitForFile`/`waitForProcess`) and the file-op helpers (`remove`/`rename`/`makeDir`/`makeSymlink`) now shell-quote interpolated values/paths. - `waitForBuildFinish` keeps fetching logs after a terminal status so the tail of the build logs (beyond the API's 100-entries-per-call limit) is no longer dropped. - COPY instructions now collect one stack trace each, so failed-step traces stay aligned after `copy()` with multiple sources or `copyItems()`. - JS `LogEntry` strips ANSI escape codes in its constructor, matching the Python SDK. **Cleanup:** consolidated three duplicate single-quote shell helpers (`shellQuote`, the new `quoteShellArg`, and git's `shellEscape`) into one faithful `shlex.quote` port in `utils.ts` — safe values stay unquoted, keeping generated commands and layer-cache hashes stable. **Behavior note:** templates with paths/URLs containing spaces or shell metacharacters now build correctly; plain paths are unchanged, so existing layer caches are preserved. --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
fef573dc17 | [skip ci] Release new versions | ||
|
|
7cec36dcb5 |
fix(python-sdk): drop stream read timeout, enforce command timeout server-side (#1448)
## Summary Follow-up to #1444 (the `httpcore` → `TimeoutException` mapping, now merged). That mapping made the flaky timeout deterministic, but the underlying cause remained: the streaming HTTP `read` timeout was set to the command `timeout`, so it raced the server's own `deadline_exceeded` response. This PR removes the read timeout on streaming calls entirely and relies on the server-side `connect-timeout-ms` header to enforce the command timeout — matching the JS SDK, which has no per-chunk read timeout. The race is now structurally impossible rather than mapped over. ## Usage ```python cmd = sandbox.commands.run("sleep 10", timeout=2, background=True) try: for _ in cmd: pass except TimeoutException: print("command timed out") # server-side deadline_exceeded, no transport race ``` ## Tradeoff A silently dropped connection (no RST) on a command with `timeout=0` (disabled) now has no client-side read backstop and relies on keepalive pings — the same posture as the JS SDK. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
5de9bc2354 |
fix(python-sdk): map httpcore timeouts to TimeoutException to fix flaky test (#1444)
## Summary
The flaky test `test_run_with_too_short_timeout_iterating` failed
intermittently because, when iterating a background command's output,
the Python SDK sets the HTTP stream `read` timeout to the command
`timeout` — so it races the server's own `deadline_exceeded` response.
When the client read timeout won, a raw `httpcore.ReadTimeout` leaked
out instead of a `TimeoutException`. This PR maps
`httpcore.TimeoutException` to `TimeoutException` in
`handle_rpc_exception`, so callers get a consistent timeout error
regardless of which side fires first, plus unit tests for the mapping.
It also adds a JS parity test (JS was never affected — connect-es always
normalizes timeouts into an already-mapped `ConnectError`).
## Usage
```python
cmd = sandbox.commands.run("sleep 10", timeout=2, background=True)
try:
for _ in cmd:
pass
except TimeoutException:
print("command timed out") # now raised reliably, no raw httpcore.ReadTimeout
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
78c200afc8 |
feat(sdk): allow disabling client-side API key validation (#1360)
## Summary
Allow disabling client-side API key **format** validation. Previously
the SDKs hard-required keys to match the `e2b_<hex>` pattern, which
blocked deployments that issue API keys with a different format. Instead
of a custom-prefix override, this adds a simple on/off toggle.
The default behaviour is unchanged (validation stays **on**).
## Configuration
| Form | JS | Python |
| --- | --- | --- |
| Env var | `E2B_VALIDATE_API_KEY=false` | `E2B_VALIDATE_API_KEY=false`
|
| Connection option | `validateApiKey: false` | `validate_api_key=False`
|
The connection option takes priority over the environment variable.
## Usage
**JavaScript / TypeScript**
```ts
import { Sandbox } from 'e2b'
// Via connection option
const sandbox = await Sandbox.create({
apiKey: 'custom_key_format',
validateApiKey: false,
})
// Or via env var: E2B_VALIDATE_API_KEY=false
```
**Python**
```python
from e2b import Sandbox
# Via connection option
sandbox = Sandbox(
api_key="custom_key_format",
validate_api_key=False,
)
# Or via env var: E2B_VALIDATE_API_KEY=false
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
e88ae338e8 |
fix(sdks): handle signed URL expiration edge cases in upload/download URLs (#1429)
## Summary
- Python `upload_url`/`download_url` now raise
`InvalidArgumentException` when `use_signature_expiration` is passed for
an unsecured sandbox, matching the JS SDK (which now throws
`InvalidArgumentError` instead of a plain `Error`).
- A signature expiration of `0` was treated as falsy and silently
produced a never-expiring signed URL; it now produces an immediately
expiring URL in both SDKs.
- Adds unit tests mirrored across both SDKs
(`tests/sandbox/urls.test.ts` ↔ `tests/test_sandbox_urls.py`) plus a
changeset.
## Usage
```python
sbx = Sandbox() # not secure=True
sbx.download_url("a.txt", use_signature_expiration=120) # now raises InvalidArgumentException instead of silently ignoring the expiration
```
```ts
const sbx = await Sandbox.create({ secure: true })
await sbx.downloadUrl('a.txt', { useSignatureExpiration: 0 }) // URL now expires immediately instead of never
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
5ea287b11e |
fix(sdk): WriteInfo.type enum, tz-aware times, gzip on default upload path (#1437)
## Summary
- Python `write()` / `write_files()` now return `WriteInfo.type` as the
`FileType` enum instead of the raw API string (sync and async; the JS
string union was already correct, and the volumes client already
converts to `VolumeEntryStatType`).
- `EntryInfo.modified_time` is now timezone-aware UTC (protobuf
`ToDatetime()` returns naive datetimes by default), and naive volume
`atime`/`mtime`/`ctime` timestamps are normalized to UTC.
- `gzip=true` uploads now imply the `application/octet-stream` path in
both JS and Python instead of being silently ignored on the default
`multipart/form-data` path; on envd < 0.5.7 the upload falls back to
uncompressed multipart, matching the existing `use_octet_stream`
fallback.
- Adds sandbox-free unit tests for the model conversions, strengthens
write/info integration test assertions, and includes changesets for
`@e2b/python-sdk` and `e2b`.
## Usage examples
```python
info = sandbox.files.write("hello.txt", "hi")
info.type == FileType.FILE # was the raw string "file"
entry = sandbox.files.get_info("hello.txt")
entry.modified_time.tzinfo # datetime.timezone.utc (was None)
sandbox.files.write("big.bin", data, gzip=True) # now actually gzip-compressed
```
## Test plan
- [x] `pytest tests/test_filesystem_models.py` (new unit tests, 5
passed)
- [x] Python sync + async integration tests for `write`, `info`,
`content_encoding` (16 each, passed against live sandboxes)
- [x] JS `write.test.ts` + `contentEncoding.test.ts` (14 passed)
- [x] `pnpm run format`, `pnpm run lint`, `pnpm run typecheck`
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|