--- title: "Cloudflare" description: "Deploy Sandbox Agent inside a Cloudflare Sandbox." --- ## Prerequisites - Cloudflare account with Workers paid plan - Docker for local `wrangler dev` - `ANTHROPIC_API_KEY` or `OPENAI_API_KEY` Cloudflare Sandbox SDK is beta. See [Sandbox SDK docs](https://developers.cloudflare.com/sandbox/). ## Quick start ```bash npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/examples/minimal cd my-sandbox ``` ## Dockerfile ```dockerfile FROM cloudflare/sandbox:0.7.0 RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/0.4.x/install.sh | sh RUN sandbox-agent install-agent claude && sandbox-agent install-agent codex EXPOSE 8000 ``` ## TypeScript example (with provider) For standalone scripts, use the `cloudflare` provider: ```bash npm install sandbox-agent@0.4.x @cloudflare/sandbox ``` ```typescript 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) For Workers with Durable Objects, use `SandboxAgent.connect(...)` with a custom `fetch` backed by `sandbox.containerFetch(...)`: ```typescript 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; 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 { 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 { const sandbox = getSandbox(env.Sandbox, name); if (!(await isServerRunning(sandbox))) { const envVars: Record = {}; 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 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: ```ts 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 ```bash npm run dev ``` Test health: ```bash curl http://localhost:8787/sandbox/demo/proxy/v1/health ``` ## Production deployment ```bash wrangler deploy ```