Skip to content

feat(sdk): public withOptions / with_params on resource classes - #1786

Closed
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
devin/1787866410-with-opts-multi-client
Closed

feat(sdk): public withOptions / with_params on resource classes#1786
devin-ai-integration[bot] wants to merge 8 commits into
mainfrom
devin/1787866410-with-opts-multi-client

Conversation

@devin-ai-integration

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

Copy link
Copy Markdown
Contributor

Summary

Reworks the multi-client mechanism: the resource classes are themselves clients, so they gain a public withOptions (JS static) / with_params (Python classmethod) that returns a copy of the class with the connection config bound, merging with any config already bound. The E2B client is built on top of these.

// JS — on Sandbox, Volume, Secret, Template (inherited from the ClientFactory base)
const MySandbox = Sandbox.withOptions({ apiKey: 'e2b_...', domain: 'e2b.dev' })
const sandbox = await MySandbox.create()

const MyTemplate = Template.withOptions({ apiKey: 'e2b_...' }) // stays callable
await MyTemplate.build(MyTemplate().fromPythonImage('3'), 'my-env')
# Python — on Sandbox/AsyncSandbox, Volume/AsyncVolume, Template/AsyncTemplate, Secret/AsyncSecret
MySandbox = Sandbox.with_params(api_key="e2b_...", domain="e2b.dev")
sandbox = MySandbox.create()
  • withOptions / with_params return a new bound subclass; the receiver is unchanged. Rebinding merges: {...alreadyBound, ...new}, and per-call options still take precedence over bound options, with env vars as the fallback.
  • Template.withOptions returns a callable template, so MyTemplate() keeps working as a builder factory.
  • JS strips signal so it is never bound; options are shallow-copied so caller mutations can't affect a bound class.
  • The bindClientOpts / bind_client_params subclassing helpers stay internal (not exported from the package entrypoints); withOptions / with_params are the public API, inherited by any ClientFactory subclass so downstream SDKs (desktop, code-interpreter) get it on their resources for free.
  • new E2B(opts) now simply calls Resource.withOptions(opts) for each resource; the client itself has no withOptions / with_params.

Link to Devin session: https://app.devin.ai/sessions/56eb7b178bad4472a975e6607475af43
Open in Devin Desktop: https://app.devin.ai/desktop/session/56eb7b178bad4472a975e6607475af43?variant=devin
Requested by: @mishushakov

devin-ai-integration Bot and others added 2 commits August 27, 2026 21:33
@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: 80385e7

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

This PR includes changesets to release 2 packages
Name Type
e2b Patch
@e2b/python-sdk Patch

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

@mishushakov
mishushakov marked this pull request as ready for review August 27, 2026 21:35
@mishushakov
mishushakov self-requested a review as a code owner August 27, 2026 21:35
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from c20f0b8. Download artifacts from this workflow run.

JS SDK (e2b@2.46.2-devin-1787866410-with-opts-multi-client.0):

npm install ./e2b-2.46.2-devin-1787866410-with-opts-multi-client.0.tgz

CLI (@e2b/cli@2.18.1-devin-1787866410-with-opts-multi-client.0):

npm install ./e2b-cli-2.18.1-devin-1787866410-with-opts-multi-client.0.tgz

Code Interpreter JS SDK (@e2b/code-interpreter@2.7.3-devin-1787866410-with-opts-multi-client.0):

npm install ./e2b-code-interpreter-2.7.3-devin-1787866410-with-opts-multi-client.0.tgz

Desktop JS SDK (@e2b/desktop@2.3.4-devin-1787866410-with-opts-multi-client.0):

npm install ./e2b-desktop-2.3.4-devin-1787866410-with-opts-multi-client.0.tgz

Python SDK (e2b==2.46.1+devin.1787866410.with.opts.multi.client):

pip install ./e2b-2.46.1+devin.1787866410.with.opts.multi.client-py3-none-any.whl

Code Interpreter Python SDK (e2b-code-interpreter==2.9.2+devin.1787866410.with.opts.multi.client):

pip install ./e2b_code_interpreter-2.9.2+devin.1787866410.with.opts.multi.client-py3-none-any.whl

Desktop Python SDK (e2b-desktop==2.4.5+devin.1787866410.with.opts.multi.client):

pip install ./e2b_desktop-2.4.5+devin.1787866410.with.opts.multi.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.

Reviewed against TASTE.md (sdk-harness), focusing on the JS/Python parity rules (T-1, T-1a, T-2), API-shape rules (T-4, T-18, T-19, T-22, T-23), and the configuration rules (T-49, T-51). The refactor itself is a good move — the binding logic now lives in one place per language and the merge precedence is unchanged.

2 violations, both on the JS side:

  1. T-1 / T-18 — withOpts is a public static on every resource class (Sandbox.withOpts(...) type-checks and autocompletes for users; @hidden only hides it from generated docs), while its Python mirror _with_params is private. See inline comment.
  2. T-23 — the option type is spelled inline as Omit<ConnectionOpts, 'signal'> even though E2BClientOpts already names exactly that shape and is exported. See inline comment.

Two notes not tied to a changed line:

  • T-51 (config objects are immutable snapshots): both new doc comments claim that copying the options means "later mutations of the caller's object/dicts cannot change the bound configuration". That holds only at the top level — headers, apiHeaders and proxy are still the caller's objects by reference, so mutating opts.headers after construction does change the bound config. Either say "shallow copy" or copy the nested maps.
  • The class extends (this as unknown as typeof ClientFactory) { … } as unknown as T double cast means nothing in this function is actually checked by TS — the T extends { prototype: ClientFactory } constraint accepts any object with a prototype, so withOpts called on an unrelated class fails only at runtime. The this-typed static plus cast is the pragmatic option given TS has no polymorphic this for statics (and the comment explains that well), but if the method stops being part of the public surface (point 1) the constraint can be tightened to typeof ClientFactory & { prototype: ClientFactory } and only the return cast kept.

Comment thread packages/js-sdk/src/connectionConfig.ts Outdated
Comment thread packages/js-sdk/src/connectionConfig.ts Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a41b0e43b5

ℹ️ 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".

Comment thread packages/js-sdk/src/connectionConfig.ts Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Looks good — a clean, mechanical refactor that moves the existing bind-and-strip-signal logic from E2B's constructor into a shared ClientFactory.withOpts/_with_params method, with no behavior change. Reviewed the JS withOpts (signal-stripping copy, this-typed generic tightened in the second commit) and Python _with_params (kwargs-unpacking dict copy via type(...)) for parity, and confirmed no stale references to the old _bind helper or inline boundOpts subclassing remain. Existing tests (boundOpts.test.ts, test_bound_api_params.py, test_client.py) exercise the new code path, and the patch changeset is appropriately scoped since the new methods are marked internal/hidden.

Extended reasoning...

Overview

This PR refactors how E2B's multi-client binds connection options to its resource classes (Sandbox, Volume, Secret, Template, and the Python async variants). The bind-a-subclass-with-config logic that previously lived inline in E2B's constructor (JS anonymous class extends X { boundOpts }, Python _bind() helper) is moved onto the shared ClientFactory base as withOpts (JS) / _with_params (Python), and client.ts/client.py are simplified to call it per resource class.

Security risks

None identified. This is purely an internal code-organization change; no new external input handling, auth, or crypto is touched. The signal-stripping behavior (to avoid an AbortSignal bleeding into every call) is preserved verbatim in JS, and the Python side never had a signal concept in ApiParams to begin with, so parity holds.

Level of scrutiny

Low-to-moderate. The change is small, self-contained, and mechanical (moving code, not changing semantics), but it touches a shared base class (ClientFactory) used by both sync and async resource classes in Python and all resource classes in JS, so I checked parity carefully: JS's withOpts still copies opts and deletes signal; Python's _with_params relies on **params kwargs-unpacking to produce a fresh dict per call (previously a single shared dict was reused across all 8 _bind calls in client.py, which is a harmless behavioral non-issue since the dict was never mutated). I verified no stale _bind( or old inline boundOpts = subclass patterns remain in the codebase after the refactor.

Other factors

Existing tests (packages/js-sdk/tests/template/boundOpts.test.ts, packages/python-sdk/tests/sync/template_sync/test_bound_api_params.py, packages/python-sdk/tests/async/template_async/test_bound_api_params.py, and the integration-style packages/python-sdk/tests/test_client.py) exercise the exact code paths touched here and were not modified, consistent with the "behavior unchanged" claim. A patch-level changeset was added for both e2b and @ e2b/python-sdk, which is appropriate since the new methods are marked @ internal @ hidden / :meta private: rather than being new public API. The second commit tightens the JS generic constraint from <T> to <T extends { prototype: ClientFactory }>, a real type-safety improvement caught during the PR's own iteration. I was unable to execute pnpm run typecheck/tests directly due to a sandbox permission restriction, so this assessment relies on careful static reading of the diff plus the pre-existing test coverage.

@mishushakov

Copy link
Copy Markdown
Member

okay let's instead switch to an exported method bindClientOpts / bind_client_params
and it should be merging not replacing the bound opts

devin-ai-integration Bot and others added 2 commits August 27, 2026 21:49
…with already-bound opts

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
…clarify shallow-copy docs

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration devin-ai-integration Bot changed the title refactor(sdk): bind multi-client config via hidden withOpts/_with_params refactor(sdk): bind multi-client config via exported bindClientOpts/bind_client_params Aug 27, 2026
…t_params helpers instead of statics

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration devin-ai-integration Bot changed the title refactor(sdk): bind multi-client config via exported bindClientOpts/bind_client_params refactor(sdk): bind multi-client config via exported bindClientOpts/bind_client_params helpers Aug 27, 2026
…w merged client; make binding helpers internal

Co-Authored-By: mish@e2b.dev <mish@e2b.dev>
@devin-ai-integration devin-ai-integration Bot changed the title refactor(sdk): bind multi-client config via exported bindClientOpts/bind_client_params helpers refactor(sdk): OpenAI-style client.withOptions / client.with_params for multi-client Aug 27, 2026
@devin-ai-integration devin-ai-integration Bot changed the title refactor(sdk): OpenAI-style client.withOptions / client.with_params for multi-client feat(sdk): public withOptions / with_params on resource classes and E2B client Aug 27, 2026
@mishushakov
mishushakov marked this pull request as draft August 27, 2026 22:10
@devin-ai-integration devin-ai-integration Bot changed the title feat(sdk): public withOptions / with_params on resource classes and E2B client feat(sdk): public withOptions / with_params on resource classes Aug 27, 2026
@mishushakov

Copy link
Copy Markdown
Member

I am thinking of keeping it as-is for now and redoing how we do the client inside the classes

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