Skip to content

Commit a8a7afd

Browse files
Dx 2740 (#718)
* feat: add crabbox to box docs * feat: add ai-sdk code interpreter guide * chore(llms): regenerate llms.txt and llms-full.txt --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 73552db commit a8a7afd

4 files changed

Lines changed: 384 additions & 1 deletion

File tree

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
---
2+
title: "Code Interpreter with Vercel AI SDK"
3+
---
4+
5+
In this guide we'll add a **code interpreter** tool to a Vercel AI SDK chat app. When a user asks a question that needs computation — math, data analysis, statistics — the model writes code and sends it to a fresh `EphemeralBox` to run. The sandbox is isolated, disposable, and auto-expires when the session ends.
6+
7+
---
8+
9+
## 1. Installation
10+
11+
```bash
12+
npm install @upstash/box @ai-sdk/anthropic @ai-sdk/react ai zod
13+
```
14+
15+
Get a Box API key from the [Upstash Console](https://console.upstash.com/box) and add your environment variables:
16+
17+
```bash title=".env.local"
18+
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
19+
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx
20+
```
21+
22+
---
23+
24+
## 2. Create the API route
25+
26+
Each time the model decides to run code, the tool spins up a fresh `EphemeralBox`, executes the snippet, and deletes the box immediately after. Nothing persists between tool calls.
27+
28+
```typescript title="app/api/chat/route.ts"
29+
import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
30+
import { anthropic } from "@ai-sdk/anthropic";
31+
import { EphemeralBox } from "@upstash/box";
32+
import { z } from "zod";
33+
34+
export async function POST(req: Request) {
35+
const { messages } = await req.json();
36+
37+
const result = streamText({
38+
model: anthropic("claude-sonnet-4-6"),
39+
system:
40+
"You are a helpful assistant with access to a secure code sandbox. " +
41+
"When the user asks for computation, data analysis, or math — write and run code " +
42+
"instead of estimating. Prefer Python for numerical work, JavaScript for JSON or string processing.",
43+
messages: await convertToModelMessages(messages),
44+
stopWhen: stepCountIs(10),
45+
tools: {
46+
executeSandboxCode: tool({
47+
description:
48+
"Run Python or JavaScript code in a secure, isolated sandbox. " +
49+
"Use this for any math, data processing, or computation.",
50+
inputSchema: z.object({
51+
lang: z.enum(["python", "js"]).describe("Language to run"),
52+
code: z.string().describe("The code to execute"),
53+
}),
54+
execute: async ({ lang, code, env }) => {
55+
const box = await EphemeralBox.create({
56+
apiKey: process.env.UPSTASH_BOX_API_KEY,
57+
runtime: lang === "python" ? "python" : "node",
58+
ttl: 120,
59+
});
60+
61+
try {
62+
const run = await box.exec.code({ lang, code, timeout: 10_000 });
63+
return {
64+
success: run.exitCode === 0,
65+
output: run.result,
66+
};
67+
} finally {
68+
await box.delete();
69+
}
70+
},
71+
}),
72+
},
73+
});
74+
75+
return result.toUIMessageStreamResponse();
76+
}
77+
```
78+
79+
<Note>
80+
`ttl: 120` means the box auto-deletes after 2 minutes even if the `finally` block is skipped. For longer-running scripts, increase this value.
81+
</Note>
82+
83+
---
84+
85+
## 3. Add a simple UI
86+
87+
Wire up a simple chat UI with `useChat` from the AI SDK. This UI also will display tool calls so that we can test the functionality.
88+
89+
```typescript title="app/page.tsx"
90+
"use client";
91+
92+
import { useState } from "react";
93+
import { useChat } from "@ai-sdk/react";
94+
95+
export default function Page() {
96+
const { messages, sendMessage, status } = useChat();
97+
const [input, setInput] = useState("");
98+
99+
function handleSubmit(e: React.FormEvent) {
100+
e.preventDefault();
101+
if (!input.trim()) return;
102+
sendMessage({ text: input });
103+
setInput("");
104+
}
105+
106+
return (
107+
<div className="mx-auto flex h-screen max-w-2xl flex-col p-4">
108+
<h1 className="mb-4 text-lg font-semibold">Code Interpreter</h1>
109+
110+
<div className="flex-1 space-y-4 overflow-y-auto">
111+
{messages.map((message) => (
112+
<div key={message.id}>
113+
<div className="text-xs font-medium text-gray-500">
114+
{message.role === "user" ? "You" : "Assistant"}
115+
</div>
116+
{message.parts.map((part, i) => {
117+
if (part.type === "text") {
118+
return (
119+
<p key={i} className="whitespace-pre-wrap text-sm">
120+
{part.text}
121+
</p>
122+
);
123+
}
124+
if (part.type.startsWith("tool-")) {
125+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
126+
const p = part as any;
127+
const toolName = part.type.slice(5);
128+
const isDone = p.state === "output-available";
129+
return (
130+
<div
131+
key={i}
132+
className="my-1 rounded border border-gray-200 bg-gray-50 p-2 text-xs"
133+
>
134+
<code>{toolName}</code>{" "}
135+
<span className={isDone ? "text-green-600" : "text-gray-400"}>
136+
{isDone ? "" : "running…"}
137+
</span>
138+
{isDone && p.output && (
139+
<pre className="mt-1 overflow-x-auto">
140+
{String(p.output.output)}
141+
</pre>
142+
)}
143+
</div>
144+
);
145+
}
146+
return null;
147+
})}
148+
</div>
149+
))}
150+
</div>
151+
152+
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
153+
<input
154+
value={input}
155+
onChange={(e) => setInput(e.target.value)}
156+
placeholder="Ask me to compute something..."
157+
disabled={status === "streaming"}
158+
className="flex-1 rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-gray-400"
159+
/>
160+
<button
161+
type="submit"
162+
disabled={status === "streaming"}
163+
className="rounded bg-black px-4 py-2 text-sm text-white disabled:opacity-40"
164+
>
165+
Send
166+
</button>
167+
</form>
168+
</div>
169+
);
170+
}
171+
```
172+
173+
---
174+
175+
## 4. Try it
176+
177+
Start your Next.js app and ask anything that needs real computation:
178+
179+
> *"What is the square root of 144 plus 25 factorial?"*
180+
181+
The model writes a Python snippet, the `executeSandboxCode` tool fires, a fresh `EphemeralBox` boots, the code runs, and the result streams back — all within a single response turn.
182+
183+
```
184+
executeSandboxCode ✓
185+
186+
Square root of 144: 12.0
187+
25 factorial: 15511210043330985984000000
188+
Sum: 1.5511210043330986e+25
189+
```
190+
191+
Every tool call gets its own isolated box, so a crash in one never affects the others. The `timeout: 10_000` on `exec.code` cuts off the HTTP call after 10 seconds — without it, an infinite loop would hang until the backend times out or the `ttl` deletes the box. Raise the timeout for long-running scripts, but always set one.

docs.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1656,7 +1656,7 @@
16561656
},
16571657
{
16581658
"group": "Guides",
1659-
"pages": ["box/guides/remote-development", "box/guides/code-review-agent", "box/guides/openclaw-setup", "box/guides/hermes-setup", "box/guides/crabbox-setup"]
1659+
"pages": ["box/guides/remote-development", "box/guides/code-review-agent", "box/guides/ai-sdk-code-interpreter", "box/guides/openclaw-setup", "box/guides/hermes-setup", "box/guides/crabbox-setup"]
16601660
}
16611661
]
16621662
},

llms-full.txt

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -703,6 +703,197 @@ Source: https://upstash.com/docs/api-reference/vector/get-vector-stats
703703
/devops/developer-api/openapi.yml get /vector/index/stats
704704
Get vector statistics for all the vector indices associated with the authenticated user
705705

706+
# Code Interpreter with Vercel AI SDK
707+
Source: https://upstash.com/docs/box/guides/ai-sdk-code-interpreter
708+
709+
In this guide we'll add a **code interpreter** tool to a Vercel AI SDK chat app. When a user asks a question that needs computation — math, data analysis, statistics — the model writes code and sends it to a fresh `EphemeralBox` to run. The sandbox is isolated, disposable, and auto-expires when the session ends.
710+
711+
***
712+
713+
## 1. Installation
714+
715+
```bash
716+
npm install @upstash/box @ai-sdk/anthropic @ai-sdk/react ai zod
717+
```
718+
719+
Get a Box API key from the [Upstash Console](https://console.upstash.com/box) and add your environment variables:
720+
721+
```bash title=".env.local"
722+
UPSTASH_BOX_API_KEY=box_xxxxxxxxxxxxxxxxxxxxxxxx
723+
ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxxxxx
724+
```
725+
726+
***
727+
728+
## 2. Create the API route
729+
730+
Each time the model decides to run code, the tool spins up a fresh `EphemeralBox`, executes the snippet, and deletes the box immediately after. Nothing persists between tool calls.
731+
732+
```typescript title="app/api/chat/route.ts"
733+
import { streamText, tool, convertToModelMessages, stepCountIs } from "ai";
734+
import { anthropic } from "@ai-sdk/anthropic";
735+
import { EphemeralBox } from "@upstash/box";
736+
import { z } from "zod";
737+
738+
export async function POST(req: Request) {
739+
const { messages } = await req.json();
740+
741+
const result = streamText({
742+
model: anthropic("claude-sonnet-4-6"),
743+
system:
744+
"You are a helpful assistant with access to a secure code sandbox. " +
745+
"When the user asks for computation, data analysis, or math — write and run code " +
746+
"instead of estimating. Prefer Python for numerical work, JavaScript for JSON or string processing.",
747+
messages: await convertToModelMessages(messages),
748+
stopWhen: stepCountIs(10),
749+
tools: {
750+
executeSandboxCode: tool({
751+
description:
752+
"Run Python or JavaScript code in a secure, isolated sandbox. " +
753+
"Use this for any math, data processing, or computation.",
754+
inputSchema: z.object({
755+
lang: z.enum(["python", "js"]).describe("Language to run"),
756+
code: z.string().describe("The code to execute"),
757+
}),
758+
execute: async ({ lang, code, env }) => {
759+
const box = await EphemeralBox.create({
760+
apiKey: process.env.UPSTASH_BOX_API_KEY,
761+
runtime: lang === "python" ? "python" : "node",
762+
ttl: 120,
763+
});
764+
765+
try {
766+
const run = await box.exec.code({ lang, code, timeout: 10_000 });
767+
return {
768+
success: run.exitCode === 0,
769+
output: run.result,
770+
};
771+
} finally {
772+
await box.delete();
773+
}
774+
},
775+
}),
776+
},
777+
});
778+
779+
return result.toUIMessageStreamResponse();
780+
}
781+
```
782+
783+
<Note>
784+
`ttl: 120` means the box auto-deletes after 2 minutes even if the `finally` block is skipped. For longer-running scripts, increase this value.
785+
</Note>
786+
787+
***
788+
789+
## 3. Add a simple UI
790+
791+
Wire up a simple chat UI with `useChat` from the AI SDK. This UI also will display tool calls so that we can test the functionality.
792+
793+
```typescript title="app/page.tsx"
794+
"use client";
795+
796+
import { useState } from "react";
797+
import { useChat } from "@ai-sdk/react";
798+
799+
export default function Page() {
800+
const { messages, sendMessage, status } = useChat();
801+
const [input, setInput] = useState("");
802+
803+
function handleSubmit(e: React.FormEvent) {
804+
e.preventDefault();
805+
if (!input.trim()) return;
806+
sendMessage({ text: input });
807+
setInput("");
808+
}
809+
810+
return (
811+
<div className="mx-auto flex h-screen max-w-2xl flex-col p-4">
812+
<h1 className="mb-4 text-lg font-semibold">Code Interpreter</h1>
813+
814+
<div className="flex-1 space-y-4 overflow-y-auto">
815+
{messages.map((message) => (
816+
<div key={message.id}>
817+
<div className="text-xs font-medium text-gray-500">
818+
{message.role === "user" ? "You" : "Assistant"}
819+
</div>
820+
{message.parts.map((part, i) => {
821+
if (part.type === "text") {
822+
return (
823+
<p key={i} className="whitespace-pre-wrap text-sm">
824+
{part.text}
825+
</p>
826+
);
827+
}
828+
if (part.type.startsWith("tool-")) {
829+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
830+
const p = part as any;
831+
const toolName = part.type.slice(5);
832+
const isDone = p.state === "output-available";
833+
return (
834+
<div
835+
key={i}
836+
className="my-1 rounded border border-gray-200 bg-gray-50 p-2 text-xs"
837+
>
838+
<code>{toolName}</code>{" "}
839+
<span className={isDone ? "text-green-600" : "text-gray-400"}>
840+
{isDone ? "✓" : "running…"}
841+
</span>
842+
{isDone && p.output && (
843+
<pre className="mt-1 overflow-x-auto">
844+
{String(p.output.output)}
845+
</pre>
846+
)}
847+
</div>
848+
);
849+
}
850+
return null;
851+
})}
852+
</div>
853+
))}
854+
</div>
855+
856+
<form onSubmit={handleSubmit} className="mt-4 flex gap-2">
857+
<input
858+
value={input}
859+
onChange={(e) => setInput(e.target.value)}
860+
placeholder="Ask me to compute something..."
861+
disabled={status === "streaming"}
862+
className="flex-1 rounded border border-gray-300 px-3 py-2 text-sm focus:outline-none focus:ring-1 focus:ring-gray-400"
863+
/>
864+
<button
865+
type="submit"
866+
disabled={status === "streaming"}
867+
className="rounded bg-black px-4 py-2 text-sm text-white disabled:opacity-40"
868+
>
869+
Send
870+
</button>
871+
</form>
872+
</div>
873+
);
874+
}
875+
```
876+
877+
***
878+
879+
## 4. Try it
880+
881+
Start your Next.js app and ask anything that needs real computation:
882+
883+
> *"What is the square root of 144 plus 25 factorial?"*
884+
885+
The model writes a Python snippet, the `executeSandboxCode` tool fires, a fresh `EphemeralBox` boots, the code runs, and the result streams back — all within a single response turn.
886+
887+
```
888+
executeSandboxCode ✓
889+
890+
Square root of 144: 12.0
891+
25 factorial: 15511210043330985984000000
892+
Sum: 1.5511210043330986e+25
893+
```
894+
895+
Every tool call gets its own isolated box, so a crash in one never affects the others. The `timeout: 10_000` on `exec.code` cuts off the HTTP call after 10 seconds — without it, an infinite loop would hang until the backend times out or the `ttl` deletes the box. Raise the timeout for long-running scripts, but always set one.
896+
706897
# Build a Code Review Agent
707898
Source: https://upstash.com/docs/box/guides/code-review-agent
708899

0 commit comments

Comments
 (0)