# Tool Use — TypeScript For conceptual overview (tool definitions, tool choice, tips), see [shared/tool-use-concepts.md](../../shared/tool-use-concepts.md). ## Tool Runner (Recommended) **Beta:** The tool runner is in beta in the TypeScript SDK. Use \`betaZodTool\` with Zod schemas to define tools with a \`run\` function, then pass them to \`client.beta.messages.toolRunner()\`: \`\`\`typescript import Anthropic from "@anthropic-ai/sdk"; import { betaZodTool } from "@anthropic-ai/sdk/helpers/beta/zod"; import { z } from "zod"; const client = new Anthropic(); const getWeather = betaZodTool({ name: "get_weather", description: "Get current weather for a location", inputSchema: z.object({ location: z.string().describe("City and state, e.g., San Francisco, CA"), unit: z.enum(["celsius", "fahrenheit"]).optional(), }), run: async (input) => { // Your implementation here return \`72°F and sunny in \${input.location}\`; }, }); // The tool runner handles the agentic loop and returns the final message const finalMessage = await client.beta.messages.toolRunner({ model: "{{OPUS_ID}}", max_tokens: 4096, tools: [getWeather], messages: [{ role: "user", content: "What's the weather in Paris?" }], }); console.log(finalMessage.content); \`\`\` **Key benefits of the tool runner:** - No manual loop — the SDK handles calling tools and feeding results back - Type-safe tool inputs via Zod schemas - Tool schemas are generated automatically from Zod definitions - Iteration stops automatically when Claude has no more tool calls --- ## Manual Agentic Loop Use this when you need fine-grained control (custom logging, conditional tool execution, streaming individual iterations, human-in-the-loop approval): \`\`\`typescript import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); const tools: Anthropic.Tool[] = [...]; // Your tool definitions let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }]; while (true) { const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 4096, tools: tools, messages: messages, }); if (response.stop_reason === "end_turn") break; // Server-side tool hit iteration limit; append assistant turn and re-send to continue if (response.stop_reason === "pause_turn") { messages.push({ role: "assistant", content: response.content }); continue; } const toolUseBlocks = response.content.filter( (b): b is Anthropic.ToolUseBlock => b.type === "tool_use", ); messages.push({ role: "assistant", content: response.content }); const toolResults: Anthropic.ToolResultBlockParam[] = []; for (const tool of toolUseBlocks) { const result = await executeTool(tool.name, tool.input); toolResults.push({ type: "tool_result", tool_use_id: tool.id, content: result, }); } messages.push({ role: "user", content: toolResults }); } \`\`\` ### Streaming Manual Loop Use \`client.messages.stream()\` + \`finalMessage()\` instead of \`.create()\` when you need streaming within a manual loop. Text deltas are streamed on each iteration; \`finalMessage()\` collects the complete \`Message\` so you can inspect \`stop_reason\` and extract tool-use blocks: \`\`\`typescript import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); const tools: Anthropic.Tool[] = [...]; let messages: Anthropic.MessageParam[] = [{ role: "user", content: userInput }]; while (true) { const stream = client.messages.stream({ model: "{{OPUS_ID}}", max_tokens: 4096, tools, messages, }); // Stream text deltas on each iteration stream.on("text", (delta) => { process.stdout.write(delta); }); // finalMessage() resolves with the complete Message — no need to // manually wire up .on("message") / .on("error") / .on("abort") const message = await stream.finalMessage(); if (message.stop_reason === "end_turn") break; // Server-side tool hit iteration limit; append assistant turn and re-send to continue if (message.stop_reason === "pause_turn") { messages.push({ role: "assistant", content: message.content }); continue; } const toolUseBlocks = message.content.filter( (b): b is Anthropic.ToolUseBlock => b.type === "tool_use", ); messages.push({ role: "assistant", content: message.content }); const toolResults: Anthropic.ToolResultBlockParam[] = []; for (const tool of toolUseBlocks) { const result = await executeTool(tool.name, tool.input); toolResults.push({ type: "tool_result", tool_use_id: tool.id, content: result, }); } messages.push({ role: "user", content: toolResults }); } \`\`\` > **Important:** Don't wrap \`.on()\` events in \`new Promise()\` to collect the final message — use \`stream.finalMessage()\` instead. The SDK handles all error/abort/completion states internally. > **Error handling in the loop:** Use the SDK's typed exceptions (e.g., \`Anthropic.RateLimitError\`, \`Anthropic.APIError\`) — see [Error Handling](./README.md#error-handling) for examples. Don't check error messages with string matching. > **SDK types:** Use \`Anthropic.MessageParam\`, \`Anthropic.Tool\`, \`Anthropic.ToolUseBlock\`, \`Anthropic.ToolResultBlockParam\`, \`Anthropic.Message\`, etc. for all API-related data structures. Don't redefine equivalent interfaces. --- ## Handling Tool Results \`\`\`typescript const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 1024, tools: tools, messages: [{ role: "user", content: "What's the weather in Paris?" }], }); for (const block of response.content) { if (block.type === "tool_use") { const result = await executeTool(block.name, block.input); const followup = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 1024, tools: tools, messages: [ { role: "user", content: "What's the weather in Paris?" }, { role: "assistant", content: response.content }, { role: "user", content: [ { type: "tool_result", tool_use_id: block.id, content: result }, ], }, ], }); } } \`\`\` --- ## Tool Choice \`\`\`typescript const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 1024, tools: tools, tool_choice: { type: "tool", name: "get_weather" }, messages: [{ role: "user", content: "What's the weather in Paris?" }], }); \`\`\` --- ## Server-Side Tools Version-suffixed \`type\` literals; \`name\` is fixed per interface. Pass plain object literals — the \`ToolUnion\` type is satisfied structurally. **The \`name\`/\`type\` pair must match the interface**: mixing \`str_replace_based_edit_tool\` (20250728 name) with \`text_editor_20250124\` (which expects \`str_replace_editor\`) is a TS2322. **Don't type-annotate as \`Tool[]\`** — \`Tool\` is just the custom-tool variant. Let structural typing infer from the \`tools\` param, or annotate as \`Anthropic.Messages.ToolUnion[]\` if you must: \`\`\`typescript // ✓ let inference work — no annotation const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 1024, tools: [ { type: "text_editor_20250728", name: "str_replace_based_edit_tool" }, { type: "bash_20250124", name: "bash" }, { type: "web_search_20260209", name: "web_search" }, { type: "code_execution_20260120", name: "code_execution" }, ], messages: [{ role: "user", content: "..." }], }); // ✗ this is a TS2352 — Tool is the CUSTOM tool variant only // const tools: Anthropic.Tool[] = [{ type: "text_editor_20250728", ... }] \`\`\` | Interface | \`name\` | \`type\` | |---|---|---| | \`ToolTextEditor20250124\` | \`str_replace_editor\` | \`text_editor_20250124\` | | \`ToolTextEditor20250429\` | \`str_replace_based_edit_tool\` | \`text_editor_20250429\` | | \`ToolTextEditor20250728\` | \`str_replace_based_edit_tool\` | \`text_editor_20250728\` | | \`ToolBash20250124\` | \`bash\` | \`bash_20250124\` | | \`WebSearchTool20260209\` | \`web_search\` | \`web_search_20260209\` | | \`WebFetchTool20260209\` | \`web_fetch\` | \`web_fetch_20260209\` | | \`CodeExecutionTool20260120\` | \`code_execution\` | \`code_execution_20260120\` | **Don't mix beta and non-beta types**: if you call \`client.beta.messages.create()\`, the response \`content\` is \`BetaContentBlock[]\` — you cannot pass that to a non-beta \`ContentBlockParam[]\` without narrowing each element. --- ## Code Execution ### Basic Usage \`\`\`typescript import Anthropic from "@anthropic-ai/sdk"; const client = new Anthropic(); const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 4096, messages: [ { role: "user", content: "Calculate the mean and standard deviation of [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]", }, ], tools: [{ type: "code_execution_20260120", name: "code_execution" }], }); \`\`\` ### Reading Local Files (ESM note) \`__dirname\` doesn't exist in ES modules. For script-relative paths use \`import.meta.url\`: \`\`\`typescript import { readFileSync } from "fs"; import { fileURLToPath } from "url"; import { dirname, join } from "path"; const __dirname = dirname(fileURLToPath(import.meta.url)); const pdfBytes = readFileSync(join(__dirname, "sample.pdf")); \`\`\` Or use a CWD-relative path if the script runs from a known directory: \`readFileSync("./sample.pdf")\`. ### Upload Files for Analysis \`\`\`typescript import Anthropic, { toFile } from "@anthropic-ai/sdk"; import { createReadStream } from "fs"; const client = new Anthropic(); // 1. Upload a file const uploaded = await client.beta.files.upload({ file: await toFile(createReadStream("sales_data.csv"), undefined, { type: "text/csv", }), betas: ["files-api-2025-04-14"], }); // 2. Pass to code execution // Code execution is GA; Files API is still beta (pass via RequestOptions) const response = await client.messages.create( { model: "{{OPUS_ID}}", max_tokens: 4096, messages: [ { role: "user", content: [ { type: "text", text: "Analyze this sales data. Show trends and create a visualization.", }, { type: "container_upload", file_id: uploaded.id }, ], }, ], tools: [{ type: "code_execution_20260120", name: "code_execution" }], }, { headers: { "anthropic-beta": "files-api-2025-04-14" } }, ); \`\`\` ### Retrieve Generated Files \`\`\`typescript import path from "path"; import fs from "fs"; const OUTPUT_DIR = "./claude_outputs"; await fs.promises.mkdir(OUTPUT_DIR, { recursive: true }); for (const block of response.content) { if (block.type === "bash_code_execution_tool_result") { const result = block.content; if (result.type === "bash_code_execution_result" && result.content) { for (const fileRef of result.content) { if (fileRef.type === "bash_code_execution_output") { const metadata = await client.beta.files.retrieveMetadata( fileRef.file_id, ); const downloadResponse = await client.beta.files.download(fileRef.file_id); const fileBytes = Buffer.from(await downloadResponse.arrayBuffer()); const safeName = path.basename(metadata.filename); if (!safeName || safeName === "." || safeName === "..") { console.warn(\`Skipping invalid filename: \${metadata.filename}\`); continue; } const outputPath = path.join(OUTPUT_DIR, safeName); await fs.promises.writeFile(outputPath, fileBytes); console.log(\`Saved: \${outputPath}\`); } } } } } \`\`\` ### Container Reuse \`\`\`typescript // First request: set up environment const response1 = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 4096, messages: [ { role: "user", content: "Install tabulate and create data.json with sample user data", }, ], tools: [{ type: "code_execution_20260120", name: "code_execution" }], }); // Reuse container // container is nullable — set only when using server-side code execution const containerId = response1.container!.id; const response2 = await client.messages.create({ container: containerId, model: "{{OPUS_ID}}", max_tokens: 4096, messages: [ { role: "user", content: "Read data.json and display as a formatted table", }, ], tools: [{ type: "code_execution_20260120", name: "code_execution" }], }); \`\`\` --- ## Memory Tool ### Basic Usage \`\`\`typescript const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 2048, messages: [ { role: "user", content: "Remember that my preferred language is TypeScript.", }, ], tools: [{ type: "memory_20250818", name: "memory" }], }); \`\`\` ### SDK Memory Helper Use \`betaMemoryTool\` with a \`MemoryToolHandlers\` implementation: \`\`\`typescript import { betaMemoryTool, type MemoryToolHandlers, } from "@anthropic-ai/sdk/helpers/beta/memory"; const handlers: MemoryToolHandlers = { async view(command) { ... }, async create(command) { ... }, async str_replace(command) { ... }, async insert(command) { ... }, async delete(command) { ... }, async rename(command) { ... }, }; const memory = betaMemoryTool(handlers); const runner = client.beta.messages.toolRunner({ model: "{{OPUS_ID}}", max_tokens: 2048, tools: [memory], messages: [{ role: "user", content: "Remember my preferences" }], }); for await (const message of runner) { console.log(message); } \`\`\` For full implementation examples, use WebFetch: - \`https://github.com/anthropics/anthropic-sdk-typescript/blob/main/examples/tools-helpers-memory.ts\` --- ## Structured Outputs ### JSON Outputs (Zod — Recommended) \`\`\`typescript import Anthropic from "@anthropic-ai/sdk"; import { z } from "zod"; import { zodOutputFormat } from "@anthropic-ai/sdk/helpers/zod"; const ContactInfoSchema = z.object({ name: z.string(), email: z.string(), plan: z.string(), interests: z.array(z.string()), demo_requested: z.boolean(), }); const client = new Anthropic(); const response = await client.messages.parse({ model: "{{OPUS_ID}}", max_tokens: 1024, messages: [ { role: "user", content: "Extract: Jane Doe (jane@co.com) wants Enterprise, interested in API and SDKs, wants a demo.", }, ], output_config: { format: zodOutputFormat(ContactInfoSchema), }, }); // parsed_output is null if parsing failed — assert or guard console.log(response.parsed_output!.name); // "Jane Doe" \`\`\` ### Strict Tool Use \`\`\`typescript const response = await client.messages.create({ model: "{{OPUS_ID}}", max_tokens: 1024, messages: [ { role: "user", content: "Book a flight to Tokyo for 2 passengers on March 15", }, ], tools: [ { name: "book_flight", description: "Book a flight to a destination", strict: true, input_schema: { type: "object", properties: { destination: { type: "string" }, date: { type: "string", format: "date" }, passengers: { type: "integer", enum: [1, 2, 3, 4, 5, 6, 7, 8], }, }, required: ["destination", "date", "passengers"], additionalProperties: false, }, }, ], }); \`\`\`