Sanitize tool-call IDs on the Anthropic Messages wire - #52
Open
jcheng5 wants to merge 1 commit into
Open
Conversation
The Anthropic Messages API validates tool_use.id against ^[a-zA-Z0-9_-]+$. Other wires accept arbitrary IDs (e.g. Kimi K3 emits <toolName>:<counter> like ls:0), so replaying a cross-provider history to an Anthropic-wire model — model switch mid-conversation, /compact summarizing with Claude — fails with a 400. Add sanitizeToolCallIdsForAnthropic: deterministic character replacement plus a message-index suffix for per-request uniqueness, with positional call/result pairing (tool-message results use j-1, provider-executed assistant-message results use their own index). Conforming, first-use IDs pass through untouched, so pure-Claude histories are a zero-copy no-op. Conforming duplicates are index-suffixed via first-occurrence tracking. Warns on non-conforming IDs, same-message base collisions, and orphan tool-results. Wire in via a shared streamTextAnthropicWire choke point used by AnthropicClient, PositAiClient (anthropic-messages branch), and SnowflakeClient (chatAnthropic); BedrockClient and GoogleVertexClient sanitize conditionally on their Anthropic routes. Fixes posit-dev/assistant#2016
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Anthropic Messages API validates
tool_use.id/tool_result.tool_use_idagainst^[a-zA-Z0-9_-]+$. Other wires accept arbitrary IDs — e.g. Kimi K3 emits<toolName>:<counter>likels:0, with the counter resetting per turn so duplicates across history are the norm. When a conversation containing such IDs is replayed to an Anthropic-wire model (model switch mid-conversation,/compactsummarizing with Claude Haiku), the API rejects the request:Fixes posit-dev/assistant#2016.
Fix
The constraint is a property of the target wire, so Anthropic-wire clients now sanitize outbound IDs unconditionally via a new
sanitizeToolCallIdsForAnthropichelper (tool-call-ids.ts):[^a-zA-Z0-9_-]→-); empty IDs fall back tocall.<base>-<i>whereiis the message index in the outbound array. Pairing is positional: atool-resultin atoolmessage atjusesj-1; a provider-executed result in an assistant message uses its own index. Deterministic and prompt-cache-stable because history is append-only.tool-resultparts.Wiring goes through a shared
streamTextAnthropicWire()choke point so future Anthropic-wire clients get sanitization by default: AnthropicClient, PositAiClient (anthropic-messagesbranch), and SnowflakeClient (chatAnthropic) use the wrapper; BedrockClient and GoogleVertexClient sanitize conditionally on their Anthropic routes since thosestreamTextcall sites are shared across protocols.Tests
ls:0in two turns), same-turn parallel calls, conforming-duplicate suffixing, empty-ID fallback, no-op passthrough, collision/orphan warnings.PositAiClientwith the observed Kimils:0format repeated across two turns, asserting the serialized request body contains only pattern-conforming, unique, paired IDs. Verified to fail on the unfixed code.Full bridge suite (286 tests) and monorepo
check-typespass.