TypeScript SDK for Upstash Box — create sandboxed AI coding agents with streaming, structured output, file I/O, git operations, and snapshots.
npm install @upstash/boximport { Box, ClaudeCode } from "@upstash/box";
const box = await Box.create({
runtime: "node",
agent: { model: ClaudeCode.Sonnet_4_5 },
});
const run = await box.agent.run({
prompt: "Create a hello world Express server",
});
console.log(run.result);
await box.delete();Pass apiKey in the config or set the UPSTASH_BOX_API_KEY environment variable.
Create a new sandboxed box.
import { Box, Agent, ClaudeCode, BoxApiKey } from "@upstash/box";
const box = await Box.create({
apiKey: "abx_...", // or set UPSTASH_BOX_API_KEY
runtime: "node", // "node" | "python" | "golang" | "ruby" | "rust"
agent: {
runner: Agent.ClaudeCode, // optional — inferred from model prefix
model: ClaudeCode.Sonnet_4_5,
apiKey: BoxApiKey.UpstashKey, // Upstash-managed key
// apiKey: BoxApiKey.StoredKey, // use a key stored via the Upstash console
// apiKey: process.env.CLAUDE_KEY!, // or pass a direct API key
},
git: { token: process.env.GITHUB_TOKEN! },
env: { NODE_ENV: "production" },
timeout: 600000,
debug: false,
});Reconnect to an existing box by ID.
const box = await Box.get("box_abc123");List all boxes for the authenticated user.
const boxes = await Box.list();Create a new box from a saved snapshot.
const box = await Box.fromSnapshot("snap_abc123", {
agent: { model: ClaudeCode.Sonnet_4_5 },
});Run the AI agent with a prompt. Supports streaming, structured output with Zod schemas, timeouts, retries, tool use callbacks, and webhooks.
// Structured output
import { z } from "zod";
const schema = z.object({
name: z.string(),
score: z.number(),
});
const run = await box.agent.run({
prompt: "Analyze this candidate",
responseSchema: schema,
});
const result = run.result; // typed as { name: string, score: number }
const stream = await box.agent.stream({
prompt: "Refactor the auth flow",
});
for await (const part of stream) {
if (part.type === "text-delta") process.stdout.write(part.text);
if (part.type === "tool-call") console.log(part.toolName, part.input);
if (part.type === "finish") console.log(part.usage.inputTokens + part.usage.outputTokens);
}Execute a shell command in the box.
const run = await box.exec.command("node index.js");
console.log(run.result);await box.files.write({ path: "hello.txt", content: "Hello!" });
const content = await box.files.read("hello.txt");
const entries = await box.files.list(".");
await box.files.upload([{ path: "./local.txt", destination: "remote.txt" }]);
await box.files.download({ folder: "output/" });await box.git.clone({ repo: "https://github.com/user/repo", branch: "main" });
const diff = await box.git.diff();
const status = await box.git.status();
await box.git.commit({ message: "feat: add feature" });
await box.git.push({ branch: "main" });
const pr = await box.git.createPR({ title: "New feature", body: "Description" });
// Run an arbitrary git command
const result = await box.git.exec({ args: ["log", "--oneline", "-5"] });
console.log(result.output);
// Switch branches
await box.git.checkout({ branch: "feature-branch" });box.cwd; // "/workspace/home" (default)
await box.cd("my-project");
box.cwd; // "/workspace/home/my-project"
// All operations now run relative to my-project/
const run = await box.exec.command("ls");
const files = await box.files.list();
const status = await box.git.status();
await box.cd(".."); // back to /workspace/home// Read the current runner and model
const { runner, model } = box.modelConfig;
// Change the model
await box.configureModel(ClaudeCode.Opus_4_5);
// modelConfig reflects the change immediately
box.modelConfig.model; // "claude/opus_4_5"await box.pause(); // Pause (preserves state)
await box.resume(); // Resume
await box.delete(); // Permanent delete
const { status } = await box.getStatus();const snapshot = await box.snapshot({ name: "checkpoint-1" });
const snapshots = await box.listSnapshots();
await box.deleteSnapshot(snapshot.id);Every agent.run() and exec.command() call returns a Run object. Streaming methods (agent.stream(), exec.stream()) return a StreamRun which extends Run and is async-iterable.
const run = await box.agent.run({ prompt: "..." });
run.id; // Run ID
run.result; // Final output (typed if schema provided)
run.status; // "running" | "completed" | "failed" | "cancelled" | "detached"
run.cost; // { inputTokens, outputTokens, computeMs, totalUsd }
await run.cancel(); // Abort
await run.logs(); // Filtered log entries
// Streaming returns a StreamRun — async-iterable with typed Chunk objects
const stream = await box.agent.stream({ prompt: "..." });
for await (const chunk of stream) {
if (chunk.type === "text-delta") process.stdout.write(chunk.text);
}
stream.status; // "completed" after iteration finishes
stream.result; // final outputThe runner field in agent config is optional — the SDK infers it from the model prefix. You can set it explicitly to use a specific runner.
| Enum | Value |
|---|---|
Agent.ClaudeCode |
claude-code |
Agent.Codex |
codex |
Agent.OpenCode |
opencode |
| Enum | Value |
|---|---|
ClaudeCode.Opus_4_5 |
claude/opus_4_5 |
ClaudeCode.Opus_4_6 |
claude/opus_4_6 |
ClaudeCode.Sonnet_4 |
claude/sonnet_4 |
ClaudeCode.Sonnet_4_5 |
claude/sonnet_4_5 |
ClaudeCode.Haiku_4_5 |
claude/haiku_4_5 |
| Enum | Value |
|---|---|
OpenAICodex.GPT_5_3_Codex |
openai/gpt-5.3-codex |
OpenAICodex.GPT_5_3_Codex_Spark |
openai/gpt-5.3-codex-spark |
OpenAICodex.GPT_5_2_Codex |
openai/gpt-5.2-codex |
OpenAICodex.GPT_5_1_Codex_Max |
openai/gpt-5.1-codex-max |
| Enum | Value |
|---|---|
OpenRouterModel.Claude_Opus_4_5 |
openrouter/anthropic/claude-opus-4-5 |
OpenRouterModel.Claude_Sonnet_4 |
openrouter/anthropic/claude-sonnet-4 |
OpenRouterModel.Claude_Haiku_4_5 |
openrouter/anthropic/claude-haiku-4-5 |
OpenRouterModel.DeepSeek_R1 |
openrouter/deepseek/deepseek-r1 |
OpenRouterModel.Gemini_2_5_Pro |
openrouter/google/gemini-2.5-pro |
OpenRouterModel.Gemini_2_5_Flash |
openrouter/google/gemini-2.5-flash |
OpenRouterModel.GPT_4_1 |
openrouter/openai/gpt-4.1 |
OpenRouterModel.O3 |
openrouter/openai/o3 |
OpenRouterModel.O4_Mini |
openrouter/openai/o4-mini |
Runtime is a string union type: "node" | "python" | "golang" | "ruby" | "rust"
See the examples/ directory for complete working examples:
basic.ts— Create a box, run an agent, read outputstreaming.ts— Parallel boxes with structured output (Zod)file-upload.ts— Upload local files into the boxgit-pr.ts— Clone a repo, make changes, create a PRsnapshot-restore.ts— Save and restore workspace statewebhook.ts— Fire-and-forget with webhook callbacksmulti-runtime.ts— Run across different runtimesmcp-skills.ts— Attach MCP servers to a box
MIT