Sandbox SDK
One TypeScript API for agent sandboxes. Files, commands, ports, snapshots, and a typed escape hatch, across every major provider.
- 8
- providers
- gated
- capabilities
- MIT
- license
01Why
Every sandbox provider exposes a slightly different API for the same handful of primitives. @sandbox-sdk/core exposes the slice that's the same everywhere (files, processes, ports, snapshots) behind one small contract, and gets out of the way for anything provider-specific.
- One small API across providers. Swap E2B for Daytona without rewriting your agent loop.
- Web-standards I/O. Accepts
string,Uint8Array,Blob,ArrayBuffer, or aReadableStream. Runs on Node, Bun, Workers. Anywhere the fetch primitives run. - Capabilities, not surprises. Each adapter declares what it supports. Branch on
supports(sandbox, "snapshotCreate")andsupports(sandbox, "snapshotDelete")andsupports(sandbox, "snapshotRestore")instead of discovering it the hard way in production. - Escape hatch via
sandbox.raw. The native client is always one property away, typed per adapter, for anything outside the unified surface. - Predictable errors. A single
SandboxErrorwith a normalizedcodeacross providers, and the original error attached ascause.
02Installation
Install @sandbox-sdk/core together with the adapter you want to run against. Each provider ships as its own package, so apps only install what they use.
03Quick start
Call withSandbox with an adapter to get a typed Sandbox that is stopped automatically after success or failure. The adapter is fixed at construction; there is no runtime { provider, ... } form, which keeps call sites flat and lets the raw property stay narrowly typed.
04Adapters
Each adapter ships as its own package. Bring only what you use; the others stay out of your node_modules. Pass credentials and provider settings explicitly when you want deterministic behavior; otherwise the wrapped provider SDK can use its own defaults.
The list below covers what ships today. More providers are planned; the capability matrix at the bottom of the page is the authoritative source for what's supported right now.
Local
Local filesystem and child process. The dev/test adapter: point it at a directory and it implements the same Sandbox contract as the cloud adapters using node:fs/promises and node:child_process. It is not an isolation boundary for untrusted code. Every sandbox path is mapped below the root, and existing symlinks are rejected when they resolve outside it. Ports return localhost URLs, and filesystem snapshots support create, delete, and restore in the same process.
Blaxel
Blaxel perpetual sandboxes via @blaxel/core. The adapter maps Blaxel files, process execution, background processes, and preview URLs onto the shared Sandbox SDK surface.
Pass apiKey and workspace explicitly for deterministic auth, or let the Blaxel SDK use BL_API_KEY, BL_CLIENT_CREDENTIALS, BL_WORKSPACE, or the Blaxel CLI config.
Treat native client session tokens as bearer credentials and create them on a trusted backend. Agent Drive is a private-preview workspace-wide feature, so drivePath and readOnly mounts are not authorization boundaries for untrusted sandbox code. Restrict access to the drive identity token and blfs when that boundary matters.
Cloudflare
Cloudflare via @cloudflare/sandbox. Backed by a Durable Object running a Linux container, so the adapter takes a binding from env rather than an API key, and all I/O stays on Cloudflare's network.
The Worker must export Sandbox from @cloudflare/sandbox and bind that Durable Object in wrangler.jsonc. The adapter uses Cloudflare's RPC transport and exposes ports through zero-config HTTPS tunnels. Set tunnel when one port needs a stable named tunnel, or use tunnels to map multiple ports to distinct labels in your Cloudflare zone. For Worker-managed WebSocket upgrades, use the typed native sandbox.raw.wsConnect(request, port) escape hatch.
Set backups to opt into normalized filesystem snapshots through Cloudflare R2 backups. Add a BACKUP_BUCKET binding and, in production, the R2 presigned URL credentials to the Worker. Snapshot creation uses the adapter cwd and names are persisted in Cloudflare backup metadata. Restore writes back to that cwd. Production restores are copy-on-write mounts, so restore again after a sandbox sleeps or restarts. Configure an R2 lifecycle rule because backup TTL limits restoration but does not delete stored objects. Snapshot deletion and fresh sandbox creation from a backup stay unavailable through the normalized API.
For Node apps and other non-Worker runtimes, deploy Cloudflare's HTTP bridge and use cloudflareBridge(). It keeps normalized files, command execution, and HTTPS tunnel previews available over HTTP.ports.expose() creates a zero-config quick tunnel by default; set tunnel for one named port or tunnels for per-port labels when the bridge Worker has the required Cloudflare account and zone credentials. Bridge working directories stay below /workspace, so relative values resolve there, custom directories are created, and external paths fail before a bridge request. Bridge lifecycle, sessions, persist, hydrate, bucket mounts, warm-pool controls, health, OpenAPI schema access, and raw tunnel controls stay typed on sandbox.raw. PTY support returns a typed WebSocket connection descriptor so your app can own the terminal client and start a long-running service before calling ports.expose(). The bridge HTTP API does not expose a lifecycle-safe background process endpoint, so process.spawn() stays unavailable. SANDBOX_API_KEY authenticates the bridge and is rejected from sandbox environment configuration.
CodeSandbox
CodeSandbox microVMs via @codesandbox/sdk. The adapter creates or resumes sandboxes, connects a session, and normalizes files, commands, background commands, and opened ports.
Use template to fork from a template sandbox, or snapshot to fork from a snapshot created with snapshots.create(). Use id on create() to resume an existing sandbox. The hibernated id names the source sandbox, so normalized snapshot deletion stays unsupported.
Daytona
Daytona dev environments via @daytona/sdk. The adapter spins up a workspace from the given image, mounts a workdir, and threads files and processes through Daytona's API.
Use the shared snapshot create option, or the adapter snapshot default, to start from a Daytona snapshot reference. Network limits are configured at creation time in the normalized adapter. Durable Daytona snapshots can be removed through snapshots.delete() when the API key has Daytona's delete:snapshots permission; snapshot creation stays on the native client until the source lifecycle has a matching stable contract. Daytona's native raw.updateNetworkSettings() is still available when the account tier supports runtime network changes.
Standard private previews work through preview.request(), which retains Daytona's preview token. Set signedPreviewonly when an external client needs a self-contained URL. Standard tokens reset after a sandbox restart, so expose the port again after restarting.
E2B
E2B's microVM sandboxes via e2b. The adapter can pin a template at construction and threads writes, commands, ports, and snapshots through the E2B SDK.
Use the shared snapshot create option to start a fresh E2B sandbox from a snapshot id. E2B snapshots capture filesystem and memory state, briefly pausing the source sandbox and dropping active command, PTY, and WebSocket connections. Use template for provider template ids and names. Named snapshots return E2B's persisted canonical name, which can include a namespace and tag. Call snapshots.delete() after the snapshot is no longer needed.
When E2B restricts preview traffic, call preview.request() after ports.expose(). It retains the traffic access header without returning it in serializable data.
Modal
Modal sandboxes via modal. The adapter creates sandboxes inside a Modal app, maps file reads and writes through Modal's sandbox filesystem, and exposes provider-declared ports through Modal tunnels. Reconnecting by sandbox id discovers existing tunnels automatically.
Modal supports filesystem snapshot creation and deletion. In-place restore and background process handles stay unsupported in the normalized adapter until the provider exposes a matching stable primitive.
Vercel
Vercel via @vercel/sandbox. Backed by Vercel's Fluid Compute: named, persistent microVMs with snapshots, dynamic ports, network policy, sessions, interactive PTY connections, and provider-native lifecycle controls. The adapter normalizes files, commands, ports, and snapshots while keeping the full Vercel SDK on raw.
Use snapshot to start from a snapshot id, or snapshots.restore() to point the current named sandbox at a snapshot and resume from it on the next operation. Call snapshots.delete() to permanently remove a durable snapshot after its dependents have been created. Deleting the current snapshot of a stopped named sandbox makes the next getOrCreate() rebuild it.
Command timeouts are enforced inside the sandbox and backed by local cancellation. Use raw.openInteractive() when an application needs Vercel's native controller-backed PTY connection.
05API reference
Every method lives on the Sandbox instance returned by create(). The unified surface only covers what every adapter can do cleanly; anything provider-specific lives on sandbox.raw.
Top-level exports
These come from @sandbox-sdk/core. create and withSandbox produce the sandbox; the capability helpers let you branch on what a provider supports before calling into it.
create(options): creates or connects to a sandbox and returns a typedSandbox.withSandbox(options, fn): runsfnwith a sandbox and stops it after success or failure.supports(sandbox, capability): returnstruewhen a normalized capability is available.capabilityMode(sandbox, capability): returns the provider-specific mode for a capability, such as"disk"versus"memory"snapshots.supportsRaw(...)/rawCapabilityMode(...): the same checks for powers reached throughsandbox.raw.requireCapability(...)/requireRawCapability(...): throwSandboxErrorwithcode: "unsupported"when a capability is missing.SandboxError/isSandboxError(error): the normalized error class and a type guard for it.fromSandboxRuntime(runtime): for adapter authors, lifts a low-levelSandboxRuntimeinto the publicSandboxAPI, using direct bounded commands when available and stream-first process handles otherwise.
files.read(path) / files.text(path)
Reads a file. read returns raw Uint8Array bytes; text decodes as UTF-8. Both throw SandboxError when the path escapes the sandbox root.
files.stream(path)
Reads a file as ReadableStream<Uint8Array>. Every adapter returns a web stream. For large files, check capabilityMode(sandbox, "fileStreaming"): "native" receives bytes incrementally, while "buffered" exposes a stream after the provider SDK has loaded the file.
files.write(path, input)
Writes a file, creating parent directories as needed. Accepts string, Uint8Array, ArrayBuffer, Blob, or ReadableStream<Uint8Array>. Streams are drained before the write completes.
files.list(path?)
Lists the immediate children of path (defaults to the sandbox cwd). Returns a sorted, frozen array of Entry values. Each carries path, kind, plus size and modified where the adapter has them cheaply.
files.remove(path)
Removes a file or directory recursively when the provider supports it. Missing paths follow provider semantics, so catch SandboxError with code: "not_found" or code: "provider" when deleting optional paths.
process.shell(command, options?) / process.exec(command, args?, options?)
Runs a command to completion, buffering stdout and stderr. Use shell for normal shell strings and exec for explicit argv execution.
process.spawn(command, args?, options?)
Starts command and returns a handle immediately. Use output to stream merged stdout + stderr, optional stdout and stderr when the provider exposes separate streams, kill() to terminate, and result for the final { code, signal?, stdout, stderr }.
ports.expose(port, options?)
Returns a provider-aware preview for port. Its request() retains header-based access credentials outside serialized data. Use request() to call a same-origin endpoint. Local sandboxes return derived localhost URLs; provider adapters may return public tunnels, create-time port URLs, or reject unsupported exposure with SandboxError. Branch on sandbox.capabilities.ports first.
Treat a provider-issued signed or tokenized url as a credential. It is intentionally usable by an external client and may therefore contain provider access data.
Requests handle redirects manually by default and reject redirect: "follow", so provider access headers cannot leave the preview origin.
snapshots.create(name?) / snapshots.delete(id) / snapshots.restore(id)
Captures provider state when snapshotCreate is supported. Snapshots can be removed with delete() when snapshotDelete is supported. For persistent provider snapshots, deletion is permanent. Restore means in-place restore of the current sandbox and is tracked separately with snapshotRestore. To create a fresh sandbox from a snapshot, pass snapshot to create() on adapters that advertise snapshotSource. Snapshot names are accepted only when the provider persists them. Other adapters reject a name rather than silently discarding it.
06The Sandbox type
Sandbox is a frozen record of the five capability namespaces (files, process, ports, snapshots, raw) plus identifiers and a lifecycle hook. capabilities declares what the underlying provider can do through the normalized API. Provider-specific powers live under capabilities.raw and are available through sandbox.raw.
The Raw type parameter is set per-adapter (the local adapter sets it to { root: string }; cloud adapters set it to their native client) so sandbox.raw stays autocomplete-friendly without losing the unified shape. Import the capability types instead of copying their unions: the generated API reference stays the source of truth as the contract evolves.
07Errors
Normalized SDK methods throw SandboxError with a normalized code and the provider name on provider. When a provider failure is wrapped, the original provider error is attached as cause. Native calls through sandbox.raw keep native provider errors.
Codes
"unsupported": the adapter doesn't implement the method. Branch oncapabilitiesto avoid hitting this path."path_escape": a file orcwdpath resolved outside the sandbox root. Always thrown by the local adapter's safety check."not_found": referenced path or snapshot id does not exist."timeout":process.shellorprocess.exechitoptions.timeoutand the partial output is attached tocause."aborted": caller cancellation throughoptions.signal."provider": anything else. Inspectcausefor the underlying error.
08Escape hatch
When you need a feature outside the unified surface, like provider-specific networking, GPU attach, custom snapshots, or container internals, drop down to the native client. The raw property is typed per adapter so you keep autocomplete, while the rest of your agent loop stays portable.
09AI tools
@sandbox-sdk/ai wraps a configured sandbox into ready-made tools for agent frameworks. The kit includes prompt context plus file, command, directory, preview tools, and an AI SDK-compatible sandbox object with JSON-schema inputs. Pick the framework that matches your stack; each tool is just a thin shim around files, process, and ports on the underlying sandbox.
Requested operations the adapter cannot perform are omitted from the model-facing tool set. The AI SDK session only starts background commands when the provider exposes separate stdout and stderr streams, so it never presents combined output as standard output.
Installation
Quick start
Pass a configured sandbox into tools(). The return value is prompt context and a record of tool definitions, each with a description, inputSchema, and execute function. Use aisdk(), openai(), or claude() when you want the exact adapter shape for a framework.
Vercel AI SDK
The root package has no hard dependency on ai. aisdk() returns the shape used by Vercel AI SDK: system, tools, and experimental_sandbox. The same tool definitions work with v6 and v7 style calls, including generateText, streamText, and agent loops that forward the sandbox to tool execution. With AI Gateway, pass a provider/model string directly as model.
Use network(sandbox) when trusted host code also needs lifecycle, ports, or raw provider controls. The returned session is AI SDK-compatible, while restricted() returns a separate session without the host-owned backend.
OpenAI Agents SDK
Install @openai/agents and import @sandbox-sdk/ai/openai. The adapter uses the real OpenAI tool() helper, emits JSON-schema parameters, names tools with a sandbox_ prefix by default, and requires approval for side-effect tools unless you opt into autonomous execution.
Claude Agent SDK
Install @anthropic-ai/claude-agent-sdk and import @sandbox-sdk/ai/claude. Claude custom tools run through an in-process MCP server, so the adapter returns mcpServers, allowedTools, canUseTool, and prompt context for systemPrompt. Read-only tools are annotated as safe, while write, exec, and preview require approval by default.
Use { requireApproval: false } only for disposable sandboxes where the agent should run without a human approval loop.
Safety model
read and list are safe by default. write, exec, and preview are side-effect tools and must be enabled explicitly. OpenAI and Claude adapters expose approval controls at the framework layer, and every tool still runs through the policy hooks configured in tools().
10Capability matrix
Every adapter implements the same core capability surface, but providers differ on what they natively support. Branch on sandbox.capabilities at runtime, or read this table at design time. Hover the warning and error icons for the why behind each one.
| Capability | Local | Blaxel | Cloudflare | CodeSandbox | Daytona | E2B | Modal | Vercel |
|---|---|---|---|---|---|---|---|---|
| files | ||||||||
| file streaming | ||||||||
| process exec | ||||||||
| process spawn | ||||||||
| ports | ||||||||
| snapshot create | ||||||||
| snapshot delete | ||||||||
| snapshot restore | ||||||||
| snapshot source | ||||||||
| environment | ||||||||
| secrets |
SupportedSupported with caveatNot supported
11Verification
Every adapter is verified against the live provider, not just mocked. Sanitized fixtures give fast contract replay in bun test, and the verify:* scripts run the same suite against real sandboxes as the source of truth for provider behavior. The deterministic suite never loads .env.local, while live scripts load it explicitly and print readiness without leaking secret values.