Give your agent a sandbox
Hand an AI SDK, Mastra, or OpenAI Agents SDK agent ready-made sandbox tools with sandboxTools, see what the model gets back, and limit what it can touch.
You want a model to run commands, read and edit files, and start a dev server, without writing tool definitions by hand. sandboxTools(box) returns ready tools in your framework’s own format.
Each framework has its own subpath, so you only load the one you use:
| Framework | Import | Returns | Page |
|---|---|---|---|
| AI SDK | @opencoredev/sandbox-sdk/ai |
A tool set for tools |
AI SDK |
| Mastra | @opencoredev/sandbox-sdk/mastra |
A tool map for new Agent |
Mastra |
| OpenAI Agents SDK | @opencoredev/sandbox-sdk/openai |
A tool array for new Agent |
OpenAI Agents |
The tools behave the same in every framework. This page uses the AI SDK. The framework pages show the same agent in Mastra and the OpenAI Agents SDK.
Hand the model the tools
npm install @opencoredev/sandbox-sdk ai zodpnpm add @opencoredev/sandbox-sdk ai zodyarn add @opencoredev/sandbox-sdk ai zodbun add @opencoredev/sandbox-sdk ai zodimport { generateText, stepCountIs } from "ai";
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";
import { sandboxTools } from "@opencoredev/sandbox-sdk/ai";
await using box = await createSandbox(e2b());
await box.git.clone("https://github.com/acme/web.git", { dir: "app", depth: 1 });
const { text } = await generateText({
model: "anthropic/claude-opus-5-5",
tools: sandboxTools(box, { root: "/workspace/app" }),
stopWhen: stepCountIs(30),
prompt: "Run the tests in /workspace/app and fix the first failure.",
});
console.log(text);
The model string goes through the Vercel AI Gateway, so set AI_GATEWAY_API_KEY on your server. Any AI SDK provider works in its place.
The agent calls run to execute the tests, read_file to look at the failure, and edit_file to change it. You get its final summary in text.
The tools
Seven tools are on by default. Each does one thing and has flat parameters, and each description tells the model when not to use it.
| Tool | Parameters | What it does |
|---|---|---|
run |
command, cwd?, timeoutMs? |
Runs a command and waits for it to exit |
read_file |
path, offset?, limit? |
Returns numbered lines and a hash of the file |
write_file |
path, content |
Writes a whole file |
edit_file |
path, find, replace |
Replaces text that appears exactly once |
start_process |
name, command, cwd?, port? |
Starts a background process, waits for it, returns its URL |
process_logs |
name, cursor? |
Reads a process’s output from a cursor |
stop_process |
name |
Stops a background process |
Four more are available when you ask for them: search, list_files, changes, and preview.
What the model sees
Tool results are short so they don’t fill the context window. Every run result opens with a verdict line, then the output:
FAILED exit=1 4.2s cwd=/workspace/app
output:
FAIL src/login.test.ts
expected 200, received 500
Output is stripped of color codes and capped at 16 KB. Past that, the model gets the start and the end with a [N bytes cut, use process_logs] marker in between. Errors follow the same “what happened, why, what to do next” format as SandboxError, and secrets never appear.
Limit what the agent can do
import { generateText, stepCountIs } from "ai";
import { createSandbox } from "@opencoredev/sandbox-sdk";
import { e2b } from "@opencoredev/sandbox-sdk/e2b";
import { sandboxTools } from "@opencoredev/sandbox-sdk/ai";
await using box = await createSandbox(e2b(), { network: { allow: ["registry.npmjs.org"] } });
const tools = sandboxTools(box, {
root: "/workspace/app",
allow: ["run", "read_file", "edit_file", "search"],
beforeRun: (command) => (command.includes("git push") ? false : command),
});
const { text } = await generateText({
model: "anthropic/claude-opus-5-5",
tools,
stopWhen: stepCountIs(20),
prompt: "Find why the build fails and fix it.",
});
console.log(text);
rootkeeps every file path and working directory the model passes inside one directory. It is not a security boundary: a shell command can stillcdout or use an absolute path, so rely on the sandbox andbeforeRunfor that.allowanddenypick which tools exist. Here the model can read, edit, search, and run, but can’t start processes.beforeRunsees eachrunandstart_processcommand before it runs. Returnfalseto refuse it, a string to run instead, ortrueto allow it.- Pair tools with a network policy so a command can’t reach hosts you didn’t allow.
The full option list, including per-tool limits, is in the agent tools reference.
Show full output in your UI
The model gets a compact result, but people often want the whole log. Pass onEvent to receive every chunk of output as it happens and forward it to your UI. With the AI SDK, the tools also stream preliminary results, so a useChat UI shows output while the command is still running. The AI app builder guide wires this up end to end.