Skip to content

feat(sdk): add an E2B client to the Code Interpreter SDKs - #1783

Draft
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1787774000-code-interpreter-e2b-client
Draft

feat(sdk): add an E2B client to the Code Interpreter SDKs#1783
devin-ai-integration[bot] wants to merge 3 commits into
mainfrom
devin/1787774000-code-interpreter-e2b-client

Conversation

@devin-ai-integration

@devin-ai-integration devin-ai-integration Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds an E2B client to @e2b/code-interpreter and e2b-code-interpreter, so the API key/domain can be bound explicitly instead of coming from the environment. packages/js-sdk and packages/python-sdk are untouched: the binding is local to this package and relies only on the existing ClientFactory class state (boundOpts / _bound_api_params) the downstream Sandbox already inherits.

import { E2B } from '@e2b/code-interpreter'

const client = new E2B({ apiKey, domain })
const sandbox = await client.Sandbox.create() // code-interpreter-v1, bound config
await sandbox.runCode('x = 1; x += 1; x')
from e2b_code_interpreter import E2B

client = E2B(api_key=..., domain=...)
sandbox = client.Sandbox.create()
sandbox.run_code("x = 1; x += 1; x")

The mechanism, per package:

// src/client.ts — Code Interpreter Sandbox is bound here…
this.Sandbox = class extends Sandbox {
  protected static override readonly boundOpts = boundOpts
}
// …the non-Code-Interpreter resources come from the core client.
const core = new CoreE2B(boundOpts)
this.Volume = core.Volume
this.Template = core.Template
this.Secret = core.Secret
# client.py
def _bind(cls, api_params):
    return type(cls.__name__, (cls,), {"_bound_api_params": api_params})

self.Sandbox = _bind(Sandbox, api_params)
self.AsyncSandbox = _bind(AsyncSandbox, api_params)
core = CoreE2B(**api_params)  # Volume/Template/Secret (+ async variants)

Options are copied (and the nested headers / apiHeaders maps snapshotted, signal dropped) 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/None does 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:http server, 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

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-integration

Copy link
Copy Markdown
Contributor Author

🤖 Devin AI Engineer

I'll be helping with this pull request! Here's what you should know:

✅ I will automatically:

  • Address comments on this PR. Add '(aside)' to your comment to have me ignore it.
  • Look at CI failures and help fix them

Note: I can only respond to comments from users who have write access to this repository.

⚙️ Control Options:

  • Disable automatic comment, CI, and merge conflict monitoring

@cla-bot cla-bot Bot added the cla-signed label Aug 27, 2026
@changeset-bot

changeset-bot Bot commented Aug 27, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: a3bce7a

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 2 packages
Name Type
@e2b/code-interpreter Minor
@e2b/code-interpreter-python Minor

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

@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from f17c887. Download artifacts from this workflow run.

JS SDK (e2b@2.46.2-devin-1787774000-code-interpreter-e2b-client.0):

npm install ./e2b-2.46.2-devin-1787774000-code-interpreter-e2b-client.0.tgz

CLI (@e2b/cli@2.18.1-devin-1787774000-code-interpreter-e2b-client.0):

npm install ./e2b-cli-2.18.1-devin-1787774000-code-interpreter-e2b-client.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.7.3-devin-1787774000-code-interpreter-e2b-client.0):

npm install ./e2b-code-interpreter-2.7.3-devin-1787774000-code-interpreter-e2b-client.0.tgz

Desktop JS SDK (@e2b/desktop@2.3.4-devin-1787774000-code-interpreter-e2b-client.0):

npm install ./e2b-desktop-2.3.4-devin-1787774000-code-interpreter-e2b-client.0.tgz

Python SDK (e2b==2.46.1+devin.1787774000.code.interpreter.e2b.client):

pip install ./e2b-2.46.1+devin.1787774000.code.interpreter.e2b.client-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.9.2+devin.1787774000.code.interpreter.e2b.client):

pip install ./e2b_code_interpreter-2.9.2+devin.1787774000.code.interpreter.e2b.client-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.4.5+devin.1787774000.code.interpreter.e2b.client):

pip install ./e2b_desktop-2.4.5+devin.1787774000.code.interpreter.e2b.client-py3-none-any.whl

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 (withOpts vs _with_params naming 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'

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Template,
Volume,
)
from e2b.connection_config import ApiParams

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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').

Suggested change
from e2b.connection_config import ApiParams
from e2b import ApiParams

(Or fold ApiParams into the from e2b import (...) block above and drop this line.)

Comment thread packages/js-sdk/src/connectionConfig.ts Outdated
* @hidden
* @hide
*/
static withOpts<T>(this: T, opts?: Omit<ConnectionOpts, 'signal'>): T {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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:

Suggested change
static withOpts<T>(this: T, opts?: Omit<ConnectionOpts, 'signal'>): T {
static withOpts<T>(this: T, opts?: E2BClientOpts): T {

"""

@classmethod
def _with_params(cls, **api_params: Unpack[ApiParams]) -> Type[Self]:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 — withOptswith_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_paramsopts) and _bound_api_paramsboundOpts. 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.

devin-ai-integration Bot and others added 2 commits August 27, 2026 21:22
…re SDKs untouched

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant