ToolContext
API reference for the ToolContext type passed to custom tool execute functions.
Tools receive ToolContext fields in their execute callback. When using tool() from experimental-agent, fields are passed directly. When using tool() from ai, access them via experimental_context.
import type { ToolContext } from "experimental-agent";
type ToolContext<TContext = Record<string, unknown>, TState = Record<string, unknown>> = {
context: TContext;
state: TState;
sessionId: string;
sandbox: SandboxInstance;
messages: UIMessage[];
};Using tool() from experimental-agent (recommended)
tool() from experimental-agent gives you typed context and state automatically via schemas:
import { tool } from "experimental-agent";
import { z } from "zod";
const contextSchema = z.object({ authToken: z.string() });
const stateSchema = z.object({ requestCount: z.number().default(0) });
const MyTool = tool({
contextSchema,
stateSchema,
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }, { context, state, sandbox }) => {
context.authToken; // string — typed from contextSchema
state.requestCount++; // number — typed from stateSchema, mutable
// ...
},
});The execute callback receives ToolContext & { toolCallId, abortSignal }.
Using tool() from ai
When using tool() from the ai package, cast experimental_context to ToolContext:
import { tool } from "ai";
import type { ToolContext } from "experimental-agent";
import { z } from "zod";
const MyTool = tool({
description: "Does something useful",
parameters: z.object({
key: z.string(),
}),
execute: async (input, { experimental_context }) => {
const { sessionId, sandbox, context, state, messages } =
experimental_context as ToolContext<{ authToken: string }>;
// context.authToken is typed, state is Record<string, unknown>
return { result: "ok" };
},
});ToolContext fields
| Field | Type | Description |
|---|---|---|
context | TContext | Transient per-request data passed via session.send(input, { context }). Not persisted. Use for secrets. |
state | TState | Mutable per-session state. Persisted across messages. Mutate directly in tools. |
sessionId | string | Current session ID. |
sandbox | SandboxInstance | Sandbox instance for executing commands, reading/writing files. |
messages | UIMessage[] | Full conversation history for the session. |
Sandbox methods
The sandbox field is a SandboxInstance. Key methods:
sandbox.exec(opts)
Execute a shell command in the sandbox.
| Parameter | Type | Required | Description |
|---|---|---|---|
command | string | Yes | Command to run (e.g. "npm", "bash"). |
args? | string[] | No | Command arguments. |
signal? | AbortSignal | No | AbortSignal to cancel the command. |
Returns: Promise<ExecResult>
ExecResult has:
commandId— Use withkill()to terminate the process.result— Promise resolving to{ stdout, stderr, exitCode }.logs()— Async iterable of{ stream: "stdout" | "stderr"; data: string }.
const result = await sandbox.exec({ command: "npm", args: ["run", "test"] });
const { stdout, stderr, exitCode } = await result.result;sandbox.readFile(opts)
Read a file from the sandbox.
| Parameter | Type | Description |
|---|---|---|
path | string | Path relative to workspace root. |
Returns: Promise<Buffer | null> — File contents, or null if not found.
sandbox.writeFiles(opts)
Write files to the sandbox.
| Parameter | Type | Description |
|---|---|---|
files | UploadableFile[] | Array of { path, content } or { path, blob }. |
destPath | string | Destination directory. |
Returns: Promise<void>
sandbox.getDomain(port)
Get the public domain URL for an exposed port. Only available for Vercel sandboxes.
Returns: Promise<string>
sandbox.kill(opts)
Kill a running command.
| Parameter | Type | Description |
|---|---|---|
commandId | string | From ExecResult.commandId. |
Returns: Promise<void>
sandbox.updateNetworkPolicy(policy)
Dynamically update network policy. Only available for Vercel sandboxes.
| Policy | Description |
|---|---|
"allow-all" | Full network access. |
"deny-all" | No network access. |
{ allow: string[] } | Allow specific domains (e.g. ["github.com", "*.github.com"]). |
Returns: Promise<NetworkPolicy>
await sandbox.updateNetworkPolicy({ allow: ["github.com", "*.github.com"] });
await sandbox.updateNetworkPolicy("deny-all");
await sandbox.updateNetworkPolicy("allow-all");Example: Tool using state
import { tool } from "experimental-agent";
import { z } from "zod";
const stateSchema = z.object({
searchHistory: z.array(z.string()).default([]),
});
const Search = tool({
stateSchema,
inputSchema: z.object({ query: z.string() }),
execute: async ({ query }, { state, sandbox }) => {
state.searchHistory.push(query);
const result = await sandbox.exec({
command: "rg",
args: [query, "."],
});
const { stdout } = await result.result;
return { matches: stdout, totalSearches: state.searchHistory.length };
},
});Example: Tool using context for auth
import { tool } from "experimental-agent";
import { z } from "zod";
const contextSchema = z.object({ authToken: z.string().optional() });
const FetchRepo = tool({
contextSchema,
inputSchema: z.object({
repo: z.string().describe("GitHub repo in owner/name format"),
}),
execute: async ({ repo }, { sandbox, context }) => {
const token = context.authToken;
const url = token
? `https://x-access-token:${token}@github.com/${repo}.git`
: `https://github.com/${repo}.git`;
const result = await sandbox.exec({
command: "git",
args: ["clone", url, "/app/repo"],
});
const { stdout, stderr, exitCode } = await result.result;
return { success: exitCode === 0, stdout, stderr };
},
});See also
- agent() — Registering tools and defining schemas
- agent() — Session State — Mutable state overview
- Session API — Sending context via
session.send() - Custom Tools — Full guide with tool patterns
- Sessions — Context and state