feat(sdk): restore transform callback with placeholder context#1335
feat(sdk): restore transform callback with placeholder context#1335mishushakov wants to merge 24 commits into
Conversation
Extends SandboxNetworkConfig.allowOut/denyOut to accept objects of the
form { host, transform: [{ headers }] } alongside plain string entries.
Updates OpenAPI spec, regenerates JS + Python clients, surfaces new TS
types (SandboxNetworkRule, SandboxNetworkRuleTransform, SandboxNetworkEntry),
and adds a contract test that curls httpbin.org/headers and asserts an
injected Authorization-style header is reflected back.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Adds SandboxNetworkRule and SandboxNetworkRuleTransform TypedDicts to the Python SDK, widens SandboxNetworkOpts.allow_out to accept them, and exposes them from the top-level e2b package. - Mirrors the TS contract: deny_out stays as List[str], only allow_out gains the object form with optional per-host transforms. - Reverts denyOut in the OpenAPI spec to items: string only (allowOut keeps the oneOf form with SandboxNetworkRule) and regenerates the JS + Python clients to match. - Adds httpbin.org/headers transform tests for both async and sync Python sandbox tests, parallel to the TS case. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
- Drops the redundant string-form duplicate of httpbin.org and the denyOut/deny_out scoping; the test now exercises just the structured rule with header transform. - Switches the Python tests to plain-dict literals (typed via a local SandboxNetworkOpts annotation) instead of TypedDict constructors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…onfig Replaces the TYPE_CHECKING / in-method imports of SandboxNetworkRule with a single top-level import. There is no circular-import risk here, so the openapi-python-client guard is unnecessary noise. Note: this file is generated by openapi-python-client and the guard will reappear on the next \`make codegen\` run unless we add a postprocess step. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There is no local SandboxNetworkConfig to collide with — the user-facing equivalent is SandboxNetworkOpts — so the ClientSandboxNetworkConfig rename was dead noise. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
The generated from_dict pre-initialized allow_out to [] and then iterated over \`_allow_out or []\`, collapsing the absent-key case (UNSET, which is falsy) into an empty list. Downstream from_client_network_config could then set "allow_out": [] in the user-facing dict, which is semantically "deny all outbound" rather than "field not provided". Now we only build the list when the key is actually present, leaving allow_out as UNSET otherwise. The other oneOf-array fields are not affected because deny_out remains a plain list[str]. Note: this lives in generated code; \`make codegen\` will reintroduce the bug until openapi-python-client is patched or a postprocess step is added. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Reverts the manual edits to the generated SandboxNetworkConfig (TYPE_CHECKING hoist + UNSET-preserving from_dict) since that file is owned by openapi-python-client and will be overwritten on the next codegen run. Addresses the same underlying bug — generated from_dict pre-inits allow_out to [] and then iterates with \`_allow_out or []\`, so an absent allowOut field deserializes to [] instead of UNSET — by treating empty allow_out as "not provided" inside the hand-written from_client_network_config wrapper. This keeps the public dict from gaining a misleading "allow_out": [] entry. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…t_network_config Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Separate the outbound network policy (allowOut/denyOut) from firewall
rules. Rules are registered under a top-level firewall map keyed by host
but do not grant egress on their own — hosts must still be referenced
via allowOut. Selectors accept either a static list or a callback that
receives { firewallHosts, allHosts } for composable policies.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Move per-host transform rules from a top-level `firewall` field to
`network.rules`. The selector context now exposes `{ allTraffic, rules }`
where `allTraffic` is `'0.0.0.0/0'` and `rules` is a Map (Mapping in
Python) view of `network.rules`. SandboxFirewall* schemas renamed to
SandboxNetworkRule / SandboxNetworkTransform.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SandboxNetworkRules now accepts both a plain object and a Map. The helper normalizes either form to a Map for the selector context and serializes via Object.fromEntries for the wire body. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Network rules can now declare transforms as a callback that receives a typed context exposing literal placeholder strings (sandboxId, teamId, executionId, identity.jwt). The proxy resolves these per request at egress, so the SDK serializes the resolved object as-is and users get typed access without hardcoding template strings. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Inject `ctx.sandboxId` as a header on the httpbin rule and verify the
reflected response matches the live `sandbox.sandbox_id`, proving the
proxy substituted `${e2b.sandboxId}` per request.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
API responses can only contain plain JSON, so rules in the info shape must not allow the JS Map variant or callback transforms accepted by the input types. Introduce SandboxNetworkRuleInfo mirroring SandboxNetworkRule with transform fixed to the static SandboxNetworkTransform, and use it for SandboxNetworkInfo.rules in both SDKs. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
We host our own httpbin mirror at httpbin.e2b.team — switch the rule host and curl target away from httpbin.org so tests don't depend on a third-party service. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment out the sandboxId placeholder tests until the proxy-side placeholder resolution (e2b.identity.jwt, e2b.sandboxId, etc.) is in place. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Prevents user transform callbacks from mutating the module-level placeholder context and corrupting subsequent sandbox creations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Stage 1 ships only static `transform: { headers: ... }` rules. The
callback variant — `transform: ({ identity, sandboxId }) => (...)` — and
its placeholder context (TRANSFORM_CONTEXT, SandboxNetworkTransformContext,
SandboxNetworkTransformResolver) are removed for now; they'll return in a
follow-up PR alongside the proxy-side placeholder resolution and tests.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This reverts commit 0dec3af.
|
PR SummaryMedium Risk Overview Reviewed by Cursor Bugbot for commit 156c0a9. Bugbot is set up for automated code reviews on this repo. Configure here. |
Package ArtifactsBuilt from 20f08c4. Download artifacts from this workflow run. JS SDK ( npm install ./e2b-2.19.2-mishushakov-network-transform-callback.0.tgzCLI ( npm install ./e2b-cli-2.10.1-mishushakov-network-transform-callback.0.tgzPython SDK ( pip install ./e2b-2.20.1+mishushakov.network.transform.callback-py3-none-any.whl |
Summary
transformcallback variant forSandboxNetworkRule(JS + Python): in addition to a static{ headers: ... }object,transformcan be a function receiving a typedSandboxNetworkTransformContextwhose fields (sandboxId,teamId,executionId,identity.jwt/sandbox_id,team_id,execution_id,identity.jwt) are literal placeholder strings (${e2b.sandboxId}, etc.). The callback is invoked at sandbox-creation time; the resolved object — with placeholders intact — goes on the wire, and the egress proxy substitutes them per request.SandboxNetworkTransformContextfrom both SDKs.transformshape. Implemented as a straight revert of the stripping commit so the diff is small and the two PRs land cleanly in order.transform callback resolves sandboxId placeholderin JS,test_transform_callback_resolves_sandbox_idin Python sync + async) are restored but left commented out / disabled until the proxy-side placeholder resolution ships.Example — inject the sandbox's identity JWT (resolved per request)
Notes
${e2b.*}strings.${e2b.sandboxId},${e2b.teamId},${e2b.executionId},${e2b.identity.jwt}substitution before the disabled tests can be re-enabled.Test plan
transform callback resolves sandboxId placeholder(JS) andtest_transform_callback_resolves_sandbox_id(Python sync + async) and verify end-to-end againsthttpbin.e2b.team