Getting Started
Cloudflare
Prerequisites
Section titled “Prerequisites”- Cloudflare account with Workers paid plan
- Docker for local
wrangler dev ANTHROPIC_API_KEYorOPENAI_API_KEY
Quick start
Section titled “Quick start”npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimalcd my-sandboxDockerfile
Section titled “Dockerfile”FROM cloudflare/sandbox:0.7.0
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | shRUN sandbox-agent install-agent claude && sandbox-agent install-agent codex
EXPOSE 8000TypeScript example (with provider)
Section titled “TypeScript example (with provider)”For standalone scripts, use the cloudflare provider:
import { SandboxAgent } from "sandbox-agent";import { cloudflare } from "sandbox-agent/cloudflare";
const sdk = await SandboxAgent.start({ sandbox: cloudflare(),});
try { const session = await sdk.createSession({ agent: "codex" }); const response = await session.prompt([ { type: "text", text: "Summarize this repository" }, ]); console.log(response.stopReason);} finally { await sdk.destroySandbox();}The cloudflare provider uses containerFetch under the hood, automatically stripping AbortSignal to avoid dropped streaming updates.
TypeScript example (Durable Objects)
Section titled “TypeScript example (Durable Objects)”For Workers with Durable Objects, use SandboxAgent.connect(...) with a custom fetch backed by sandbox.containerFetch(...):
import { getSandbox, type Sandbox } from "@cloudflare/sandbox";import { Hono } from "hono";import { SandboxAgent } from "sandbox-agent";
export { Sandbox } from "@cloudflare/sandbox";
type Bindings = { Sandbox: DurableObjectNamespace<Sandbox>; ASSETS: Fetcher; ANTHROPIC_API_KEY?: string; OPENAI_API_KEY?: string;};
const app = new Hono<{ Bindings: Bindings }>();const PORT = 8000;
async function isServerRunning(sandbox: Sandbox): Promise<boolean> { try { const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v1/health`); return result.success; } catch { return false; }}
async function getReadySandbox(name: string, env: Bindings): Promise<Sandbox> { const sandbox = getSandbox(env.Sandbox, name); if (!(await isServerRunning(sandbox))) { const envVars: Record<string, string> = {}; if (env.ANTHROPIC_API_KEY) envVars.ANTHROPIC_API_KEY = env.ANTHROPIC_API_KEY; if (env.OPENAI_API_KEY) envVars.OPENAI_API_KEY = env.OPENAI_API_KEY; await sandbox.setEnvVars(envVars); await sandbox.startProcess(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`); } return sandbox;}
app.post("/sandbox/:name/prompt", async (c) => { const sandbox = await getReadySandbox(c.req.param("name"), c.env);
const sdk = await SandboxAgent.connect({ fetch: (input, init) => sandbox.containerFetch( input as Request | string | URL, { ...(init ?? {}), // Avoid passing AbortSignal through containerFetch; it can drop streamed session updates. signal: undefined, }, PORT, ), });
const session = await sdk.createSession({ agent: "codex" }); const response = await session.prompt([{ type: "text", text: "Summarize this repository" }]); await sdk.destroySession(session.id); await sdk.dispose();
return c.json(response);});
app.all("/sandbox/:name/proxy/*", async (c) => { const sandbox = await getReadySandbox(c.req.param("name"), c.env); const wildcard = c.req.param("*"); const path = wildcard ? `/${wildcard}` : "/"; const query = new URL(c.req.raw.url).search;
return sandbox.containerFetch(new Request(`http://localhost${path}${query}`, c.req.raw), PORT);});
app.all("*", (c) => c.env.ASSETS.fetch(c.req.raw));
export default app;This keeps all Sandbox Agent calls inside the Cloudflare sandbox routing path and does not require a baseUrl.
Troubleshooting streaming updates
Section titled “Troubleshooting streaming updates”If you only receive:
- the outbound prompt request
- the final
{ stopReason: "end_turn" }response
then the streamed update channel dropped. In Cloudflare sandbox paths, this is typically caused by forwarding AbortSignal from SDK fetch init into containerFetch(...).
Fix:
const sdk = await SandboxAgent.connect({ fetch: (input, init) => sandbox.containerFetch( input as Request | string | URL, { ...(init ?? {}), // Avoid passing AbortSignal through containerFetch; it can drop streamed session updates. signal: undefined, }, PORT, ),});This keeps prompt completion behavior the same, but restores streamed text/tool updates.
Local development
Section titled “Local development”npm run devTest health:
curl http://localhost:8787/sandbox/demo/proxy/v1/healthProduction deployment
Section titled “Production deployment”wrangler deploy