| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
|
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. |
Sorry, something went wrong.
|
@rekram1-node Any chance you could have a look? ~20 LOC of actual changes, rest is tests |
Sorry, something went wrong.
|
Confirmed this fixes the three transform.ts paths cleanly — verified against messages.X.content.Y: 'thinking' or 'redacted_thinking' blocks in the latest assistant message cannot be modified on claude-opus-4-x (extended thinking, tool-heavy multi-turn). One gap worth folding in: message-v2.ts strips the thinking signature before transform.ts ever runs, on a path this PR doesn't touch. In toModelMessagesEffect, the reasoning branch is roughly: if (part.type === "reasoning") {
if (differentModel) { /* demote to text, drop providerMetadata */ }
// providerMetadata only kept when !differentModel
}with differentModel = ${model.providerID}/${model.id} !== ${msg.info.providerID}/${msg.info.modelID}. For Anthropic thinking blocks the providerMetadata carries the cryptographic signature, so dropping it on differentModel re-triggers the exact same cannot be modified 400 on the next turn — even with this PR's transform.ts fix applied. It reliably reproduces whenever provider/model identity shifts between turns: fallback-model switches, variant changes, and proxy setups where the stored providerID differs from the live one (we hit it through a proxy front where requests resolve under a different provider id than what's persisted in the session). Stripping text/tool metadata there is correct; stripping thinking metadata is not. Minimal fix that pairs with this PR: if (part.type === "reasoning") {
const isAnthropicReasoning =
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. |
Sorry, something went wrong.
|
Heads-up on the empty-reasoning filter in this PR — the providerOptions != null check is too loose and still lets the 400 through in one case: if (part.type === "reasoning") {
return part.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 providerOptions: {} (an empty object — still != null). Those get kept and re-sent as malformed thinking blocks, and Anthropic rejects the next turn with the very error this PR targets: messages.N.content.M: `thinking` or `redacted_thinking` blocks in the latest assistant message cannot be modified. 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 !== "") return true
const po = part.providerOptions ?? {}
// keep ONLY genuine redacted_thinking / signature-only blocks
return Object.values(po).some((v) => v != null && (v.signature != null || v.redactedData != null))
}Empty reasoning with {} / no signature is a truncated remnant and should be dropped, not replayed. Might be worth folding into this PR. (Related: #30046 fixes the upstream message-v2.ts strip that drops the signature on differentModel.) |
Sorry, something went wrong.
| Back | FazBrowse Home | New Git URL |
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 thinking and redacted_thinking blocks in assistant messages are sent back byte-for-byte identical in subsequent requests. Three code paths in normalizeMessages / applyCaching in src/provider/transform.ts were inadvertently modifying these blocks:
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