Summary
Anthropic validates server_tool_use.id against ^srvtoolu_[a-zA-Z0-9_]+$ and rejects the entire request with a 400 when one does not match.
A provider that runs its own built-in tools emits its native id instead of an Anthropic one. z.ai/glm does this for analyze_image, producing OpenAI-style call_ ids:
{"type": "server_tool_use", "id": "call_50b82aba1b754d82a4408a53", "name": "analyze_image", "input": {}}
In a mixed combo those blocks stay in the conversation history. As soon as the combo routes a later turn to a Claude provider, the passthrough body carries the foreign id and the API rejects it:
API Error: 400 messages.1.content.1.server_tool_use.id:
String should match pattern '^srvtoolu_[a-zA-Z0-9_]+$'
The session cannot recover on its own: the bad block is now part of the history, so every subsequent turn fails the same way.
Reproduction
- Set up a combo mixing a Claude provider and
glm/glm-5.3.
- Let a turn land on GLM and trigger its built-in
analyze_image tool, so a server_tool_use block with a call_ id enters the history.
- Let a later turn route to the Claude provider → 400 on every attempt from then on.
Expected
The same treatment normalizeClaudePassthrough already applies to foreign thinking signatures — claude.js#L164-L166 notes that a combo mixing models leaks foreign signatures into history and drops them. A foreign server_tool_use id is the same class of leak and should be dropped before the request reaches Anthropic, along with the tool_result that references it (an orphan result is rejected in turn).
PR follows.
Summary
Anthropic validates
server_tool_use.idagainst^srvtoolu_[a-zA-Z0-9_]+$and rejects the entire request with a 400 when one does not match.A provider that runs its own built-in tools emits its native id instead of an Anthropic one. z.ai/glm does this for
analyze_image, producing OpenAI-stylecall_ids:{"type": "server_tool_use", "id": "call_50b82aba1b754d82a4408a53", "name": "analyze_image", "input": {}}In a mixed combo those blocks stay in the conversation history. As soon as the combo routes a later turn to a Claude provider, the passthrough body carries the foreign id and the API rejects it:
The session cannot recover on its own: the bad block is now part of the history, so every subsequent turn fails the same way.
Reproduction
glm/glm-5.3.analyze_imagetool, so aserver_tool_useblock with acall_id enters the history.Expected
The same treatment
normalizeClaudePassthroughalready applies to foreign thinking signatures —claude.js#L164-L166notes that a combo mixing models leaks foreign signatures into history and drops them. A foreignserver_tool_useid is the same class of leak and should be dropped before the request reaches Anthropic, along with thetool_resultthat references it (an orphan result is rejected in turn).PR follows.