fix(provider): preserve assistant message content when reasoning blocks present (#21370)

Co-authored-by: Omer Koren <54630488+omer-koren@users.noreply.github.com>
Co-authored-by: Aiden Cline <aidenpcline@gmail.com>
This commit is contained in:
André Cruz
2026-05-07 00:57:56 +01:00
committed by GitHub
parent 2dffdfff4a
commit 233fc5b910
2 changed files with 122 additions and 2 deletions

View File

@@ -854,13 +854,31 @@ export const toModelMessagesEffect = Effect.fnUntraced(function* (
role: "assistant",
parts: [],
}
// Anthropic adaptive thinking can persist assistant turns like:
// step-start, reasoning(signature), text(""), step-start,
// reasoning(signature). The empty text part is a structural separator,
// but it does not carry the signature metadata itself. Dropping it shifts
// signed thinking positions after step-start splitting/provider regrouping;
// keeping it as "" is filtered by the AI SDK and rejected by Anthropic.
// It is unclear whether this shape originates in our stream processing,
// a proxy, or a lower-level library, but preserving a non-empty separator
// here is the only safe replay point we have.
// Use a single space so the separator survives replay without changing
// the neighboring signed reasoning blocks. Bedrock-hosted Claude stores
// the same signature under the bedrock metadata namespace.
const hasSignedReasoning = msg.parts.some((part) => {
if (part.type !== "reasoning") return false
return part.metadata?.anthropic?.signature != null || part.metadata?.bedrock?.signature != null
})
for (const part of msg.parts) {
if (part.type === "text")
if (part.type === "text") {
const text = part.text === "" && hasSignedReasoning ? " " : part.text
assistantMessage.parts.push({
type: "text",
text: part.text,
text,
...(differentModel ? {} : { providerMetadata: part.metadata }),
})
}
if (part.type === "step-start")
assistantMessage.parts.push({
type: "step-start",