feat(sdk): add an E2B client to the Code Interpreter SDKs - #1783
feat(sdk): add an E2B client to the Code Interpreter SDKs#1783devin-ai-integration[bot] wants to merge 5 commits into
Conversation
Move the configuration binding onto the resource classes in the core, so the core and the downstream clients share one implementation. Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
🦋 Changeset detectedLatest commit: ecd493b The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Package ArtifactsBuilt from 4f31712. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.46.2-devin-1787774000-code-interpreter-e2b-client.0.tgzCLI ( npm install ./e2b-cli-2.18.1-devin-1787774000-code-interpreter-e2b-client.0.tgzCode Interpreter JS SDK ( npm install ./e2b-code-interpreter-2.7.3-devin-1787774000-code-interpreter-e2b-client.0.tgzDesktop JS SDK ( npm install ./e2b-desktop-2.3.4-devin-1787774000-code-interpreter-e2b-client.0.tgzPython SDK ( pip install ./e2b-2.46.1+devin.1787774000.code.interpreter.e2b.client-py3-none-any.whlCode Interpreter Python SDK ( pip install ./e2b_code_interpreter-2.9.2+devin.1787774000.code.interpreter.e2b.client-py3-none-any.whlDesktop Python SDK ( pip install ./e2b_desktop-2.4.5+devin.1787774000.code.interpreter.e2b.client-py3-none-any.whl |
There was a problem hiding this comment.
TASTE.md review (sdk-harness) of the new E2B client plumbing.
Checked: cross-language parity (T-1, T-2), API shape and option-type conventions (T-3, T-19, T-22, T-23), module/entry-point structure (T-54), configuration precedence and immutability (T-49–T-51), naming (T-9, T-12).
The core design holds up well: required-vs-optional shape is right (new E2B(opts?) / E2B(**opts), T-3), per-call options taking precedence keeps the explicit → env → default chain intact (T-49), the bound options are snapshotted rather than aliased (T-51), and hoisting the binding into ClientFactory so both SDKs and both downstream packages share one implementation is exactly the consistency T-1/T-2 ask for.
4 violations, all in the wiring rather than the design:
- 2× T-54 (entry-point/export hygiene)
- 1× T-23 (inline option type in a signature)
- 1× T-1a/T-22 (
withOptsvs_with_paramsnaming parity)
Not tied to a changed line: E2BClientParams in packages/code-interpreter-python/e2b_code_interpreter/client.py (and its e2b counterpart it mirrors) breaks T-22, which asks for the Opts suffix in both languages — E2BClientOpts in JS, so E2BClientOpts in Python too. Since the name is already shipped in the core e2b package, I'm not asking for it here alone; renaming both together in one change is the fix, and doing it now while the downstream client is still unreleased is cheaper than later. Same root cause as the _with_params comment below.
| @@ -1,5 +1,6 @@ | |||
| export * from 'e2b' | |||
|
|
|||
| export { E2B, type E2BClientOpts } from './client' | |||
There was a problem hiding this comment.
T-54 — one flat entry point, and in JS "runtime values use export and type-only names use export type — never mixed". This line mixes the class and the type alias in one statement; every other export in this file (and in js-sdk/src/index.ts) keeps them apart.
| export { E2B, type E2BClientOpts } from './client' | |
| export { E2B } from './client' | |
| export type { E2BClientOpts } from './client' |
Note the same mixed line already exists at packages/js-sdk/src/index.ts:160 from the earlier client PR — worth fixing there in this stack too rather than propagating it.
There was a problem hiding this comment.
Done — export { E2B } and export type { E2BClientOpts } are now separate statements (4b90c98). Left packages/js-sdk/src/index.ts alone: this PR no longer touches the core SDKs at all.
| Template, | ||
| Volume, | ||
| ) | ||
| from e2b.connection_config import ApiParams |
There was a problem hiding this comment.
T-54 — "one flat entry point per package … no subpath exports". Reaching into e2b.connection_config from a different package pins this client to the core's internal module layout, and it isn't necessary: ApiParams is already in e2b.__all__ (packages/python-sdk/e2b/__init__.py). The JS sibling gets this right (import { ConnectionOpts, … } from 'e2b').
| from e2b.connection_config import ApiParams | |
| from e2b import ApiParams |
(Or fold ApiParams into the from e2b import (...) block above and drop this line.)
There was a problem hiding this comment.
Done — now from e2b import ApiParams (4b90c98).
| * @hidden | ||
| * @hide | ||
| */ | ||
| static withOpts<T>(this: T, opts?: Omit<ConnectionOpts, 'signal'>): T { |
There was a problem hiding this comment.
T-23 — "the option type is always named and exported from the entry point, never an inline intersection in the signature". Omit<ConnectionOpts, 'signal'> is spelled inline here, again in TemplateBase.withOpts (packages/js-sdk/src/template/index.ts), and a third time in packages/code-interpreter-js/src/client.ts — while the identical shape already has a name, E2BClientOpts. Three inline copies mean a future change to what is bindable has to be found by grep instead of following the type.
Declare the alias here in connectionConfig.ts (client.ts imports from this module, so it can't live there without a cycle) and re-export it unchanged from client.ts / index.ts, then:
| static withOpts<T>(this: T, opts?: Omit<ConnectionOpts, 'signal'>): T { | |
| static withOpts<T>(this: T, opts?: E2BClientOpts): T { |
There was a problem hiding this comment.
Obsolete: ClientFactory.withOpts is gone. packages/js-sdk and packages/python-sdk are untouched now; the Code Interpreter client binds its own Sandbox locally (anonymous subclass carrying boundOpts) and delegates Volume/Template/Secret to the core E2B client.
| """ | ||
|
|
||
| @classmethod | ||
| def _with_params(cls, **api_params: Unpack[ApiParams]) -> Type[Self]: |
There was a problem hiding this comment.
T-1a / T-22 — the three surfaces "mirror each other 1:1 in names and semantics, differing only by language idiom", and option-shaped things carry the Opts suffix in both languages. This is the same operation as JS ClientFactory.withOpts, but the names diverge twice over: withOpts → _with_params swaps Opts for params and changes visibility (public + @internal in JS, underscore-private in Python), so the shared vocabulary downstream packages have to learn is different per language.
Pick one spelling and mirror it — withOpts ↔ with_opts if it stays part of the (documented-as-internal) cross-package surface, _withOpts ↔ _with_opts if it should be private in both. Same for the parameter name (api_params → opts) and _bound_api_params ↔ boundOpts. Not suggesting a patch inline because the rename touches the body and the callers in e2b/client.py, e2b_code_interpreter/client.py, and the tests.
There was a problem hiding this comment.
Obsolete: _with_params is gone — the core Python SDK is untouched; binding is a local _bind(cls, api_params) helper in e2b_code_interpreter/client.py. On E2BClientParams vs E2BClientOpts: keeping E2BClientParams here because that's the name the released core e2b package already uses for the same type, and renaming would mean changing the core SDK, which is explicitly out of scope for this PR. Happy to do the parity rename (core + both downstream packages) in a follow-up.
…re SDKs untouched Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
|
can you implement taste suggestions if sill relevant |
Split the type-only export out of the runtime one, import ApiParams from the e2b entry point instead of its module, and drop the computed __all__. Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 4b90c9848b
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| from typing import Dict, Type, TypeVar, cast | ||
|
|
||
| from e2b import ApiParams | ||
| from e2b import E2B as CoreE2B |
There was a problem hiding this comment.
Require the core SDK version that provides E2B
When an environment resolves any currently allowed e2b version from 2.39.1 through 2.43.x, this import raises ImportError because the core E2B client was introduced in 2.44.0. Since e2b_code_interpreter.__init__ imports this module unconditionally, even existing imports such as from e2b_code_interpreter import Sandbox stop working in those valid dependency environments; raise the e2b lower bound in packages/code-interpreter-python/pyproject.toml to the first compatible release.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Yes, the floor needed raising — fixed. E2B and ApiParams both first shipped in e2b 2.44.0 (release commit 33195ae, changeset 5759f17), while the floor was >=2.39.1, so a resolution to 2.39.1–2.43.x would ImportError on import e2b_code_interpreter. Now e2b>=2.44.0,<3.0.0 (ecd493b); uv.lock is unchanged since the workspace resolves e2b as an editable path dependency. Same bump applied to packages/desktop-python in #1784.
There was a problem hiding this comment.
Looks good — a straightforward, symmetric extension of the existing js-sdk/python-sdk E2B client mechanism (from PR #1720, unmodified here) into the Code Interpreter packages.
What was reviewed: the new client.ts/client.py binding logic (defensive copy of opts, headers/apiHeaders snapshot, signal drop in JS with no Python equivalent needed), the Sandbox/AsyncSandbox subclassing via boundOpts/_bound_api_params against the pre-existing ClientFactory/mergeOpts base in js-sdk, and the Cloudflare vitest exclusion (justified — the test spins up a node:http server workerd can't bind). Tests in both packages cover precedence, isolation between clients, and mutation-safety of bound options/headers.
Extended reasoning...
Overview
The PR adds an E2B client class to both @ e2b/code-interpreter (JS) and e2b-code-interpreter (Python) that lets callers bind connection config (API key, domain, etc.) explicitly instead of relying on environment variables. It reuses a mechanism already merged into packages/js-sdk and packages/python-sdk in a prior PR (#1720): a ClientFactory base class with a boundOpts/_bound_api_params static field and a mergeOpts helper that gives per-call options precedence over bound options over env vars. This PR's new code just subclasses Sandbox/AsyncSandbox with the bound options and delegates Volume/Template/Secret (and Python's async variants) to a bound core E2B client instance — no changes to js-sdk or python-sdk themselves, confirmed by git log showing boundOpts/ClientFactory/mergeOpts predate this diff.
Security risks
None identified. This is client-side configuration binding with no new network surface, no crypto, no auth bypass. The defensive copying of the options object and its nested headers/apiHeaders maps (both JS and Python) correctly prevents a caller from mutating the bound config after client construction, which is verified by dedicated tests in both suites.
Level of scrutiny
Moderate — this touches the public SDK surface (hence the changeset) and needs JS/Python parity per CLAUDE.md, but the underlying mechanism was already reviewed and merged separately, and this diff is a mechanical, low-risk application of that pattern to a downstream package. I checked JS/Python parity point by point (subclassing approach, defensive copying, precedence semantics, signal handling) and found it consistent; the asymmetry in signal handling (dropped explicitly in JS via delete, no analog needed in Python since ApiParams/E2BClientParams has no signal field at all) is correctly reasoned, not an oversight.
Other factors
Test coverage is thorough on both sides (244 lines JS, 236 lines Python) covering precedence, cross-client isolation, isolation from env-configured top-level exports, and mutation-safety. The Cloudflare vitest config exclusion is narrowly scoped and well-commented, with the Node/bun/deno legs still running the excluded test. The inline review comments left by the bot's own earlier pass appear addressed by the subsequent commit (4b90c98) predating the human comment from mishushakov, and there is no outstanding CHANGES_REQUESTED review from a third party. No bugs were reported by the automated bug hunter for this diff.
|
can you check codex comment do we need to raise min version ? |
The client imports E2B and ApiParams from the e2b entry point, both of which first shipped in 2.44.0. Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
Summary
Adds an
E2Bclient to@e2b/code-interpreterande2b-code-interpreter, so the API key/domain can be bound explicitly instead of coming from the environment.packages/js-sdkandpackages/python-sdkare untouched: the binding is local to this package and relies only on the existingClientFactoryclass state (boundOpts/_bound_api_params) the downstreamSandboxalready inherits.The mechanism, per package:
Options are copied (and the nested
headers/apiHeadersmaps snapshotted,signaldropped) so later mutations of the caller's object can't change what's bound.Precedence is the core one, unchanged: per-call options > client options > env; explicit
undefined/Nonedoes not clear a bound value. Clients are isolated from each other and from the env-configured top-level exports.Also: the new JS suite serves a mocked API from a local
node:httpserver, which workerd can't listen on, so it's excluded from the Cloudflare runtime leg (the Node/bun/deno legs keep running it).Link to Devin session: https://app.devin.ai/sessions/ec1af68649fd4880a1cb27cb51d819e9
Open in Devin Desktop: https://app.devin.ai/desktop/session/ec1af68649fd4880a1cb27cb51d819e9?variant=devin
Requested by: @mishushakov