feat(cloudflare): add React frontend and improve deployment docs

- Add React + Vite frontend for Cloudflare example with sandbox-agent SDK
- Update ensureRunning to poll health endpoint instead of fixed wait
- Fix SDK fetch binding issue (globalThis.fetch.bind)
- Update docs with .dev.vars format warning and container caching tip
- Use containerFetch proxy pattern for reliable local dev
This commit is contained in:
Nathan Flurry 2026-02-03 02:10:45 -08:00
parent 44382d2c12
commit 3576b7fcca
12 changed files with 619 additions and 425 deletions

View file

@ -22,10 +22,69 @@ npm create cloudflare@latest -- my-sandbox --template=cloudflare/sandbox-sdk/exa
cd my-sandbox
```
## Dockerfile
Create a `Dockerfile` with sandbox-agent and agents pre-installed:
```dockerfile
FROM cloudflare/sandbox:0.7.0
# Install sandbox-agent
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh
# Pre-install agents
RUN sandbox-agent install-agent claude && \
sandbox-agent install-agent codex
# Required for local development with wrangler dev
EXPOSE 8000
```
<Note>
The `EXPOSE 8000` directive is required for `wrangler dev` to proxy requests to the container. Port 3000 is reserved for the Cloudflare control plane.
</Note>
## Wrangler Configuration
Update `wrangler.jsonc` to use your Dockerfile:
```jsonc
{
"name": "my-sandbox-agent",
"main": "src/index.ts",
"compatibility_date": "2025-01-01",
"compatibility_flags": ["nodejs_compat"],
"containers": [
{
"class_name": "Sandbox",
"image": "./Dockerfile",
"instance_type": "lite",
"max_instances": 1
}
],
"durable_objects": {
"bindings": [
{
"class_name": "Sandbox",
"name": "Sandbox"
}
]
},
"migrations": [
{
"new_sqlite_classes": ["Sandbox"],
"tag": "v1"
}
]
}
```
## TypeScript Example
This example proxies requests to sandbox-agent via `containerFetch`, which works reliably in both local development and production:
```typescript
import { getSandbox, proxyToSandbox, type Sandbox } from "@cloudflare/sandbox";
import { getSandbox, type Sandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
type Env = {
@ -34,62 +93,60 @@ type Env = {
OPENAI_API_KEY?: string;
};
/** Check if sandbox-agent is already running by probing its health endpoint */
const PORT = 8000;
/** Check if sandbox-agent is already running */
async function isServerRunning(sandbox: Sandbox): Promise<boolean> {
try {
const result = await sandbox.exec("curl -sf http://localhost:8000/v1/health");
const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v1/health`);
return result.success;
} catch {
return false;
}
}
/** Ensure sandbox-agent is running in the container */
async function ensureRunning(sandbox: Sandbox, env: Env): Promise<void> {
if (await isServerRunning(sandbox)) return;
// Set environment variables for agents
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);
// Start sandbox-agent server
await sandbox.startProcess(
`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`
);
// Poll health endpoint until server is ready
for (let i = 0; i < 30; i++) {
if (await isServerRunning(sandbox)) return;
await new Promise((r) => setTimeout(r, 200));
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Proxy requests to exposed ports first
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
const url = new URL(request.url);
const { hostname } = new URL(request.url);
const sandbox = getSandbox(env.Sandbox, "sandbox-agent");
// Proxy requests: /sandbox/:name/v1/...
const match = url.pathname.match(/^\/sandbox\/([^/]+)(\/.*)?$/);
if (match) {
const [, name, path = "/"] = match;
const sandbox = getSandbox(env.Sandbox, name);
// Check if server is already running to avoid port conflicts
const alreadyRunning = await isServerRunning(sandbox);
await ensureRunning(sandbox, env);
if (!alreadyRunning) {
// Install sandbox-agent
await sandbox.exec(
"curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"
// Proxy request to container
return sandbox.containerFetch(
new Request(`http://localhost${path}${url.search}`, request),
PORT
);
// Install agents
await sandbox.exec("sandbox-agent install-agent claude");
await sandbox.exec("sandbox-agent install-agent codex");
// Set environment variables for agents
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);
// Start sandbox-agent server as background process
await sandbox.startProcess(
"sandbox-agent server --no-token --host 0.0.0.0 --port 8000"
);
// Wait for server to start
await new Promise((r) => setTimeout(r, 2000));
}
// Expose the port with a preview URL
const exposed = await sandbox.exposePort(8000, { hostname });
return Response.json({
endpoint: exposed.url,
message: alreadyRunning
? "sandbox-agent server was already running"
: "sandbox-agent server started",
});
return new Response("Not found", { status: 404 });
},
};
```
@ -99,11 +156,10 @@ export default {
```typescript
import { SandboxAgent } from "sandbox-agent";
// Get the endpoint from the Worker
const { endpoint } = await fetch("http://localhost:8787").then((r) => r.json());
// Connect to sandbox-agent
const client = await SandboxAgent.connect({ baseUrl: endpoint });
// Connect via the proxy endpoint
const client = await SandboxAgent.connect({
baseUrl: "http://localhost:8787/sandbox/my-sandbox",
});
// Wait for server to be ready
for (let i = 0; i < 30; i++) {
@ -116,42 +172,49 @@ for (let i = 0; i < 30; i++) {
}
// Create a session and start coding
await client.createSession("my-session", {
agent: "claude",
permissionMode: "default",
});
await client.createSession("my-session", { agent: "claude" });
await client.postMessage("my-session", {
message: "Summarize this repository",
});
for await (const event of client.streamEvents("my-session")) {
console.log(event.type, event.data);
}
```
// Auto-approve permissions
if (event.type === "permission.requested") {
await client.replyPermission("my-session", event.data.permission_id, {
reply: "once",
});
}
## Configuration
Update `wrangler.jsonc` to add environment variables:
```jsonc
{
"vars": {
"ANTHROPIC_API_KEY": "your-api-key"
// Handle text output
if (event.type === "item.delta" && event.data?.delta) {
process.stdout.write(event.data.delta);
}
}
```
Or use `.dev.vars` for local development:
## Environment Variables
Use `.dev.vars` for local development:
```bash
echo "ANTHROPIC_API_KEY=your-api-key" > .dev.vars
```
<Warning>
Use plain `KEY=value` format in `.dev.vars`. Do not use `export KEY=value` - wrangler won't parse the bash syntax.
</Warning>
<Note>
The `.dev.vars` file is automatically gitignored and only used during local development with `npm run dev`.
</Note>
For production, set secrets via wrangler:
```bash
wrangler secret put ANTHROPIC_API_KEY
```
## Local Development
Start the development server:
@ -167,40 +230,22 @@ First run builds the Docker container (2-3 minutes). Subsequent runs are much fa
Test with curl:
```bash
curl http://localhost:8787
curl http://localhost:8787/sandbox/demo/v1/health
```
<Tip>
Containers cache environment variables. If you change `.dev.vars`, either use a new sandbox name or clear existing containers:
```bash
docker ps -a | grep sandbox | awk '{print $1}' | xargs -r docker rm -f
```
</Tip>
## Production Deployment
For production, preview URLs require a custom domain with wildcard DNS routing.
Deploy to Cloudflare:
See [Cloudflare Production Deployment](https://developers.cloudflare.com/sandbox/guides/production-deployment/) for setup instructions.
## Faster Cold Starts
To speed up cold starts, you can:
1. Create a custom Dockerfile with sandbox-agent pre-installed
2. Pre-install agents in the Docker image
Example `Dockerfile`:
```dockerfile
FROM docker.io/cloudflare/sandbox:0.7.0
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh && \
sandbox-agent install-agent claude && \
sandbox-agent install-agent codex
```bash
wrangler deploy
```
Then update `wrangler.jsonc`:
```jsonc
{
"containers": {
"sandbox": {
"image": "./Dockerfile"
}
}
}
```
For production with preview URLs (direct container access), you'll need a custom domain with wildcard DNS routing. See [Cloudflare Production Deployment](https://developers.cloudflare.com/sandbox/guides/production-deployment/) for setup instructions.

View file

@ -49,9 +49,9 @@
"deploy/index",
"deploy/local",
"deploy/e2b",
"deploy/daytona",
"deploy/vercel",
"deploy/cloudflare",
"deploy/daytona",
"deploy/docker"
]
},

View file

@ -0,0 +1,11 @@
FROM cloudflare/sandbox:0.7.0
# Install sandbox-agent
RUN curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh
# Pre-install agents
RUN sandbox-agent install-agent claude && \
sandbox-agent install-agent codex
# Expose port for local dev (wrangler dev requires EXPOSE directives)
EXPOSE 8000

View file

@ -0,0 +1,272 @@
import { useState, useRef, useEffect, useCallback } from "react";
import { SandboxAgent } from "sandbox-agent";
import type { PermissionEventData, QuestionEventData } from "sandbox-agent";
export function App() {
const [sandboxName, setSandboxName] = useState("demo");
const [prompt, setPrompt] = useState("");
const [output, setOutput] = useState("");
const [status, setStatus] = useState<"idle" | "connecting" | "ready" | "thinking">("idle");
const [error, setError] = useState<string | null>(null);
const clientRef = useRef<SandboxAgent | null>(null);
const sessionIdRef = useRef<string>(`session-${Date.now()}`);
const abortRef = useRef<AbortController | null>(null);
const isThinkingRef = useRef(false);
const log = useCallback((msg: string) => {
setOutput((prev) => prev + msg + "\n");
}, []);
const connect = useCallback(async () => {
setStatus("connecting");
setError(null);
setOutput("");
try {
// Connect via proxy endpoint (need full URL for SDK)
const baseUrl = `${window.location.origin}/sandbox/${encodeURIComponent(sandboxName)}`;
log(`Connecting to sandbox: ${sandboxName}`);
const client = await SandboxAgent.connect({ baseUrl });
clientRef.current = client;
// Wait for health (this also ensures the container is started)
log("Waiting for sandbox-agent to be ready...");
for (let i = 0; i < 30; i++) {
try {
await client.getHealth();
break;
} catch {
if (i === 29) throw new Error("Timeout waiting for sandbox-agent");
await new Promise((r) => setTimeout(r, 1000));
}
}
// Create session
await client.createSession(sessionIdRef.current, { agent: "claude" });
log("Session created. Ready to chat.\n");
setStatus("ready");
// Start listening for events
startEventStream(client);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
setStatus("idle");
}
}, [sandboxName, log]);
const startEventStream = useCallback(
async (client: SandboxAgent) => {
abortRef.current?.abort();
const controller = new AbortController();
abortRef.current = controller;
try {
for await (const event of client.streamEvents(sessionIdRef.current, undefined, controller.signal)) {
console.log("Event:", event.type, event.data);
// Auto-approve permissions
if (event.type === "permission.requested") {
const data = event.data as PermissionEventData;
log(`[Auto-approved] ${data.action}`);
await client.replyPermission(sessionIdRef.current, data.permission_id, { reply: "once" });
}
// Reject questions (don't support interactive input)
if (event.type === "question.requested") {
const data = event.data as QuestionEventData;
log(`[Question rejected] ${data.prompt}`);
await client.rejectQuestion(sessionIdRef.current, data.question_id);
}
// Track when assistant starts thinking
if (event.type === "item.started") {
const item = (event.data as any)?.item;
if (item?.role === "assistant") {
isThinkingRef.current = true;
}
}
// Show deltas while assistant is thinking
if (event.type === "item.delta" && isThinkingRef.current) {
const delta = (event.data as any)?.delta;
if (delta) {
const text = typeof delta === "string" ? delta : delta.type === "text" ? delta.text || "" : "";
if (text) {
setOutput((prev) => prev + text);
}
}
}
// Track assistant turn completion
if (event.type === "item.completed") {
const item = (event.data as any)?.item;
if (item?.role === "assistant") {
isThinkingRef.current = false;
setOutput((prev) => prev + "\n\n");
setStatus("ready");
}
}
// Handle errors
if (event.type === "error") {
const data = event.data as any;
log(`Error: ${data?.message || JSON.stringify(data)}`);
}
// Handle session end
if (event.type === "session.ended") {
const data = event.data as any;
log(`Session ended: ${data?.reason || "unknown"}`);
setStatus("idle");
}
}
} catch (err) {
if (controller.signal.aborted) return;
console.error("Event stream error:", err);
}
},
[log]
);
const send = useCallback(async () => {
if (!clientRef.current || !prompt.trim() || status !== "ready") return;
const message = prompt.trim();
setPrompt("");
setOutput((prev) => prev + `user: ${message}\n\nassistant: `);
setStatus("thinking");
try {
await clientRef.current.postMessage(sessionIdRef.current, { message });
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
setStatus("ready");
}
}, [prompt, status]);
// Cleanup on unmount
useEffect(() => {
return () => {
abortRef.current?.abort();
};
}, []);
return (
<div style={styles.container}>
<h1 style={styles.title}>Sandbox Agent</h1>
{status === "idle" && (
<div style={styles.connectForm}>
<label style={styles.label}>
Sandbox name:
<input
style={styles.input}
value={sandboxName}
onChange={(e) => setSandboxName(e.target.value)}
placeholder="demo"
/>
</label>
<button style={styles.button} onClick={connect}>
Connect
</button>
</div>
)}
{status === "connecting" && <div style={styles.status}>Connecting to sandbox...</div>}
{error && <div style={styles.error}>{error}</div>}
{(status === "ready" || status === "thinking") && (
<>
<div style={styles.output}>{output}</div>
<div style={styles.inputRow}>
<input
style={styles.promptInput}
value={prompt}
onChange={(e) => setPrompt(e.target.value)}
onKeyDown={(e) => e.key === "Enter" && send()}
placeholder="Enter prompt..."
disabled={status === "thinking"}
/>
<button style={styles.button} onClick={send} disabled={status === "thinking"}>
{status === "thinking" ? "..." : "Send"}
</button>
</div>
</>
)}
</div>
);
}
const styles: Record<string, React.CSSProperties> = {
container: {
fontFamily: "system-ui, sans-serif",
maxWidth: 800,
margin: "2rem auto",
padding: "1rem",
},
title: {
marginBottom: "1rem",
},
connectForm: {
display: "flex",
gap: "1rem",
alignItems: "flex-end",
},
label: {
display: "flex",
flexDirection: "column",
gap: "0.25rem",
fontSize: "0.875rem",
color: "#666",
},
input: {
padding: "0.5rem",
fontSize: "1rem",
width: 200,
},
button: {
padding: "0.5rem 1rem",
fontSize: "1rem",
cursor: "pointer",
backgroundColor: "#0066cc",
color: "white",
border: "none",
borderRadius: 4,
},
status: {
color: "#666",
fontStyle: "italic",
},
error: {
color: "#cc0000",
padding: "0.5rem",
backgroundColor: "#fff0f0",
borderRadius: 4,
marginBottom: "1rem",
},
output: {
whiteSpace: "pre-wrap",
background: "#1e1e1e",
color: "#d4d4d4",
padding: "1rem",
minHeight: 300,
fontFamily: "monospace",
fontSize: 14,
overflow: "auto",
borderRadius: 4,
},
inputRow: {
display: "flex",
gap: "0.5rem",
marginTop: "1rem",
},
promptInput: {
flex: 1,
padding: "0.5rem",
fontSize: "1rem",
},
};

View file

@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Sandbox Agent</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/main.tsx"></script>
</body>
</html>

View file

@ -0,0 +1,9 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { App } from "./App";
createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
</StrictMode>
);

View file

@ -3,17 +3,25 @@
"private": true,
"type": "module",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy",
"dev": "vite build --watch & wrangler dev",
"build": "vite build",
"deploy": "vite build && wrangler deploy",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@cloudflare/sandbox": "latest"
"@cloudflare/sandbox": "latest",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"sandbox-agent": "workspace:*"
},
"devDependencies": {
"@cloudflare/workers-types": "latest",
"@types/node": "latest",
"@types/react": "^19.1.0",
"@types/react-dom": "^19.1.0",
"@vitejs/plugin-react": "^4.5.0",
"typescript": "latest",
"vite": "^6.2.0",
"vitest": "^3.0.0",
"wrangler": "latest"
}

View file

@ -1,69 +1,76 @@
import { getSandbox, proxyToSandbox, type Sandbox } from "@cloudflare/sandbox";
import { getSandbox, type Sandbox } from "@cloudflare/sandbox";
export { Sandbox } from "@cloudflare/sandbox";
type Env = {
Sandbox: DurableObjectNamespace<Sandbox>;
ANTHROPIC_API_KEY?: string;
OPENAI_API_KEY?: string;
Bindings: {
Sandbox: DurableObjectNamespace<Sandbox>;
ASSETS: Fetcher;
ANTHROPIC_API_KEY?: string;
OPENAI_API_KEY?: string;
};
};
const PORT = 8000;
/** Check if sandbox-agent is already running by probing its health endpoint */
async function isServerRunning(sandbox: Sandbox): Promise<boolean> {
try {
const result = await sandbox.exec("curl -sf http://localhost:8000/v1/health");
const result = await sandbox.exec(`curl -sf http://localhost:${PORT}/v1/health`);
return result.success;
} catch {
return false;
}
}
/** Ensure sandbox-agent is running in the container */
async function ensureRunning(sandbox: Sandbox, env: Env["Bindings"]): Promise<void> {
if (await isServerRunning(sandbox)) return;
// Set environment variables for agents
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);
// Start sandbox-agent server as background process
await sandbox.startProcess(`sandbox-agent server --no-token --host 0.0.0.0 --port ${PORT}`);
// Poll health endpoint until server is ready (max ~6 seconds)
for (let i = 0; i < 30; i++) {
if (await isServerRunning(sandbox)) return;
await new Promise((r) => setTimeout(r, 200));
}
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
// Proxy requests to exposed ports first
const proxyResponse = await proxyToSandbox(request, env);
if (proxyResponse) return proxyResponse;
async fetch(request: Request, env: Env["Bindings"]): Promise<Response> {
const url = new URL(request.url);
const { hostname } = new URL(request.url);
const sandbox = getSandbox(env.Sandbox, "sandbox-agent");
// Proxy requests to sandbox-agent: /sandbox/:name/v1/...
const match = url.pathname.match(/^\/sandbox\/([^/]+)(\/.*)?$/);
if (match) {
if (!env.ANTHROPIC_API_KEY && !env.OPENAI_API_KEY) {
return Response.json(
{ error: "ANTHROPIC_API_KEY or OPENAI_API_KEY must be set" },
{ status: 500 }
);
}
// Check if server is already running to avoid port conflicts
const alreadyRunning = await isServerRunning(sandbox);
const name = match[1];
const path = match[2] || "/";
const sandbox = getSandbox(env.Sandbox, name);
if (!alreadyRunning) {
console.log("Installing sandbox-agent...");
await sandbox.exec(
"curl -fsSL https://releases.rivet.dev/sandbox-agent/latest/install.sh | sh"
await ensureRunning(sandbox, env);
// Proxy request to container
return sandbox.containerFetch(
new Request(`http://localhost${path}${url.search}`, request),
PORT
);
console.log("Installing agents...");
await sandbox.exec("sandbox-agent install-agent claude");
await sandbox.exec("sandbox-agent install-agent codex");
// Set environment variables for agents
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);
console.log("Starting sandbox-agent server...");
await sandbox.startProcess(
"sandbox-agent server --no-token --host 0.0.0.0 --port 8000"
);
// Wait for server to start
await new Promise((r) => setTimeout(r, 2000));
}
// Expose the port with a preview URL
const exposed = await sandbox.exposePort(8000, { hostname });
console.log("Server accessible at:", exposed.url);
return Response.json({
endpoint: exposed.url,
message: alreadyRunning
? "sandbox-agent server was already running"
: "sandbox-agent server started",
});
// Serve frontend assets
return env.ASSETS.fetch(request);
},
};
} satisfies ExportedHandler<Env["Bindings"]>;

View file

@ -0,0 +1,11 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
export default defineConfig({
plugins: [react()],
root: "frontend",
build: {
outDir: "../dist",
emptyOutDir: true,
},
});

View file

@ -4,10 +4,14 @@
"main": "src/cloudflare.ts",
"compatibility_date": "2025-01-01",
"compatibility_flags": ["nodejs_compat"],
"assets": {
"directory": "./dist",
"binding": "ASSETS"
},
"containers": [
{
"class_name": "Sandbox",
"image": "docker.io/cloudflare/sandbox:0.7.0",
"image": "./Dockerfile",
"instance_type": "lite",
"max_instances": 1
}

375
pnpm-lock.yaml generated
View file

@ -13,13 +13,22 @@ importers:
version: 2.7.6
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
examples/cloudflare:
dependencies:
'@cloudflare/sandbox':
specifier: latest
version: 0.7.0
react:
specifier: ^19.1.0
version: 19.2.4
react-dom:
specifier: ^19.1.0
version: 19.2.4(react@19.2.4)
sandbox-agent:
specifier: workspace:*
version: link:../../sdks/typescript
devDependencies:
'@cloudflare/workers-types':
specifier: latest
@ -27,12 +36,24 @@ importers:
'@types/node':
specifier: latest
version: 25.2.0
'@types/react':
specifier: ^19.1.0
version: 19.2.10
'@types/react-dom':
specifier: ^19.1.0
version: 19.2.3(@types/react@19.2.10)
'@vitejs/plugin-react':
specifier: ^4.5.0
version: 4.7.0(vite@6.4.1(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2))
typescript:
specifier: latest
version: 5.9.3
vite:
specifier: ^6.2.0
version: 6.4.1(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
wrangler:
specifier: latest
version: 4.61.1(@cloudflare/workers-types@4.20260131.0)
@ -41,14 +62,14 @@ importers:
dependencies:
'@daytonaio/sdk':
specifier: latest
version: 0.135.0(ws@8.19.0)
version: 0.138.0(ws@8.19.0)
'@sandbox-agent/example-shared':
specifier: workspace:*
version: link:../shared
devDependencies:
'@types/node':
specifier: latest
version: 25.0.10
version: 25.2.0
tsx:
specifier: latest
version: 4.21.0
@ -70,7 +91,7 @@ importers:
version: 4.0.1
'@types/node':
specifier: latest
version: 25.0.10
version: 25.2.0
tsx:
specifier: latest
version: 4.21.0
@ -79,7 +100,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.10)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
examples/e2b:
dependencies:
@ -95,7 +116,7 @@ importers:
devDependencies:
'@types/node':
specifier: latest
version: 25.0.10
version: 25.2.0
tsx:
specifier: latest
version: 4.21.0
@ -104,7 +125,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.0.10)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
examples/shared:
dependencies:
@ -114,7 +135,7 @@ importers:
devDependencies:
'@types/node':
specifier: latest
version: 25.0.10
version: 25.2.0
typescript:
specifier: latest
version: 5.9.3
@ -133,7 +154,7 @@ importers:
devDependencies:
'@types/node':
specifier: latest
version: 25.1.0
version: 25.2.0
tsx:
specifier: latest
version: 4.21.0
@ -142,7 +163,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.1.0)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
frontend/packages/inspector:
dependencies:
@ -216,10 +237,10 @@ importers:
dependencies:
'@anthropic-ai/claude-code':
specifier: latest
version: 2.1.22
version: 2.1.29
'@openai/codex':
specifier: latest
version: 0.92.0
version: 0.94.0
cheerio:
specifier: ^1.0.0
version: 1.2.0
@ -294,14 +315,14 @@ importers:
dependencies:
'@daytonaio/sdk':
specifier: latest
version: 0.135.0(ws@8.19.0)
version: 0.138.0(ws@8.19.0)
'@e2b/code-interpreter':
specifier: latest
version: 2.3.3
devDependencies:
'@types/node':
specifier: latest
version: 25.0.10
version: 25.2.0
tsx:
specifier: latest
version: 4.21.0
@ -326,7 +347,7 @@ importers:
devDependencies:
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)
version: 3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
sdks/cli/platforms/darwin-arm64: {}
@ -352,7 +373,7 @@ importers:
version: 5.9.3
vitest:
specifier: ^3.0.0
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)
version: 3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
packages:
@ -360,8 +381,8 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'}
'@anthropic-ai/claude-code@2.1.22':
resolution: {integrity: sha512-WMwUUC/Ux87LqBDBC4KI/uYE0L/jcro3XcBzyd4a/YCkGVRyruyhypeeyHqAW7bUxm72xxWaPoy0keBkxpgIpQ==}
'@anthropic-ai/claude-code@2.1.29':
resolution: {integrity: sha512-vMHTOXrYdnreGtKUsWdd3Bwx5fKprTyNG7shrvbx3L2/jU9jexkOJrEKmN5loeR5jrE54LSB38QpaIj8pVM6eQ==}
engines: {node: '>=18.0.0'}
hasBin: true
@ -754,14 +775,14 @@ packages:
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
engines: {node: '>=12'}
'@daytonaio/api-client@0.135.0':
resolution: {integrity: sha512-7/gY3FimUXtgQvyUzEnxlr6ztET7G7bG0whdc4HRmeoWIZhNX+Fr2L2IpOH55UvFkb0CY7p60Ecubx2IltlMJA==}
'@daytonaio/api-client@0.138.0':
resolution: {integrity: sha512-mKO3Aqk2aCnOw4ej+UxvKE+Z1ixmo9OKTAFElkvRb6UOwb5zioudqTyqEfijkA2tXUXO8yPGhQDPaICLgpPopA==}
'@daytonaio/sdk@0.135.0':
resolution: {integrity: sha512-bJBBpLvFAfpcGlI2rg5xe2lKf0P5RWmt2OSL73mRyWJBQ1m2NRPijnmnE//sf62YoOOZpwL2Ykq3DkT8fczrew==}
'@daytonaio/sdk@0.138.0':
resolution: {integrity: sha512-cnbsflZYJ1NA4pQ2uX2lLN4w4ZQsO/xqdGDnpmwSu/LIW5F+O5gA8z4mfuWdIRcFFT4UhIpTzMuh3zRwxH7dIw==}
'@daytonaio/toolbox-api-client@0.135.0':
resolution: {integrity: sha512-XkaFm3nKF9PlOJi/qZS8pXpKjDn00X/lFvtpwUwCzIZSe0sC68uJZfp7/+DNgIVU1927kBOKgiyb6OMp4MCLDw==}
'@daytonaio/toolbox-api-client@0.138.0':
resolution: {integrity: sha512-unM9e7MOQiyDXdY8hCW1uTctYbxpo/TGZ6L71ZXyS/j2Cnz9/ud4VWBLcQP2VzlC+lrBP2YMrhT90zSSvcNfmA==}
'@e2b/code-interpreter@2.3.3':
resolution: {integrity: sha512-WOpSwc1WpvxyOijf6WMbR76BUuvd2O9ddXgCHHi65lkuy6YgQGq7oyd8PNsT331O9Tqbccjy6uF4xanSdLX1UA==}
@ -1667,8 +1688,8 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
'@openai/codex@0.92.0':
resolution: {integrity: sha512-DR9A2QlJDtEpMwqUGMIztTCzzCYTVrM7rqG3XuMVURnQ4b7XrScmY5RnSUuUZ/ga7wDTqw0BTmVzPurm4NX3Tw==}
'@openai/codex@0.94.0':
resolution: {integrity: sha512-GKOU2ty3NXls2aeiFSCnSSB6zQBtENqC5OnPa8s79Z576YP1r2DIfUrhQZzVDKmFei852E1SG4TNljFL/081gg==}
engines: {node: '>=16'}
hasBin: true
@ -2153,12 +2174,6 @@ packages:
'@types/node@24.10.9':
resolution: {integrity: sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==}
'@types/node@25.0.10':
resolution: {integrity: sha512-zWW5KPngR/yvakJgGOmZ5vTBemDoSqF3AcV/LrO5u5wTWyEAVVh+IT39G4gtyAkh3CtTZs8aX/yRM82OfzHJRg==}
'@types/node@25.1.0':
resolution: {integrity: sha512-t7frlewr6+cbx+9Ohpl0NOTKXZNV9xHRmNOvql47BFJKcEG1CxtxlPEEe+gR9uhVWM4DwhnvTF110mIL4yP9RA==}
'@types/node@25.2.0':
resolution: {integrity: sha512-DZ8VwRFUNzuqJ5khrvwMXHmvPe+zGayJhr2CDNiKB1WBE1ST8Djl00D0IC4vvNmHMdj6DlbYRIaFE7WHjlDl5w==}
@ -2467,10 +2482,6 @@ packages:
chownr@1.1.4:
resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==}
chownr@2.0.0:
resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==}
engines: {node: '>=10'}
chownr@3.0.0:
resolution: {integrity: sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==}
engines: {node: '>=18'}
@ -2868,10 +2879,6 @@ packages:
fs-constants@1.0.0:
resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==}
fs-minipass@2.1.0:
resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==}
engines: {node: '>= 8'}
fsevents@2.3.3:
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
@ -3332,22 +3339,10 @@ packages:
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
engines: {node: '>=16 || 14 >=14.17'}
minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
minipass@5.0.0:
resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==}
engines: {node: '>=8'}
minipass@7.1.2:
resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==}
engines: {node: '>=16 || 14 >=14.17'}
minizlib@2.1.2:
resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==}
engines: {node: '>= 8'}
minizlib@3.1.0:
resolution: {integrity: sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==}
engines: {node: '>= 18'}
@ -3355,11 +3350,6 @@ packages:
mkdirp-classic@0.5.3:
resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==}
mkdirp@1.0.4:
resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==}
engines: {node: '>=10'}
hasBin: true
mlly@1.8.0:
resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==}
@ -3923,11 +3913,6 @@ packages:
tar-stream@3.1.7:
resolution: {integrity: sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==}
tar@6.2.1:
resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==}
engines: {node: '>=10'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exhorbitant rates) by contacting i@izs.me
tar@7.5.6:
resolution: {integrity: sha512-xqUeu2JAIJpXyvskvU3uvQW8PAmHrtXp2KDuMJwQqW8Sqq0CaZBAQ+dKS3RBXVhU4wC5NjAdKrmh84241gO9cA==}
engines: {node: '>=18'}
@ -4447,9 +4432,6 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
yallist@4.0.0:
resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
yallist@5.0.0:
resolution: {integrity: sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==}
engines: {node: '>=18'}
@ -4509,7 +4491,7 @@ snapshots:
'@alloc/quick-lru@5.2.0': {}
'@anthropic-ai/claude-code@2.1.22':
'@anthropic-ai/claude-code@2.1.29':
optionalDependencies:
'@img/sharp-darwin-arm64': 0.33.5
'@img/sharp-darwin-x64': 0.33.5
@ -5307,18 +5289,18 @@ snapshots:
dependencies:
'@jridgewell/trace-mapping': 0.3.9
'@daytonaio/api-client@0.135.0':
'@daytonaio/api-client@0.138.0':
dependencies:
axios: 1.13.4
transitivePeerDependencies:
- debug
'@daytonaio/sdk@0.135.0(ws@8.19.0)':
'@daytonaio/sdk@0.138.0(ws@8.19.0)':
dependencies:
'@aws-sdk/client-s3': 3.975.0
'@aws-sdk/lib-storage': 3.975.0(@aws-sdk/client-s3@3.975.0)
'@daytonaio/api-client': 0.135.0
'@daytonaio/toolbox-api-client': 0.135.0
'@daytonaio/api-client': 0.138.0
'@daytonaio/toolbox-api-client': 0.138.0
'@iarna/toml': 2.2.5
axios: 1.13.4
busboy: 1.6.0
@ -5329,13 +5311,13 @@ snapshots:
isomorphic-ws: 5.0.0(ws@8.19.0)
pathe: 2.0.3
shell-quote: 1.8.3
tar: 6.2.1
tar: 7.5.6
transitivePeerDependencies:
- aws-crt
- debug
- ws
'@daytonaio/toolbox-api-client@0.135.0':
'@daytonaio/toolbox-api-client@0.138.0':
dependencies:
axios: 1.13.4
transitivePeerDependencies:
@ -5888,7 +5870,7 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.20.1
'@openai/codex@0.92.0': {}
'@openai/codex@0.94.0': {}
'@oslojs/encoding@1.1.0': {}
@ -6424,13 +6406,13 @@ snapshots:
'@types/docker-modem@3.0.6':
dependencies:
'@types/node': 24.10.9
'@types/node': 25.2.0
'@types/ssh2': 1.15.5
'@types/dockerode@4.0.1':
dependencies:
'@types/docker-modem': 3.0.6
'@types/node': 24.10.9
'@types/node': 25.2.0
'@types/ssh2': 1.15.5
'@types/estree@1.0.8': {}
@ -6463,14 +6445,6 @@ snapshots:
dependencies:
undici-types: 7.16.0
'@types/node@25.0.10':
dependencies:
undici-types: 7.16.0
'@types/node@25.1.0':
dependencies:
undici-types: 7.16.0
'@types/node@25.2.0':
dependencies:
undici-types: 7.16.0
@ -6553,30 +6527,6 @@ snapshots:
chai: 5.3.3
tinyrainbow: 2.0.0
'@vitest/mocker@3.2.4(vite@5.4.21(@types/node@22.19.7))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 5.4.21(@types/node@22.19.7)
'@vitest/mocker@3.2.4(vite@5.4.21(@types/node@25.0.10))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 5.4.21(@types/node@25.0.10)
'@vitest/mocker@3.2.4(vite@5.4.21(@types/node@25.1.0))':
dependencies:
'@vitest/spy': 3.2.4
estree-walker: 3.0.3
magic-string: 0.30.21
optionalDependencies:
vite: 5.4.21(@types/node@25.1.0)
'@vitest/mocker@3.2.4(vite@5.4.21(@types/node@25.2.0))':
dependencies:
'@vitest/spy': 3.2.4
@ -6936,8 +6886,6 @@ snapshots:
chownr@1.1.4: {}
chownr@2.0.0: {}
chownr@3.0.0: {}
ci-info@4.3.1: {}
@ -7400,10 +7348,6 @@ snapshots:
fs-constants@1.0.0: {}
fs-minipass@2.1.0:
dependencies:
minipass: 3.3.6
fsevents@2.3.3:
optional: true
@ -8079,27 +8023,14 @@ snapshots:
dependencies:
brace-expansion: 2.0.2
minipass@3.3.6:
dependencies:
yallist: 4.0.0
minipass@5.0.0: {}
minipass@7.1.2: {}
minizlib@2.1.2:
dependencies:
minipass: 3.3.6
yallist: 4.0.0
minizlib@3.1.0:
dependencies:
minipass: 7.1.2
mkdirp-classic@0.5.3: {}
mkdirp@1.0.4: {}
mlly@1.8.0:
dependencies:
acorn: 8.15.0
@ -8343,7 +8274,7 @@ snapshots:
'@protobufjs/path': 1.1.2
'@protobufjs/pool': 1.1.0
'@protobufjs/utf8': 1.1.0
'@types/node': 24.10.9
'@types/node': 25.2.0
long: 5.3.2
proxy-from-env@1.1.0: {}
@ -8779,15 +8710,6 @@ snapshots:
- bare-abort-controller
- react-native-b4a
tar@6.2.1:
dependencies:
chownr: 2.0.0
fs-minipass: 2.1.0
minipass: 5.0.0
minizlib: 2.1.2
mkdirp: 1.0.4
yallist: 4.0.0
tar@7.5.6:
dependencies:
'@isaacs/fs-minipass': 4.0.1
@ -9044,15 +8966,16 @@ snapshots:
'@types/unist': 3.0.3
vfile-message: 4.0.3
vite-node@3.2.4(@types/node@22.19.7):
vite-node@3.2.4(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 5.4.21(@types/node@22.19.7)
vite: 6.4.1(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
@ -9061,52 +8984,19 @@ snapshots:
- sugarss
- supports-color
- terser
- tsx
- yaml
vite-node@3.2.4(@types/node@25.0.10):
vite-node@3.2.4(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 5.4.21(@types/node@25.0.10)
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vite-node@3.2.4(@types/node@25.1.0):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 5.4.21(@types/node@25.1.0)
transitivePeerDependencies:
- '@types/node'
- less
- lightningcss
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vite-node@3.2.4(@types/node@25.2.0):
dependencies:
cac: 6.7.14
debug: 4.4.3
es-module-lexer: 1.7.0
pathe: 2.0.3
vite: 5.4.21(@types/node@25.2.0)
vite: 6.4.1(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- '@types/node'
- jiti
- less
- lightningcss
- sass
@ -9115,6 +9005,8 @@ snapshots:
- sugarss
- supports-color
- terser
- tsx
- yaml
vite@5.4.21(@types/node@22.19.7):
dependencies:
@ -9125,24 +9017,6 @@ snapshots:
'@types/node': 22.19.7
fsevents: 2.3.3
vite@5.4.21(@types/node@25.0.10):
dependencies:
esbuild: 0.21.5
postcss: 8.5.6
rollup: 4.56.0
optionalDependencies:
'@types/node': 25.0.10
fsevents: 2.3.3
vite@5.4.21(@types/node@25.1.0):
dependencies:
esbuild: 0.21.5
postcss: 8.5.6
rollup: 4.56.0
optionalDependencies:
'@types/node': 25.1.0
fsevents: 2.3.3
vite@5.4.21(@types/node@25.2.0):
dependencies:
esbuild: 0.21.5
@ -9152,6 +9026,21 @@ snapshots:
'@types/node': 25.2.0
fsevents: 2.3.3
vite@6.4.1(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.56.0
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 22.19.7
fsevents: 2.3.3
jiti: 1.21.7
tsx: 4.21.0
yaml: 2.8.2
vite@6.4.1(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
@ -9171,11 +9060,11 @@ snapshots:
optionalDependencies:
vite: 6.4.1(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.7):
vitest@3.2.4(@types/debug@4.1.12)(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@22.19.7))
'@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@25.2.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
@ -9194,12 +9083,13 @@ snapshots:
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 5.4.21(@types/node@22.19.7)
vite-node: 3.2.4(@types/node@22.19.7)
vite-node: 3.2.4(@types/node@22.19.7)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 22.19.7
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
@ -9209,86 +9099,10 @@ snapshots:
- sugarss
- supports-color
- terser
- tsx
- yaml
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.0.10):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@25.0.10))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
debug: 4.4.3
expect-type: 1.3.0
magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 5.4.21(@types/node@25.0.10)
vite-node: 3.2.4(@types/node@25.0.10)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 25.0.10
transitivePeerDependencies:
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.1.0):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
'@vitest/mocker': 3.2.4(vite@5.4.21(@types/node@25.1.0))
'@vitest/pretty-format': 3.2.4
'@vitest/runner': 3.2.4
'@vitest/snapshot': 3.2.4
'@vitest/spy': 3.2.4
'@vitest/utils': 3.2.4
chai: 5.3.3
debug: 4.4.3
expect-type: 1.3.0
magic-string: 0.30.21
pathe: 2.0.3
picomatch: 4.0.3
std-env: 3.10.0
tinybench: 2.9.0
tinyexec: 0.3.2
tinyglobby: 0.2.15
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 5.4.21(@types/node@25.1.0)
vite-node: 3.2.4(@types/node@25.1.0)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 25.1.0
transitivePeerDependencies:
- less
- lightningcss
- msw
- sass
- sass-embedded
- stylus
- sugarss
- supports-color
- terser
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.0):
vitest@3.2.4(@types/debug@4.1.12)(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
'@types/chai': 5.2.3
'@vitest/expect': 3.2.4
@ -9311,12 +9125,13 @@ snapshots:
tinypool: 1.1.1
tinyrainbow: 2.0.0
vite: 5.4.21(@types/node@25.2.0)
vite-node: 3.2.4(@types/node@25.2.0)
vite-node: 3.2.4(@types/node@25.2.0)(jiti@1.21.7)(tsx@4.21.0)(yaml@2.8.2)
why-is-node-running: 2.3.0
optionalDependencies:
'@types/debug': 4.1.12
'@types/node': 25.2.0
transitivePeerDependencies:
- jiti
- less
- lightningcss
- msw
@ -9326,6 +9141,8 @@ snapshots:
- sugarss
- supports-color
- terser
- tsx
- yaml
vscode-languageserver-textdocument@1.0.12: {}
@ -9417,8 +9234,6 @@ snapshots:
yallist@3.1.1: {}
yallist@4.0.0: {}
yallist@5.0.0: {}
yaml@2.8.2: {}

View file

@ -66,7 +66,7 @@ export class SandboxAgent {
private constructor(options: SandboxAgentConnectOptions) {
this.baseUrl = options.baseUrl.replace(/\/$/, "");
this.token = options.token;
this.fetcher = options.fetch ?? globalThis.fetch;
this.fetcher = options.fetch ?? globalThis.fetch.bind(globalThis);
this.defaultHeaders = options.headers;
if (!this.fetcher) {