TanStack AI version
0.39.1
Framework/Library version
@tanstack/ai-anthropic: 0.16.0
Describe the bug and the steps to reproduce it
When an assistant UIMessage contains signed Anthropic thinking parts interleaved with provider-executed tools, convertMessagesToModelMessages does not preserve the original part order.
This matters for Anthropic because signed thinking / redacted_thinking blocks must be replayed exactly as they appeared in the original response. If they are moved relative to tool-use blocks, the next Anthropic request can fail with:
400 {"type":"error","error":{"type":"invalid_request_error","message":"messages.N.content.M: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. These blocks must remain as they were in the original response."}}
The shape that triggers it is:
assistant UIMessage parts:
thinking(signature A)
provider-executed web_search tool-call
thinking(signature B)
local tool-call
local tool-result
The original order is valid because the second thinking block happened after the provider tool result. But after conversion to model messages, both thinking blocks are attached to the same assistant segment and replayed before both tool calls.
Reproduction Script
import { convertMessagesToModelMessages } from "@tanstack/ai"
const messages = [
{
id: "assistant-message",
role: "assistant",
parts: [
{
type: "thinking",
content: "First signed thinking block",
signature: "signature-1",
},
{
type: "tool-call",
id: "srvtoolu_search",
name: "web_search",
arguments: '{"query":"top AI companies"}',
state: "input-complete",
metadata: {
providerExecuted: true,
anthropic: {
serverToolType: "web_search",
resultBlockType: "web_search_tool_result",
result: [
{
type: "web_search_result",
title: "Example result",
url: "https://example.com",
encrypted_content: "opaque-provider-payload",
},
],
},
},
},
{
type: "thinking",
content: "Second signed thinking block",
signature: "signature-2",
},
{
type: "tool-call",
id: "toolu_create_block",
name: "createBlock",
arguments: '{"block":{"type":"entity-grid"}}',
state: "complete",
output: { ok: true },
},
{
type: "tool-result",
toolCallId: "toolu_create_block",
content: '{"ok":true}',
state: "complete",
},
],
},
]
const modelMessages = convertMessagesToModelMessages(messages)
console.log(
JSON.stringify(
modelMessages.map((message) => ({
role: message.role,
thinking: message.thinking?.map((part) => part.content),
toolCalls: message.toolCalls?.map((toolCall) => toolCall.function.name),
toolCallId: message.toolCallId,
content: message.content,
})),
null,
2,
),
)
Actual Output
Both signed thinking blocks are attached to the same assistant model message:
[
{
"role": "assistant",
"thinking": [
"First signed thinking block",
"Second signed thinking block"
],
"toolCalls": [
"web_search",
"createBlock"
],
"content": null
},
{
"role": "tool",
"toolCallId": "toolu_create_block",
"content": "{\"ok\":true}"
}
]
With the Anthropic adapter, that means the signed thinking blocks are appended before the provider server_tool_use and the local tool_use, changing the original signed transcript order.
Expected Output
The conversion should split assistant segments when a later signed thinking block appears after an already accumulated tool call, so the original interleaving is preserved:
[
{
"role": "assistant",
"thinking": [
"First signed thinking block"
],
"toolCalls": [
"web_search"
],
"content": null
},
{
"role": "assistant",
"thinking": [
"Second signed thinking block"
],
"toolCalls": [
"createBlock"
],
"content": null
},
{
"role": "tool",
"toolCallId": "toolu_create_block",
"content": "{\"ok\":true}"
}
]
Equivalently, the Anthropic adapter needs to replay signed thinking blocks in the same relative order as the original provider response content blocks.
Why This Is Blocking
This happens in normal multi-turn conversations that use Anthropic web search / web fetch plus thinking. The first turn succeeds and can be persisted as a UIMessage, but the next user turn fails because the stored assistant message is converted back into a modified Anthropic transcript.
The issue is especially easy to hit with provider-executed server tools because the provider result is stored in the tool-call metadata rather than as a normal tool-result part, so there is no tool-result boundary that forces a segment flush before the next thinking block.
Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
See bug description
Screenshots or Videos (Optional)
No response
Do you intend to try to help solve this bug with your own PR?
No, because I do not know how
Terms & Code of Conduct
TanStack AI version
0.39.1
Framework/Library version
@tanstack/ai-anthropic: 0.16.0
Describe the bug and the steps to reproduce it
When an assistant
UIMessagecontains signed Anthropicthinkingparts interleaved with provider-executed tools,convertMessagesToModelMessagesdoes not preserve the original part order.This matters for Anthropic because signed
thinking/redacted_thinkingblocks must be replayed exactly as they appeared in the original response. If they are moved relative to tool-use blocks, the next Anthropic request can fail with:The shape that triggers it is:
The original order is valid because the second
thinkingblock happened after the provider tool result. But after conversion to model messages, both thinking blocks are attached to the same assistant segment and replayed before both tool calls.Reproduction Script
Actual Output
Both signed thinking blocks are attached to the same assistant model message:
[ { "role": "assistant", "thinking": [ "First signed thinking block", "Second signed thinking block" ], "toolCalls": [ "web_search", "createBlock" ], "content": null }, { "role": "tool", "toolCallId": "toolu_create_block", "content": "{\"ok\":true}" } ]With the Anthropic adapter, that means the signed thinking blocks are appended before the provider
server_tool_useand the localtool_use, changing the original signed transcript order.Expected Output
The conversion should split assistant segments when a later signed thinking block appears after an already accumulated tool call, so the original interleaving is preserved:
[ { "role": "assistant", "thinking": [ "First signed thinking block" ], "toolCalls": [ "web_search" ], "content": null }, { "role": "assistant", "thinking": [ "Second signed thinking block" ], "toolCalls": [ "createBlock" ], "content": null }, { "role": "tool", "toolCallId": "toolu_create_block", "content": "{\"ok\":true}" } ]Equivalently, the Anthropic adapter needs to replay signed thinking blocks in the same relative order as the original provider response content blocks.
Why This Is Blocking
This happens in normal multi-turn conversations that use Anthropic web search / web fetch plus thinking. The first turn succeeds and can be persisted as a
UIMessage, but the next user turn fails because the stored assistant message is converted back into a modified Anthropic transcript.The issue is especially easy to hit with provider-executed server tools because the provider result is stored in the tool-call metadata rather than as a normal
tool-resultpart, so there is no tool-result boundary that forces a segment flush before the next thinking block.Your Minimal, Reproducible Example - (Sandbox Highly Recommended)
See bug description
Screenshots or Videos (Optional)
No response
Do you intend to try to help solve this bug with your own PR?
No, because I do not know how
Terms & Code of Conduct