@e2b/cli@2.13.2
1033 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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>
|
||
|
|
0bd06d86d2 |
chore(js-sdk,cli): modernize tsconfig and adopt TypeScript 7 (side-by-side) (#1536)
Supersedes #1516 (same modernization at TypeScript 6.0). Rebased onto `main` now that the build runs on **tsdown** (#1515). ## What & why Adopt **TypeScript 7** for both packages and modernize the compiler config. TypeScript 7.0's native compiler [ships no programmatic API yet](https://devblogs.microsoft.com/typescript/announcing-typescript-7-0/#running-side-by-side-with-typescript-6.0) (it lands in 7.1), so anything built on the TS compiler API breaks on it — here that's tsdown's `.d.ts` generation and the codegen scripts (`openapi-typescript`, `json-schema-to-typescript`). Per the official guidance, TS 7 is installed **side-by-side** with TS 6: ```json "@typescript/native": "npm:typescript@^7.0.2", // native tsc — used for type-checking "typescript": "npm:@typescript/typescript6@^6.0.2" // TS6 w/ compiler API — used by tooling ``` - `tsc --noEmit` (typecheck) → **native TypeScript 7.0.2** (verified: `tsc --version` → 7.0.2) - `import 'typescript'` → **TypeScript 6.0** *with* the compiler API → tsdown dts + codegen keep working - Bonus: tsdown's dts no longer prints the "TypeScript 7.0 does not yet have a stable API and is experimental" warning (it's on the 6.0 API now) **Internal build-config change only — no public API or runtime behavior changes.** ## Compiler options: before → after ### `packages/js-sdk/tsconfig.json` | option | before | after | |---|---|---| | `target` | `es6` | `es2022` | | `lib` | `["dom","ESNext"]` | `["dom","es2022"]` | | `module` | _(unset)_ | `esnext` | | `moduleResolution` | `node` | `bundler` | | `allowJs` | `true` | **removed** (no `.js` sources) | | `allowSyntheticDefaultImports` | `true` | **removed** (implied by `esModuleInterop`) | | `useDefineForClassFields` | _(false, implied by es6)_ | **`false` (now explicit)** — see note | ### `packages/cli/tsconfig.json` | option | before | after | |---|---|---| | `moduleResolution` | `node` | `bundler` | | `strictNullChecks`, `strictFunctionTypes`, `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitThis`, `alwaysStrict` | `true` | **removed** (implied by `strict`) | | `downlevelIteration` | `true` | **removed** (removed in TS 7; no-op at `es2022`) | | `baseUrl` | `"."` | **removed** (removed in TS 7) | | `paths` | `{ e2b }` | `{ src, "src/*", e2b }` (replaces `baseUrl` for the existing `src/...` import style) | | `outDir` | `"dist"` | **removed** (unused under `tsc --noEmit`) | | `exclude` | _(none)_ | `["dist","node_modules"]` (so the built bundle is never type-checked) | `target`/`lib` for the CLI were already `es2022`. ## Notes / decisions - **Why side-by-side, not a plain `typescript@7` bump:** TS 7.0 is the native (Go) compiler rewrite — feature-identical to 6.0 for type-checking, no programmatic API until 7.1. A plain bump crashed both codegen tools (`Cannot read properties of undefined (reading 'createKeywordTypeNode')`). Side-by-side gives native-TS-7 checking while keeping the TS-6 API for tooling. Once 7.1 ships the API and the tools update, this collapses back to a single `typescript@7` dep. - **`useDefineForClassFields: false` is pinned explicitly.** Raising js-sdk's `target` to `es2022` flips this default to `true`, changing class-field emit and shifting stack frames. The template builder resolves the caller's directory and per-step traces via **fixed-depth** stack walking (`getCallerDirectory` in `src/template/index.ts`), so the extra frames threw it off by one — resolving `.copy('folder/*', …)` against the wrong base dir and mis-attributing build steps (`tests/template/build.test.ts` + `stacktrace.test.ts`). Pinning `false` keeps the exact pre-existing field semantics (es6 already implied `false`); adopting `define` semantics should be a separate, deliberately tested change. - **Target stays at `es2022`, not `es2023`.** `engines` still allow Node 20 (`>=20.18.1 <21 || >=22`). - **`moduleResolution: "bundler"`** typechecks + builds cleanly in both packages. The CLI's `baseUrl`-based bare imports (`from 'src/user'`, `from 'src'`) are preserved via `paths`; the bundled output still resolves them (build verified, binary smoke-tested). ## Not done (intentionally) - **`verbatimModuleSyntax`** — ~177 `import type` conversions; left as a follow-up. - **Shared `tsconfig.base.json`** — the two configs diverge too much to factor out cleanly. ## Verification - `pnpm run typecheck` ✅ both packages, on **native TS 7.0.2** - `pnpm run build` ✅ both packages (js-sdk ESM + CJS + **DTS**; cli CJS; binary smoke-tested) - codegen ✅ `openapi-typescript` + `json2ts` run and produce identical output (idempotent) - `pnpm run lint` ✅ both packages - `pnpm run test` — `template/build` + `template/stacktrace` now pass (`stacktrace` verified locally 30/30); remaining local failures are all `E2B_API_KEY`-gated live tests, unaffected by this change 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
49367c8491 |
build: switch from tsup to tsdown (#1515)
Switches the build tooling for `packages/js-sdk` and `packages/cli` from `tsup` (esbuild) to `tsdown` (rolldown), replacing each `tsup.config.js` with a `tsdown.config.ts` and updating the `build`/`dev` scripts and devDependencies. The published artifact layout is intentionally unchanged — the SDK still ships `dist/index.js` (CJS), `dist/index.mjs` (ESM) and `dist/index.d.ts`/`.d.mts`, and the CLI still ships an executable `dist/index.js` plus `dist/templates` — kept identical via `fixedExtension: false`. CLI dependency bundling is preserved by mapping the old `noExternal` to tsdown's `deps.alwaysBundle` (still excluding the ESM-only, dynamically-imported `inquirer`), and template copying moves from an `onSuccess` shell step to tsdown's `copy` option. Also aligns Node versions: `engines.node` for both packages is set to `20 || >=22`, the CLI build targets `node20`, and the pinned `nodejs` in `.tool-versions` is bumped to `22.11.0`. The large `pnpm-lock.yaml` diff is expected — it swaps the tsup/esbuild dependency tree for tsdown's rolldown tree (no lockfile format change). ## Verification - Both packages build cleanly with output filenames identical to the previous tsup builds. - `typecheck`, `lint` (oxlint) and `build` pass for both packages; the built CLI runs (`--version`). - Built js-sdk imports correctly in both CJS (`require`) and ESM (`import`), exposing the default `Sandbox` export and all named exports. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
e6c4e7e9d5 |
chore(js): modernize Connect/Protobuf and React test deps (#1512)
## What Modernizes the JS SDK's dependencies while remaining fully compatible with the current supported Node range (`>=20.18.1`) — no engine changes and no breaking impact for consumers. - **`@connectrpc/connect` / `@connectrpc/connect-web`:** `2.0.0-rc.3` → `^2.1.2` (off the pre-release pin onto the stable line, and switched to a `^` range). - **`@bufbuild/protobuf`:** `^2.6.2` → `^2.12.1`. - **React test deps:** `react` / `@types/react` → `^19.2.0`, and `react-dom` / `@types/react-dom` added at `^19.2.0` (previously auto-installed as v18 peers). Dev/test-only — no runtime impact. - **CI:** standardized `actions/setup-node` (mixed v3/v4/v6) to `v6` across all workflows; the three `@v3` uses were on the deprecated Node16 action runtime. No public SDK API changes — the sandbox filesystem and command RPCs use the same Connect transport configuration. ## Why undici / Node floor were dropped from this PR An earlier revision also bumped `undici` 7 → 8 and raised the Node floor to `>=22.19.0`. Usage data shows **Node 20 is still the single largest SDK runtime (~39% of sandbox creations)**, so dropping it would break the largest consumer segment via `engine-strict` install failures. undici 8 was the *only* change forcing Node 22, and undici `7.28.0` (already the latest 7.x) supports Node 20 — so undici stays at `^7.28.0` and the engine floor is unchanged. undici 8 is a good candidate for a future major once Node 20 usage declines. ## Verification - typecheck, lint (oxlint), and build pass - 22 mocked Connect/undici transport unit tests pass - 106 live filesystem/command tests pass over connectrpc `2.1.2` + undici `7.28.0` 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
0feb926937 | [skip ci] Release new versions | ||
|
|
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>
|
||
|
|
a39db3bb36 |
chore: switch from eslint to oxlint (#1514)
Replaces ESLint (and its `@typescript-eslint/*` and `unused-imports` plugins) with [oxlint](https://oxc.rs) across the `js-sdk` and `cli` packages. A root `.oxlintrc.json` replaces the three `.eslintrc.cjs` files, the package `lint` scripts now run `oxlint`, the related devDependencies are swapped for `oxlint`, and the lint CI path filter is updated accordingly. Formatting rules (`quotes`/`semi`/`linebreak-style`) are dropped because Prettier already enforces them, and `no-unused-vars` is set to error to preserve the previous unused-imports check. The one behavior change is that `@typescript-eslint/member-ordering` has no oxlint equivalent and is no longer enforced. `lint`, `typecheck`, and `prettier` all pass clean for both packages. 🤖 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> |
||
|
|
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>
|
||
|
|
dabac31cab |
Reuse toUploadBody in JS volume writeFile (#1473)
Replace the inlined stream/buffer logic in the JS volume `writeFile` with the shared `toUploadBody` helper, matching the `sandbox/filesystem` write path and dropping the now-unused local `runtime` and `toBlob` imports. The helper already returns a `ReadableStream` only when the body should be streamed (non-browser stream input) and otherwise buffers into a Blob, so deriving `isStream` from `body instanceof ReadableStream` is byte-for-byte equivalent to the old check. This is a pure refactor with no behavior change, keeping the two write paths from drifting. Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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. |
||
|
|
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> |
||
|
|
73826a3089 |
chore(deps): bump undici from 7.25.0 to 7.28.0 in the npm_and_yarn group across 1 directory (#1461)
Bumps the npm_and_yarn group with 1 update in the / directory: [undici](https://github.com/nodejs/undici). Updates `undici` from 7.25.0 to 7.28.0 <details> <summary>Release notes</summary> <p><em>Sourced from <a href="https://github.com/nodejs/undici/releases">undici's releases</a>.</em></p> <blockquote> <h2>v7.28.0</h2> <h1>⚠️ Security Release</h1> <p>This release line addresses <strong>7 security advisories</strong>, all shipped in <strong>v7.28.0</strong>.</p> <blockquote> <p><strong>Action required:</strong> Upgrade to <strong>undici 7.28.0</strong> or later.</p> <pre lang="sh"><code>npm install undici@^7.28.0 </code></pre> </blockquote> <p>The v7 line is <strong>not</strong> affected by GHSA-38rv-x7px-6hhq (CVE-2026-9675), which is an 8.x-only regression.</p> <blockquote> <p><strong>Note on GHSA-hm92-r4w5-c3mj:</strong> this fix shipped in <strong>v7.28.0</strong>, not the earlier 7.2x line — the vulnerable single-pool code was still present through <code>v7.27.2</code>. The per-origin pool fix is <a href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a> (<a href="https://redirect.github.com/nodejs/undici/pull/5041">#5041</a>).</p> </blockquote> <h2>Summary</h2> <table> <thead> <tr> <th>Advisory</th> <th>CVE</th> <th>Severity (CVSS)</th> <th>Fixed in</th> <th>Fix commit</th> </tr> </thead> <tbody> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></td> <td>CVE-2026-12151</td> <td>High (7.5)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></td> <td>CVE-2026-9697</td> <td>High (7.4)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/04201f89"><code>04201f89</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-hm92-r4w5-c3mj">GHSA-hm92-r4w5-c3mj</a></td> <td>CVE-2026-6734</td> <td>High (7.5)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-pr7r-676h-xcf6">GHSA-pr7r-676h-xcf6</a></td> <td>CVE-2026-9678</td> <td>Moderate (5.9)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/85a24055"><code>85a24055</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-p88m-4jfj-68fv">GHSA-p88m-4jfj-68fv</a></td> <td>CVE-2026-9679</td> <td>Moderate (5.9)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-g8m3-5g58-fq7m">GHSA-g8m3-5g58-fq7m</a></td> <td>CVE-2026-11525</td> <td>Low (3.7)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td> </tr> <tr> <td><a href="https://github.com/nodejs/undici/security/advisories/GHSA-35p6-xmwp-9g52">GHSA-35p6-xmwp-9g52</a></td> <td>CVE-2026-6733</td> <td>Low (3.7)</td> <td>7.28.0</td> <td><a href="https://github.com/nodejs/undici/commit/ea8930cf"><code>ea8930cf</code></a></td> </tr> </tbody> </table> <hr /> <h2>High severity</h2> <h3>WebSocket DoS via fragment count bypass — CVE-2026-12151</h3> <p><strong><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></strong> · CWE-400, CWE-770 <strong>Fix:</strong> <a href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a> <em>websocket: limit the number of fragments in a message</em> (part of backport <a href="https://github.com/nodejs/undici/commit/a027a4a0"><code>a027a4a0</code></a> <em>Backport WebSocket maxPayloadSize fixes to v7.x</em>, <a href="https://redirect.github.com/nodejs/undici/pull/5423">#5423</a>)</p> <p>A malicious WebSocket server can stream a large number of small or empty continuation frames. Undici enforced a limit on cumulative payload size but did not limit the <em>number</em> of fragments per message, leading to unbounded memory growth and denial of service.</p> <ul> <li><strong>Affected:</strong> applications using <code>new WebSocket(...)</code> or <code>WebSocketStream</code> against untrusted endpoints.</li> <li><strong>Workaround:</strong> none — upgrade is required.</li> </ul> <h3>TLS certificate validation bypass in SOCKS5 ProxyAgent — CVE-2026-9697</h3> <p><strong><a href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></strong> · CWE-295</p> <!-- raw HTML omitted --> </blockquote> <p>... (truncated)</p> </details> <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/nodejs/undici/commit/f9eba0ad9134e1c0977848476bba9d49734696e4"><code>f9eba0a</code></a> Bumped v7.28.0 (<a href="https://redirect.github.com/nodejs/undici/issues/5430">#5430</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/a027a4a04c6c055877d1abaf5f60ee4917e7e01f"><code>a027a4a</code></a> Backport WebSocket maxPayloadSize fixes to v7.x (<a href="https://redirect.github.com/nodejs/undici/issues/5423">#5423</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/8cb10f983eb6005dd53f3744d95d3b6d7dbcee0f"><code>8cb10f9</code></a> websocket: limit the number of fragments in a message</li> <li><a href="https://github.com/nodejs/undici/commit/04201f8947041f0f4f2ac865dbdb1677e46a8844"><code>04201f8</code></a> fix: honor requestTls when proxy is SOCKS5</li> <li><a href="https://github.com/nodejs/undici/commit/fcd642ff613ea9030dec87cf622e68d4b1ae9847"><code>fcd642f</code></a> fix(socks5): preserve dispatch backpressure return value (<a href="https://redirect.github.com/nodejs/undici/issues/5166">#5166</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/bc98c97906abf26fa1e959b2f6111b53ade0e18f"><code>bc98c97</code></a> fix(socks5): use configured connector in Socks5ProxyAgent (<a href="https://redirect.github.com/nodejs/undici/issues/5168">#5168</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/9e1c74372a2b27cacd92d27c13a83a6d84f10e0e"><code>9e1c743</code></a> fix(socks5): encode embedded IPv4 tails in IPv6 literals correctly (<a href="https://redirect.github.com/nodejs/undici/issues/5099">#5099</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/376c8be27cb40cc17ccaad6b6ebb317fa7148d65"><code>376c8be</code></a> fix(socks5): enforce authenticated state before CONNECT (<a href="https://redirect.github.com/nodejs/undici/issues/5097">#5097</a>)</li> <li><a href="https://github.com/nodejs/undici/commit/3805b8f8518882991044048c256e005dc3c10a85"><code>3805b8f</code></a> fix(socks5-proxy-agent): use per-origin pools to prevent cross-origin routing...</li> <li><a href="https://github.com/nodejs/undici/commit/85a240551c9feb8b8a0ecc56c84b2b3015add8a9"><code>85a2405</code></a> fix(cache): trim qualified field names</li> <li>Additional commits viewable in <a href="https://github.com/nodejs/undici/compare/v7.25.0...v7.28.0">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> |
||
|
|
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 | ||
|
|
432c0913c8 |
Add integration user agent composibility (#1454)
user agent is now composable, improving attribution |
||
|
|
71b450f30e |
fix(js-sdk): detect Bun and Deno connection-dropped errors in health check (#1451)
When the connection to a sandbox is dropped mid-request, each JS runtime surfaces it with different wording, but the health check only recognized Node/undici's `terminated` — so on Bun and Deno a sandbox killed mid-request was reported as a raw transport error instead of a `TimeoutError`. This matches every known variant (Node: `terminated`, Bun: `The socket connection was closed unexpectedly`, Deno: `error reading a body from connection`) via a shared list used by both the RPC and fetch error handlers, and broadens the fetch-error type guard from `TypeError` to `Error` since Bun raises a plain `Error`. Added parametrized tests covering all three runtimes; format, lint, and typecheck pass. No Python SDK change is needed — it detects dropped connections by exception type (`RemoteProtocolError`), which is already runtime-agnostic, and Bun/Deno are JS-only runtimes. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> |
||
|
|
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 | ||
|
|
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>
|
||
|
|
8c084cbd7c |
chore(deps): bump tar from 7.5.12 to 7.5.16 in the npm_and_yarn group across 1 directory (#1443)
Bumps the npm_and_yarn group with 1 update in the / directory: [tar](https://github.com/isaacs/node-tar). Updates `tar` from 7.5.12 to 7.5.16 <details> <summary>Commits</summary> <ul> <li><a href="https://github.com/isaacs/node-tar/commit/cf213384ac558b539c07c830ed7cca5ffc9e8550"><code>cf21338</code></a> 7.5.16</li> <li><a href="https://github.com/isaacs/node-tar/commit/21a822027658c8063542be330530bbecdf0dbbfe"><code>21a8220</code></a> do not apply PAX header fields to meta entries</li> <li><a href="https://github.com/isaacs/node-tar/commit/52632cf38fdbdbbc7ab86184d68a85b11fdb0970"><code>52632cf</code></a> update project deps</li> <li><a href="https://github.com/isaacs/node-tar/commit/302f51ff3f8eefeccc06be4caf7d0de9b7c321d7"><code>302f51f</code></a> fix inconsequential typo in PENDINGLINKS symbol name</li> <li><a href="https://github.com/isaacs/node-tar/commit/55dbb99b27640bb12b2ac059b26883c23523b3e3"><code>55dbb99</code></a> remove some uses of mutate-fs</li> <li><a href="https://github.com/isaacs/node-tar/commit/87cc309f13c21d598b0b833235d387a252455058"><code>87cc309</code></a> 7.5.15</li> <li><a href="https://github.com/isaacs/node-tar/commit/7aef486f0d21c10fd7790b16b1b28f04648cf334"><code>7aef486</code></a> fix: regression in pending links detection</li> <li><a href="https://github.com/isaacs/node-tar/commit/6244eb33846bbd407443f5d0e339bd8c91663cd6"><code>6244eb3</code></a> 7.5.14</li> <li><a href="https://github.com/isaacs/node-tar/commit/9704d8c6f639573775133cbbd541aba83cb46c9c"><code>9704d8c</code></a> stricter protection against hardlinks preempting their targets</li> <li><a href="https://github.com/isaacs/node-tar/commit/700734f9aeb113bcc5f1400d81b8be7d499e54a2"><code>700734f</code></a> update workflows and deps</li> <li>Additional commits viewable in <a href="https://github.com/isaacs/node-tar/compare/v7.5.12...v7.5.16">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> |
||
|
|
cb061d269b |
fix(sdk): correct command/PTY stream handling in Python and JS SDKs (#1441)
## Summary
Fixes three command/PTY streaming issues in the Python and JS SDKs:
- **Multibyte UTF-8 corruption (JS + Python sync/async):** stdout/stderr
were decoded per-chunk, so a UTF-8 character split across two stream
chunks turned into replacement characters. Each handle now keeps a
persistent incremental decoder per stream
(`codecs.getincrementaldecoder` in Python, a shared `TextDecoder` with
`{ stream: true }` in JS) and flushes any incomplete trailing bytes to
`�` on the end event, preserving the existing broken-UTF-8 behavior.
- **`commands.list()` optionals (Python):** now returns `None` instead
of `""` for unset proto3-optional `tag` and `cwd` fields, matching the
declared `Optional[str]` types and the JS SDK.
- **Leaked connections (Python):** command/PTY/watch streams are now
closed when stream setup fails, instead of abandoning the generator (and
its pooled HTTP connection) until GC.
## Usage example
```python
# Split multibyte output is now decoded correctly instead of returning "ð\x9f\x98\x80"-style garbage
result = sandbox.commands.run("printf '😀'")
assert result.stdout == "😀"
# Unset fields are None rather than ""
proc = sandbox.commands.list()[0]
assert proc.tag is None # previously ""
```
## Testing
- New unit tests for incremental/trailing UTF-8 decoding (Python sync +
async, JS).
- Live command/PTY/watch integration suites pass.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
b52eb3c76d |
fix: debug-mode Sandbox.connect() and Unset token handling in Python connect() (#1428)
## Description
`Sandbox.connect()` now short-circuits in debug mode instead of calling
the control plane, matching `Sandbox.create()` — fixed in the JS SDK and
both sync/async Python SDKs (static and instance variants). The Python
SDK's `connect()` also previously passed the generated client's `Unset`
sentinel through as the envd/traffic access tokens when they were absent
(non-secure sandboxes), which made `download_url()`/`upload_url()` emit
broken signed URLs; `_cls_connect` now normalizes the response into
`SandboxCreateResponse` with proper `None` values, the same pattern
`_create_sandbox` already uses. Dead `Unset` checks and the now-unused
generated `Sandbox` model import were cleaned up, and unit tests cover
both behaviors in all three implementations. Debug mode is resolved
through `ConnectionConfig`, so the `E2B_DEBUG` env var triggers the
short-circuit in both `connect()` and `create()`, not just an explicit
`debug=True`.
## Usage
```ts
// JS: works fully offline with E2B_DEBUG / debug: true (no control plane call)
const sbx = await Sandbox.connect(sandboxId, { debug: true })
```
```python
# Python: non-secure sandboxes get unsigned URLs again instead of broken signatures
sbx = Sandbox.connect(sandbox_id)
print(sbx.download_url("file.txt")) # no garbage signature when envd token is absent
# Debug mode skips the control plane, like Sandbox.create()
sbx = Sandbox.connect(sandbox_id, debug=True)
```
## Testing
New unit tests in `tests/sandbox/connect.test.ts`,
`tests/sync/sandbox_sync/test_connect.py`, and
`tests/async/sandbox_async/test_connect.py`; existing connect
integration suites pass against the live API (8/8 sync Python, 8/8 async
Python, 6/6 JS).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
e0ed071443 |
fix(python-sdk): anchor file-metadata validation regexes with \A/\Z (#1438)
## Summary
Python's `$` regex anchor also matches just before a trailing newline,
so file-metadata keys/values ending in `\n` passed client-side
validation (unlike the JS SDK, where `$` matches only the true end of
string) and then failed deep in the HTTP stack with an opaque "illegal
header value" error. This re-anchors both validation regexes with
`\A`/`\Z` so such inputs are rejected upfront with
`InvalidArgumentException`, matching JS behavior and the existing
convention used for the API-key pattern.
Also adds trailing-newline rejection test cases to the sync/async Python
suites and, for parity of coverage, to the JS SDK suite (JS already
rejected them — no behavior change there).
## Usage example
```python
sandbox.files.write("file.txt", "x", metadata={"author": "mish\n"})
# before: passed validation, then httpcore raised 'Illegal header value'
# after: raises InvalidArgumentException with a clear message
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
82add5b4ea |
fix(sdks): raise typed, actionable errors when sandbox dies mid-request (#1419)
## Problem
When a sandbox is killed (or reaches its end of life) while a request is
in flight, both SDKs surfaced unusable errors:
- **JS**: `SandboxError: 2: [unknown] terminated` — typed, but cryptic
and says nothing about the sandbox being killed.
- **Python**: leaked a completely raw `httpcore.RemoteProtocolError:
<StreamReset stream_id:1, error_code:2, remote_reset:True>`.
This affected the whole envd streaming family (`commands.run`, PTY
sessions, `files.watchDir`/`watch_dir`) and the `files.read`/`write`
HTTP transfers.
The stream-reset signature alone can't distinguish the sandbox dying
from an intermediary (load balancer, network) dropping the connection —
so the SDKs now actively check, and only transform the error when the
sandbox is confirmed gone.
## Fix
**Health-check disambiguation.** When the connection-terminated
signature appears (JS: `ConnectError` `Code.Unknown` + `terminated` or
Undici `TypeError: terminated`; Python: `httpcore`/`httpx`
`RemoteProtocolError`), the SDK probes envd's `/health` endpoint:
- **502 (sandbox confirmed gone)** → `TimeoutError` (JS) /
`TimeoutException` (Python): "The sandbox was killed or reached its end
of life while the request was in flight." This matches how requests to
an *already-dead* sandbox surface today (the 502 / `Code.Unavailable`
mappings raise the timeout error type), so the exception type no longer
depends on whether the sandbox died just before or just during the
request.
- **Anything else** (still running, or probe inconclusive) → the
original error propagates unchanged, exactly as before this PR.
The probe (5s timeout) runs only on the termination signature, never on
the happy path or for other errors. A health-check closure is plumbed
into `Commands`/`Pty`/`Filesystem` and the command/watch handles in JS
and sync/async Python; `Commands`/`Pty` now receive the envd API client
in their constructors (internal signature change).
**Cleanup** (`e2b_connect/client.py`): removed the
`@_retry(RemoteProtocolError, 3)` decorators from
`call_server_stream`/`acall_server_stream`. They never executed —
`inspect.iscoroutinefunction` is false for (async) generator functions,
and calling a generator function doesn't run its body, so the wrapper's
`try/except` could never fire. A *working* mid-stream retry would be
wrong anyway (it would replay already-delivered events). Unary retries
are unchanged.
## Before / after
```ts
const sandbox = await Sandbox.create()
const cmd = await sandbox.commands.run('sleep 60', { background: true })
await sandbox.kill() // e.g. from another process
await cmd.wait()
// before: SandboxError: 2: [unknown] terminated
// after: TimeoutError: [unknown] terminated: The sandbox was killed or reached
// its end of life while the request was in flight.
```
```python
sandbox = Sandbox.create()
cmd = sandbox.commands.run("sleep 60", background=True)
sandbox.kill()
cmd.wait()
# before: httpcore.RemoteProtocolError: <StreamReset stream_id:1, error_code:2, remote_reset:True> (not an e2b type!)
# after: e2b.exceptions.TimeoutException: <StreamReset ...>: The sandbox was killed
# or reached its end of life while the request was in flight.
```
If the health probe does not confirm the sandbox is gone (e.g. a load
balancer dropped the connection, or local envd in debug mode), the
original error propagates unchanged — the SDK only makes a claim when it
has verified it.
## Notes
- Not covered: errors raised while consuming a `format: 'stream'` body
**after** `files.read` returns (JS `ReadableStream` consumption happens
in user code). Python is fully covered since httpx buffers non-streaming
responses inside the request call.
## Tests
- Unit: confirmed-kill → `TimeoutError`/`TimeoutException`, raw-error
passthrough for running/unknown/probe-failure, health check skipped for
unrelated errors — 28 Python + 25 JS assertions pass.
- Integration (run against live sandboxes, all passing): start `sleep
60`, kill the sandbox, assert `wait()` raises
`TimeoutError`/`TimeoutException` with the *confirmed* kill message —
JS, sync Python, and async Python.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
a6c801d043 | [skip ci] Release new versions | ||
|
|
e873ee94b6 |
feat(sdk): add allowNetworkMounts option to filesystem watch (#1420)
Client-side counterpart to [e2b-dev/infra#2982](https://github.com/e2b-dev/infra/pull/2982): adds an `allowNetworkMounts`/`allow_network_mounts` option to filesystem directory watching across the JS and Python (sync + async) SDKs, so clients can explicitly opt into watching paths on network filesystem mounts (NFS, CIFS, SMB, FUSE), which envd rejects by default. Events on network mounts may be unreliable or not delivered at all, hence the explicit opt-in. This regenerates the filesystem proto code from the updated spec and threads the flag through `watchDir`/`watch_dir` (streaming `WatchDir` and polling `CreateWatcher`). The option requires envd 0.6.4 (shipped by the infra PR); using it against an older sandbox throws a `TemplateError`/`TemplateException`. Default behavior is unchanged. Includes new watch tests for all three SDKs and a minor-bump changeset for `e2b` and `@e2b/python-sdk`. > Note: the new tests exercise the flag on a regular directory (a network mount can't be set up from SDK tests) and require envd 0.6.4, so this should land with/after the infra deploy. All pre-existing watch tests pass; the new ones currently fail with the expected `TemplateError` against the deployed envd. ### Usage **JavaScript** ```ts const handle = await sandbox.files.watchDir( '/mnt/nfs-share/my-dir', (event) => console.log(event.type, event.name), { allowNetworkMounts: true } ) ``` **Python (async)** ```python handle = await sandbox.files.watch_dir( "/mnt/nfs-share/my-dir", on_event=lambda e: print(e.type, e.name), allow_network_mounts=True, ) ``` **Python (sync)** ```python handle = sandbox.files.watch_dir("/mnt/nfs-share/my-dir", allow_network_mounts=True) for e in handle.get_new_events(): print(e.type, e.name) ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
cab59caa0b |
fix(js-sdk): pass getMetrics start/end as query params (#1427)
## Description
`Sandbox.getMetrics()` in the JS SDK passed the `start` and `end`
options as path parameters instead of query parameters, so openapi-fetch
never serialized them and the requested time range was silently ignored.
They are now sent under `query`, matching the OpenAPI spec; the Python
SDKs already passed them correctly. The metrics tests across JS and
Python (sync/async) now assert that returned metrics fall within the
requested window (with slack for 5s metric-bucket alignment) and that a
window from before the sandbox existed returns no metrics, which would
have caught this regression.
## Usage
```ts
const start = new Date(Date.now() - 60_000)
const metrics = await sandbox.getMetrics({ start, end: new Date() })
// metrics are now actually limited to the requested time range
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
0b0c728fe8 |
Fix volume SDK issues: transports, timeouts, empty files, eager stream errors (#1431)
Fixes a batch of review findings in the JS and Python volume SDKs,
aligning behavior between the two. Python now caches `AsyncVolume` HTTP
transports per event loop and proxy (sync per thread) instead of a
process-wide singleton, applies the 60s default `request_timeout` to
metadata operations that previously ran with httpx timeouts disabled, no
longer falls back to `E2B_ACCESS_TOKEN` for volume content auth, and no
longer mutates the caller's `headers` dict. JS `Volume.readFile` now
returns empty values instead of `undefined` for empty files, and volume
content requests get the documented 60s default request timeout. All
changes are covered by new mock-based unit tests (no live API needed)
plus changesets for both SDKs.
## Usage examples
```python
volume = await AsyncVolume.connect(volume_id)
# Times out after 60s by default (previously could hang indefinitely)
entries = await volume.list("/")
```
```ts
const volume = await Volume.connect(volumeId)
// Returns an empty Blob / ReadableStream for empty files (previously undefined)
const blob = await volume.readFile('empty.txt', { format: 'blob' })
// Times out after the documented 60s by default; pass 0 to disable
await volume.getInfo('file.txt', { requestTimeoutMs: 0 })
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
91e84d9186 |
fix(js-sdk): raise errors for non-2xx API and envd responses with empty bodies (#1430)
## Description
`handleApiError` and `handleEnvdApiError` used the presence of an error
body as the failure signal, but openapi-fetch returns `error: undefined`
for non-2xx responses with `Content-Length: 0` (and `error: ''` for
empty bodies without that header), so such failures were silently
treated as success — e.g. `sandbox.isRunning()` returned `true` for a
failing `/health` endpoint. Both handlers now gate on `response.ok` and
fall back to the status text when no error message is available. Volumes
go through the same shared handler, so they inherit the fix. Regression
tests cover both handlers and the volume path, including a
500-with-empty-body case that fails against the old code.
## Example
```ts
// Before: a 500 response with Content-Length: 0 was treated as success
await Volume.create('my-volume') // resolved with 'Response data is missing'
// After: the failure surfaces as the proper SDK error
await Volume.create('my-volume') // throws VolumeError('500: Internal Server Error')
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|
||
|
|
feb2d452fa |
fix(js-sdk): return boolean from Sandbox.kill() instance method (#1426)
## Description The `Sandbox.kill()` instance method discarded the boolean result of the static `SandboxApi.kill()`, returning `Promise<void>` — inconsistent with the static method and with both Python SDKs (sync and async), which return `bool` from both variants. It now returns `Promise<boolean>` (`true` if the sandbox was killed, `false` if it was not found), including `true` in debug mode to match the other implementations. Non-breaking: `void` → `boolean` is additive for existing callers. The kill test now asserts the return value. ## Usage ```ts const sandbox = await Sandbox.create() const killed = await sandbox.kill() console.log(killed) // true if killed, false if the sandbox was not found ``` 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Fable 5 <noreply@anthropic.com> |
||
|
|
b9e131ad88 | [skip ci] Release new versions | ||
|
|
1d5259ca1d |
fix(sdks): transport proxy caching, request timeout, and option handling (#1421)
Fixes a batch of connection-handling bugs found in review, with unit
tests for each and a changeset for both SDKs.
- **Python**: cached HTTP transports are now keyed on the configured
proxy, so clients with different (or no) proxy settings no longer
silently reuse the transport built for the first proxy seen.
- **Python**: `request_timeout` now applies to control-plane API
requests — the underlying httpx client was previously built with no
timeout at all (`request_timeout=0` still disables it).
- **Python**: the server-stream parser no longer stalls or drops the
final envelope when the remaining payload is shorter than the 5-byte
envelope header; also removed the no-op `@_retry` decorators from the
streaming RPC methods to avoid confusion.
- **JS + Python**: an explicit `debug=False` / `debug: false` now
overrides `E2B_DEBUG=true` instead of being ignored.
- **JS**: the RPC logger no longer crashes requests with a `TypeError`
when a response contains protobuf int64 (`bigint`) fields (e.g.
`EntryInfo.size` returned by `files.list()`/`stat()` and `includeEntry`
watch events); they are logged as strings.
## Usage examples
```ts
// JS: logging RPCs whose responses carry int64 fields no longer throws
const sbx = await Sandbox.create({ logger: console })
await sbx.files.list('/')
// JS: force-disable debug mode even when E2B_DEBUG=true is set
const sbx2 = await Sandbox.create({ debug: false })
```
```python
# Python: per-call API timeout is now actually enforced
Sandbox.list(request_timeout=10)
# Python: clients with different proxies get their own transports
Sandbox.create(proxy="http://127.0.0.1:8080")
Sandbox.create() # no longer routed through the proxy above
```
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
|