Uh oh!
There was an error while loading. Please reload this page.
fix: preserve thinking/redacted_thinking blocks in Anthropic message transforms - #23755
fix: preserve thinking/redacted_thinking blocks in Anthropic message transforms#23755bbartels wants to merge 15 commits into
Conversation
The following comment was made by an LLM, it may be inaccurate: Potential Duplicate/Related PRs Found:
These PRs appear to be addressing overlapping issues with preserving thinking/reasoning blocks in Anthropic message transforms. The current PR (23755) consolidates fixes for three specific code paths (empty content filtering, tool-use reordering, and cache control hints) that were inadvertently modifying these blocks. |
bbartels
commented
Apr 22, 2026
@rekram1-node Any chance you could have a look? ~20 LOC of actual changes, rest is tests |
codeg-dev
commented
May 31, 2026
Confirmed this fixes the three One gap worth folding in: if(part.type==="reasoning"){if(differentModel){/* demote to text, drop providerMetadata */}// providerMetadata only kept when !differentModel}with It reliably reproduces whenever provider/model identity shifts between turns: fallback-model switches, Minimal fix that pairs with this PR: if(part.type==="reasoning"){constisAnthropicReasoning=model.providerID==="anthropic"||model.providerID.includes("anthropic")||model.api?.npm==="@ai-sdk/anthropic"// keep the signature for Anthropic even across differentModel...(differentModel&&!isAnthropicReasoning ? {} : {providerMetadata: part.metadata})}Happy to open a small follow-up PR (rebased on this) if useful. Disclosure: investigation + draft assisted by an AI agent; reproduction and the message-v2.ts gap were validated against a live multi-provider (CLIProxyAPI) deployment. |
codeg-dev
commented
May 31, 2026
Heads-up on the empty-reasoning filter in this PR — the if(part.type==="reasoning"){returnpart.text!==""||part.providerOptions!=null}With Anthropic interleaved thinking across several tool-call rounds, a turn that is cut off (e.g. output-length / max-tokens) can leave trailing reasoning parts that have empty text and We hit this in production after applying an equivalent of this filter: the error recurred on a long Opus extended-thinking + multi-tool session. Tightening the predicate to require an actual signature/redactedData fixed it: if(part.type==="reasoning"){if(part.text!=="")returntrueconstpo=part.providerOptions??{}// keep ONLY genuine redacted_thinking / signature-only blocksreturnObject.values(po).some((v)=>v!=null&&(v.signature!=null||v.redactedData!=null))}Empty reasoning with |
Issue for this PR
Closes#14332
Type of change
What does this PR do?
Fixes the Anthropic API error:
messages.X.content.Y: 'thinking' or 'redacted_thinking' blocks in the latest assistant message cannot be modified.The Anthropic API requires that
thinkingandredacted_thinkingblocks in assistant messages are sent back byte-for-byte identical in subsequent requests. Three code paths innormalizeMessages/applyCachinginsrc/provider/transform.tswere inadvertently modifying these blocks:redacted_thinkingblocks. The AI SDK represents these as{ type: "reasoning", text: "", providerOptions: { anthropic: { redactedData: "..." } } }— the empty text caused them to be filtered out. Fix: only filter reasoning parts that have noproviderOptions.[thinking, tool_call, text]into two separate messages, changing which message the thinking blocks belong to. Fix: skip the reorder when reasoning blocks are present.How did you verify your code works?
Added tests covering all three fixes to
test/provider/transform.test.ts. All 143 tests pass (130 existing + 13 new).bun test test/provider/transform.test.ts
143 pass, 0 fail
Screenshots / recordings
N/A - not a UI change.
Checklist