Adding Tools
Deep dive into tool patterns — inline tools, createTool, composition, and advanced usage.
Inline vs createTool
Inline — for simple, one-off tools:
<Tool
name="greet"
description="Greet someone"
input={z.object({ name: z.string() })}
handler={({ name }) => `Hello, ${name}!`}
/>createTool — for reusable tools with static access:
const GreetTool = createTool({
name: "greet",
description: "Greet someone",
input: z.object({ name: z.string() }),
handler: ({ name }) => `Hello, ${name}!`,
});
// Use as component
<GreetTool />;
// Call programmatically
await GreetTool.run({ name: "Ryan" });Use createTool when you need to reuse the tool across agents or call it programmatically. Use inline <Tool> for agent-specific tools that won't be shared.
Tool Composition
Tools are components — they compose like any React component:
function DataTools({ database }: { database: Database }) {
return (
<>
<Tool
name="query"
description="Run a SQL query"
input={z.object({ sql: z.string() })}
handler={({ sql }) => database.query(sql)}
/>
<Tool
name="schema"
description="Get table schema"
input={z.object({ table: z.string() })}
handler={({ table }) => database.schema(table)}
/>
</>
);
}
// Use in any agent
<DataTools database={productionDb} />;Async Handlers
Handlers can be async. The execution waits for the promise to resolve before proceeding to the next tick.
const FetchTool = createTool({
name: "fetch_url",
description: "Fetch content from a URL",
input: z.object({ url: z.string().url() }),
handler: async ({ url }) => {
const res = await fetch(url);
if (!res.ok) return `Error: ${res.status}`;
return await res.text();
},
});Context Object Model (ctx)
The second argument to handlers is the ctx — the Context Object Model:
handler: async ({ query }, ctx) => {
// Set session state
ctx?.setState("lastQuery", query);
// Emit an event
ctx?.emit("search:started", { query });
// Read state
const count = ctx?.getState("searchCount") ?? 0;
ctx?.setState("searchCount", count + 1);
return results;
},Context Injection with use()
Tools defined at module scope can't access React Context directly in their handlers. The use() hook solves this — it runs at render time, captures values from the tree, and passes them to the handler as deps.
The problem: You have a <SandboxProvider> that provides a sandbox via React Context. A tool needs that sandbox, but its handler runs outside the component tree.
The solution: use() bridges the gap.
const ShellTool = createTool({
name: "shell",
description: "Run a shell command",
input: z.object({ command: z.string() }),
use: () => ({ sandbox: useSandbox() }),
handler: async ({ command }, deps) => {
const result = await deps!.sandbox.exec(command);
return [{ type: "text", text: result.stdout }];
},
});The deps parameter is { ctx, sandbox } when rendered in JSX, undefined when called via .run(). This makes it safe — direct calls still work, they just don't get tree context.
Multiple providers: Two tool instances under different providers capture different values:
<SandboxProvider sandbox={localSandbox}>
<ShellTool /> {/* gets localSandbox */}
</SandboxProvider>
<SandboxProvider sandbox={dockerSandbox}>
<ShellTool /> {/* gets dockerSandbox */}
</SandboxProvider>When to use use(): When your tool handler needs something from the component tree — a provider value, a custom hook result, a context-scoped service. If the tool only needs COM state, plain ctx is sufficient.
User-Audience Tools
audience: "user" is a visibility flag — the tool is hidden from the model but still registered for programmatic invocation.
const ResetTool = createTool({
name: "reset-state",
description: "Reset the agent's working state",
input: z.object({}),
audience: "user",
aliases: ["reset"],
handler: async (_, ctx) => {
ctx?.setState("buffer", []);
return [{ type: "text", text: "State reset" }];
},
});session.dispatch(name, input) invokes any tool by name — it works on both regular and user-audience tools. User-audience tools can only be reached this way since the model can't see them. Use aliases for alternative dispatch names.
Error Handling
Throw from handlers to return tool errors to the model:
handler: async ({ id }) => {
const item = await db.find(id);
if (!item) throw new Error(`Item ${id} not found`);
return item;
},The error message is sent back to the model as the tool result. The model can then decide how to proceed.