fix(ai): complete textSignature round-trip for Google providers

- Store thoughtSignature on text blocks during streaming (all 3 providers)
- Replay textSignature as thoughtSignature in convertMessages
- Remove redundant conditional since retainThoughtSignature handles undefined

Per Google docs, text part signatures are optional but recommended for
high-quality reasoning in multi-turn conversations.
This commit is contained in:
theBucky 2026-01-12 01:14:14 +08:00 committed by Mario Zechner
parent 4f757fbe23
commit a315cfe813
4 changed files with 16 additions and 19 deletions

View file

@ -511,12 +511,10 @@ export const streamGoogleGeminiCli: StreamFunction<"google-gemini-cli"> = (
}); });
} else { } else {
currentBlock.text += part.text; currentBlock.text += part.text;
if (part.thoughtSignature) { currentBlock.textSignature = retainThoughtSignature(
currentBlock.textSignature = retainThoughtSignature( currentBlock.textSignature,
currentBlock.textSignature, part.thoughtSignature,
part.thoughtSignature, );
);
}
stream.push({ stream.push({
type: "text_delta", type: "text_delta",
contentIndex: blockIndex(), contentIndex: blockIndex(),

View file

@ -85,7 +85,10 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
if (block.type === "text") { if (block.type === "text") {
// Skip empty text blocks - they can cause issues with some models (e.g. Claude via Antigravity) // Skip empty text blocks - they can cause issues with some models (e.g. Claude via Antigravity)
if (!block.text || block.text.trim() === "") continue; if (!block.text || block.text.trim() === "") continue;
parts.push({ text: sanitizeSurrogates(block.text) }); parts.push({
text: sanitizeSurrogates(block.text),
...(block.textSignature && { thoughtSignature: block.textSignature }),
});
} else if (block.type === "thinking") { } else if (block.type === "thinking") {
// Skip empty thinking blocks // Skip empty thinking blocks
if (!block.thinking || block.thinking.trim() === "") continue; if (!block.thinking || block.thinking.trim() === "") continue;

View file

@ -142,12 +142,10 @@ export const streamGoogleVertex: StreamFunction<"google-vertex"> = (
}); });
} else { } else {
currentBlock.text += part.text; currentBlock.text += part.text;
if (part.thoughtSignature) { currentBlock.textSignature = retainThoughtSignature(
currentBlock.textSignature = retainThoughtSignature( currentBlock.textSignature,
currentBlock.textSignature, part.thoughtSignature,
part.thoughtSignature, );
);
}
stream.push({ stream.push({
type: "text_delta", type: "text_delta",
contentIndex: blockIndex(), contentIndex: blockIndex(),

View file

@ -129,12 +129,10 @@ export const streamGoogle: StreamFunction<"google-generative-ai"> = (
}); });
} else { } else {
currentBlock.text += part.text; currentBlock.text += part.text;
if (part.thoughtSignature) { currentBlock.textSignature = retainThoughtSignature(
currentBlock.textSignature = retainThoughtSignature( currentBlock.textSignature,
currentBlock.textSignature, part.thoughtSignature,
part.thoughtSignature, );
);
}
stream.push({ stream.push({
type: "text_delta", type: "text_delta",
contentIndex: blockIndex(), contentIndex: blockIndex(),