Fine-Grained Tool Streaming
Purpose and Scope
Fine-grained tool streaming is the low-latency form of Claude tool use where tool input fragments can be delivered while Claude is still generating them, rather than after server-side buffering and JSON validation. In practical SDK terms, this page focuses on how the TypeScript helper layer receives streamed tool-use data, maps a tool call to a local implementation, and sends a tool result back into the conversation or Managed Agent session. It is most relevant when a tool parameter may be large, such as generated code, a document, or a structured command payload that an application wants to inspect early.
The SDK does not make fine-grained streaming a separate runner class. Instead, it builds on the existing beta tool contracts and streaming message helpers. A runnable tool defines a model-visible tool shape plus parse and run functions, while a tool runner owns the conversation loop. For Messages, the beta tool runner can yield either a final BetaMessage or a BetaMessageStream depending on whether the request is streaming. For Managed Agents, the session tool runner watches session events and dispatches agent.tool_use and agent.custom_tool_use events to the same runnable tool shape. Sources: src/lib/tools/BetaRunnableTool.ts, src/lib/tools/BetaToolRunner.ts, src/lib/tools/SessionToolRunner.ts
Relevant Source Files
src/lib/tools/BetaRunnableTool.tsdefines the shared runnable-tool contract: client-runnable tool types, theBetaToolUseunion, theBetaToolRunContext,BetaRunnableTool<Input>,toolName,toolErrorContent, and the outcome shape used by runners.src/lib/tools/BetaToolRunner.tsimplements the newer beta Messages tool runner, including request-option handling, helper headers, streaming versus non-streaming iteration, cloned message state, iteration tracking, and deprecated compaction-control handling.src/lib/tools/SessionToolRunner.tsimplements the Managed Agents session-event tool runner, including the managed-agents beta header, local tool registry dispatch, event result pairing, retries, idle shutdown, abort behavior, and per-request options.src/lib/tools/ToolError.tsdefines the structured error type that a tool can throw when it wants the runner to return rich tool-result content withis_error: true.src/lib/tools/ToolRunner.tsshows the earlier tool-runner implementation and is useful for understanding the core async-iterable loop without the newer helper-header and request-options additions.src/resources/beta/agents/index.tsexports the generated Managed Agents agent and toolset types, including custom tools, MCP toolsets, theagent_toolset_20260401family, and versioning entry points.
Core Primitives
The most important primitive is BetaRunnableTool<Input>. It combines a client-executable beta tool definition with parse(content) and run(args, context) callbacks. The runner calls parse on the raw tool input and then calls run with typed arguments plus context. That context includes the original tool-use object as toolUse, a deprecated alias toolUseBlock, and an optional abort signal. Because the BetaToolUse union spans both Messages tool_use content blocks and Managed Agents tool-use events, one runnable implementation can often be reused across surfaces as long as it only depends on shared fields such as id, name, and input. Sources: src/lib/tools/BetaRunnableTool.ts
Tool lookup is intentionally small and consistent. The shared toolName helper resolves the registry key from name for ordinary tools and from mcp_server_name for MCP toolsets. That matters for mixed tool arrays, because the runner must match the model-addressed name to the local runnable implementation. The source also separates client-runnable tools from server-side tools: code execution, web search, and MCP toolsets are excluded from BetaClientRunnableToolType, while bash, computer use, text editor, memory, and custom beta tools can be implemented locally. Sources: src/lib/tools/BetaRunnableTool.ts, src/resources/beta/agents/index.ts
Request Pattern
To enable fine-grained input streaming for a user-defined tool, the Claude API documentation specifies setting eager_input_streaming to true on the tool and using a streaming request. Omitting the field keeps standard buffered streaming, where the service buffers and validates each parameter before returning it. The TypeScript SDK side remains the same runner surface: define the tool, include it in the beta Messages request, and consume the stream or runner events. Applications should be prepared for partial or invalid JSON while the parameter is still being produced.
import Anthropic from '@anthropic-ai/sdk';
const client = new Anthropic();
const writeFileTool = {
name: 'write_file',
description: 'Write generated text to a workspace file.',
input_schema: {
type: 'object',
properties: {
path: { type: 'string' },
contents: { type: 'string' },
},
required: ['path', 'contents'],
},
eager_input_streaming: true,
parse: (input: unknown) => input as { path: string; contents: string },
run: async ({ path, contents }) => {
// Validate and write inside your own sandbox before returning success.
return `Wrote ${contents.length} bytes to ${path}`;
},
};The example shows the request-level feature flag from the public docs and the SDK-level runnable-tool shape from source. The key operational detail is that fine-grained fragments may not form a complete JSON object until the tool input is complete. A robust application should accumulate streamed deltas, display or validate partial content cautiously, and only execute local side effects after parsing and policy checks have succeeded. If the generation stops because of token limits, a large parameter can be cut off; the tool should return a clear error result rather than attempting a destructive partial operation.
Execution Flow
The Messages runner is an async iterable. On the first iteration it marks itself consumed, resets mutation state, increments an iteration counter, and performs a beta Messages request. If params.stream is true, it calls client.beta.messages.stream, stores stream.finalMessage() as the eventual assistant message, and yields the BetaMessageStream to the caller. If streaming is false, it calls client.beta.messages.create with stream: false and yields the message promise. After the assistant message resolves, the runner can append it to the conversation and generate a tool response for the next turn. Sources: src/lib/tools/BetaToolRunner.ts, src/lib/tools/ToolRunner.ts
That loop is the bridge between fine-grained input arrival and automatic tool execution. The stream gives the application access to incremental events, while the final message gives the runner a stable view of tool-use blocks once the assistant turn is complete. The runnable tool’s parse method is where raw model input becomes the application’s typed argument object, and run is where local side effects happen. For latency-sensitive user interfaces, this means you can render or inspect fragments as they arrive, but should keep irreversible execution behind the parser and runner-managed tool response step.
Managed Agents use the same local-tool concept but a different transport. SessionToolRunner operates on session events rather than message content blocks. It dispatches agent.tool_use to user.tool_result and agent.custom_tool_use to user.custom_tool_result, while explicitly excluding server-side agent.mcp_tool_use calls from local handling. The runner also owns operational concerns such as the managed-agents-2026-04-01 beta header, stream backoff constants, send retries, tool timeouts, drain timeouts, idle shutdown, and abort propagation. Sources: src/lib/tools/SessionToolRunner.ts
Error Handling and Safety
Fine-grained streaming trades early visibility for weaker intermediate guarantees. Because input may be incomplete while it streams, tool implementations should treat parse as a validation boundary and run as an execution boundary. A parser can reject malformed or incomplete input, normalize arguments, enforce required fields, and protect the tool from acting on untrusted partial data. If a tool keeps process-level resources, such as a persistent shell, the close hook gives session runners a cleanup point when iteration ends. Sources: src/lib/tools/BetaRunnableTool.ts, src/lib/tools/SessionToolRunner.ts
When execution fails, prefer ToolError for model-readable failures. Throwing ToolError lets a runnable tool return either a string or an array of beta tool-result content blocks as the error result. The shared toolErrorContent helper preserves structured content for ToolError and converts other thrown values into an Error: <message> string. This keeps error reporting consistent across runner surfaces and allows rich diagnostic responses, such as text plus image content, to be sent back with is_error: true. Sources: src/lib/tools/ToolError.ts, src/lib/tools/BetaRunnableTool.ts
API Components Reference
| Component | Source | Contract |
|---|---|---|
BetaRunnableTool<Input> | src/lib/tools/BetaRunnableTool.ts | Tool definition plus parse(content), run(args, context), and optional close() for local execution. |
BetaToolUse | src/lib/tools/BetaRunnableTool.ts | Union of Messages tool-use blocks and Managed Agents tool-use events. |
BetaToolRunContext | src/lib/tools/BetaRunnableTool.ts | Supplies toolUse, deprecated toolUseBlock, and optional signal. |
toolName(tool) | src/lib/tools/BetaRunnableTool.ts | Resolves name or mcp_server_name for registry lookup. |
ToolError | src/lib/tools/ToolError.ts | Structured exception whose content is returned as an error tool result. |
BetaToolRunner<Stream> | src/lib/tools/BetaToolRunner.ts | Async iterable for beta Messages tool loops, yielding streams or messages according to request streaming. |
SessionToolRunner | src/lib/tools/SessionToolRunner.ts | Managed Agents event-loop runner for local tool execution and result event sending. |
Next Steps
Use fine-grained tool streaming when the first bytes of a large tool input are more valuable than waiting for a fully buffered, validated argument object. Start with a normal streaming Messages tool request, add eager_input_streaming: true only to tools that benefit from early fragments, and keep validation in parse before side effects in run. For Managed Agents, reuse the same runnable-tool shape through session event tooling, but remember that MCP tool-use events are handled server-side rather than by SessionToolRunner. Next, read tool-helpers, streaming-responses, mcp-integration, and managed-agent-event-streaming for the adjacent runner, SSE, and agent-session details.