docs: expand tool runner documentation with internal mechanics#881
docs: expand tool runner documentation with internal mechanics#881karpetrosyan wants to merge 28 commits intoanthropics:nextfrom
Conversation
fix(client): don't strip path from filename for skills endpoint
docs: update README with Claude branding - Add Claude sparkle logo to header - Rename to "Claude SDK for TypeScript" - Use "Claude API" terminology instead of "Anthropic API" - Link to TypeScript SDK docs at platform.claude.com - Simplify and consolidate documentation sections Co-Authored-By: Claude <noreply@anthropic.com>
* "Claude PR Assistant workflow" * "Claude Code Review workflow"
Co-authored-by: Vibeke Tengroth <vibeketengroth@gmail.com> Co-authored-by: Packy Gallagher <packy@anthropic.com>
helpers.md
Outdated
|
|
||
| #### Iteration Lifecycle | ||
|
|
||
| On each iteration, the tool runner performs three key operations: |
There was a problem hiding this comment.
Can we clarify "on" — is it before or after the user's loop block is executed?
There was a problem hiding this comment.
I’ve changed the step order instead and mentioned that the first step is before the yield—hope that makes it clearer
* feat(api): update via SDK Studio * feat(api): update via SDK Studio * codegen metadata * feat: migrate output_format to output_config.format for structured outputs - Update beta header from structured-outputs-2025-11-13 to structured-outputs-2025-12-15 - Transform deprecated output_format parameter to output_config.format - Add warning when both output_format and output_config.format are provided - Update beta-parser to support parsing from either location - Update tests to expect new output_config.format structure Co-Authored-By: Claude Code (/Users/davidmeadows/stainless/stainless) <noreply@anthropic.com> * feat: add @deprecated JSDoc tag and update example to use output_config - Add @deprecated JSDoc tag to output_format parameter in BetaParseableMessageCreateParams - Update parsing-zod.ts example to use output_config.format instead of output_format Co-Authored-By: Claude Code (/Users/davidmeadows/stainless/stainless/dist/customer-repos/stainless-sdks/anthropic-typescript) <noreply@anthropic.com> * fix: throw error instead of warning when both output params provided Co-Authored-By: Claude Code (/Users/davidmeadows/stainless/stainless/dist/customer-repos/stainless-sdks/anthropic-typescript) <noreply@anthropic.com> * Remove note about beta header injection * clean up things a bit * revert changes * undo a change * undo changes from next * fixup! * fixes * fixup! * fix: move structured output formatting to dedicated function and add to token counting * feat: Test exception case in structured output * feat: improve types and add other unit test for structured output transformation --------- Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> Co-authored-by: Claude Code (/Users/davidmeadows/stainless/stainless) <noreply@anthropic.com> Co-authored-by: Cameron McAteer <246350779+cameron-mcateer@users.noreply.github.com>
Add helper functions for integrating with the [Model Context Protocol (MCP) SDK](https://github.com/modelcontextprotocol/sdk). These helpers reduce the boilerplate required to convert MCP types to Anthropic API types from ~100+ lines to single function calls. **New helpers:** - `mcpTool(tool, mcpClient)` - Convert single MCP tool to `BetaRunnableTool` - `mcpTools(tools, mcpClient)` - Convert array of MCP tools to `BetaRunnableTool[]` for use with `toolRunner()` - `mcpMessage(message)` - Convert single MCP `PromptMessage` to `BetaMessageParam` - `mcpMessages(messages)` - Convert array of MCP `PromptMessage` to `BetaMessageParam[]` - `mcpContent(content)` - Convert single MCP content block to Anthropic content block - `mcpResourceToContent(resource)` - Convert MCP resource to content block (document or image) - `mcpResourceToFile(resource)` - Convert MCP resource to `File` for `files.upload()`
helpers.md
Outdated
|
|
||
| 1. **State update (only if unchanged)**: The tool runner appends the last message from the API response (the one yielded to the client) to its internal state only if the state wasn't modified during that iteration via `pushMessages()` or `setMessagesParams()`. If the state was mutated, it ignores that message and continues using the user-mutated state. | ||
|
|
||
| 2. **Tool handling (always)**: The tool runner inspects the last message. If it contains any `tool_use` blocks, it handles them and appends an appropriate message containing the corresponding `tool_result` blocks — regardless of whether the state was mutated. |
There was a problem hiding this comment.
can we be precise here about where they are appended to?
| 2. **Tool handling (always)**: The tool runner inspects the last message. If it contains any `tool_use` blocks, it handles them and appends an appropriate message containing the corresponding `tool_result` blocks — regardless of whether the state was mutated. | |
| 2. **Tool handling (always)**: The tool runner inspects the last message. If it contains any `tool_use` blocks, it handles them and appends an appropriate message containing the corresponding `tool_result` blocks to `runner.params.messages` — regardless of whether the `runner.params` was mutated by the consumer. |
helpers.md
Outdated
|
|
||
| 2. **Tool handling (always)**: The tool runner inspects the last message. If it contains any `tool_use` blocks, it handles them and appends an appropriate message containing the corresponding `tool_result` blocks — regardless of whether the state was mutated. | ||
|
|
||
| 3. **Next request + repeat**: It sends a new request to the API using the current internal state, yields the new message to the user, and repeats the loop. |
There was a problem hiding this comment.
does "repeat the loop" mean yielding control to the consumer (executing the loop block)?
helpers.md
Outdated
|
|
||
| #### generateToolResponse() | ||
|
|
||
| The `generateToolResponse()` method is a helper that reads the `tool_use` blocks, calls the tools, and generates a message containing the corresponding `tool_result` blocks. Note that: |
There was a problem hiding this comment.
Could we mention whether this is what the tool runner calls internally on each iteration?
| The `generateToolResponse()` method is a helper that reads the `tool_use` blocks, calls the tools, and generates a message containing the corresponding `tool_result` blocks. Note that: | ||
|
|
||
| - It **does not mutate state** — calling generateToolResponse alone won’t prevent the loop from adding its message to state | ||
| - It **caches results** to avoid redundant calls — if you pass the same state, it returns the cached result |
There was a problem hiding this comment.
thought: this is interesting because it prevents a user from implementing retries of tools when they fail
There was a problem hiding this comment.
I believe there’s a way to do retries. If we could wrap the tool with a function that handles retries, we’d get retry functionality even without caching—though I’m not sure how flexible it is
| On each iteration, the tool runner performs three key operations: | ||
|
|
||
| 1. **Next request:** It sends a new request to the API using the current internal state (before yield), and yields the new message to the user. | ||
|
|
There was a problem hiding this comment.
Can we insert a step here that is "The code inside the user's for loop block runs, with access to the current message response"
7dcc12a to
31220e0
Compare
… into improve-tool-runner-docs
eeb7fab to
7b4849b
Compare
6bcd8a5 to
883bbb6
Compare
How the Tool Runner Works
Iteration Lifecycle
On each iteration, the tool runner performs three key operations:
State update (only if unchanged): The tool runner appends the last message from the API response (the one yielded to the client) to its internal state only if the state wasn't modified during that iteration via
pushMessages()orsetMessagesParams(). If the state was mutated, it ignores that message and continues using the user-mutated state.Tool handling (always): The tool runner inspects the last message. If it contains any
tool_useblocks, it handles them and appends an appropriate message containing the correspondingtool_resultblocks — regardless of whether the state was mutated.Next request + repeat: It sends a new request to the API using the current internal state, yields the new message to the user, and repeats the loop.
generateToolResponse()
The
generateToolResponse()method is a helper that reads thetool_useblocks, calls the tools, and generates a message containing the correspondingtool_resultblocks. Note that:If you push both the last message and the result of
generateToolResponse()into the state, the tool runner will effectively do nothing except send the next request:Execution Flow Diagram
sequenceDiagram autonumber participant U as User participant TR as ToolRunner participant API as Model API participant Tools as Tools loop Repeat until done TR->>API: Send request (using current state) API-->>TR: Message TR-->>U: Yield message note over U: User can read message<br/>and optionally change state via<br/>pushMessages or setMessagesParams U->>TR: Resume iteration alt User did not change state TR->>TR: Append message to history else User changed state TR->>TR: Keep user state (no auto-append) end alt Message contains tool request TR->>Tools: Run tools (with generateToolResponse) Tools-->>TR: Tool results TR->>TR: Append tool results else No tool request TR->>TR: Finish end end