mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-20 02:03:16 +00:00
fix(ai): use skip_thought_signature_validator for unsigned Gemini 3 tool calls
Replace text fallback with the official sentinel value so unsigned function calls retain structured context in multi-turn conversations. closes #1829
This commit is contained in:
parent
cfbb15876a
commit
a0d839ce84
4 changed files with 148 additions and 50 deletions
|
|
@ -45,6 +45,11 @@ export function retainThoughtSignature(existing: string | undefined, incoming: s
|
|||
// Thought signatures must be base64 for Google APIs (TYPE_BYTES).
|
||||
const base64SignaturePattern = /^[A-Za-z0-9+/]+={0,2}$/;
|
||||
|
||||
// Sentinel value that tells the Gemini API to skip thought signature validation.
|
||||
// Used for unsigned function call parts (e.g. replayed from providers without thought signatures).
|
||||
// See: https://ai.google.dev/gemini-api/docs/thought-signatures
|
||||
const SKIP_THOUGHT_SIGNATURE = "skip_thought_signature_validator";
|
||||
|
||||
function isValidThoughtSignature(signature: string | undefined): boolean {
|
||||
if (!signature) return false;
|
||||
if (signature.length % 4 !== 0) return false;
|
||||
|
|
@ -138,28 +143,19 @@ export function convertMessages<T extends GoogleApiType>(model: Model<T>, contex
|
|||
} else if (block.type === "toolCall") {
|
||||
const thoughtSignature = resolveThoughtSignature(isSameProviderAndModel, block.thoughtSignature);
|
||||
// Gemini 3 requires thoughtSignature on all function calls when thinking mode is enabled.
|
||||
// When replaying history from providers without thought signatures (e.g. Claude via Antigravity),
|
||||
// convert unsigned function calls to text to avoid API validation errors.
|
||||
// We include a note telling the model this is historical context to prevent mimicry.
|
||||
// Use the skip_thought_signature_validator sentinel for unsigned function calls
|
||||
// (e.g. replayed from providers without thought signatures like Claude via Antigravity).
|
||||
const isGemini3 = model.id.toLowerCase().includes("gemini-3");
|
||||
if (isGemini3 && !thoughtSignature) {
|
||||
const argsStr = JSON.stringify(block.arguments ?? {}, null, 2);
|
||||
parts.push({
|
||||
text: `[Historical context: a different model called tool "${block.name}" with arguments: ${argsStr}. Do not mimic this format - use proper function calling.]`,
|
||||
});
|
||||
} else {
|
||||
const part: Part = {
|
||||
functionCall: {
|
||||
name: block.name,
|
||||
args: block.arguments ?? {},
|
||||
...(requiresToolCallId(model.id) ? { id: block.id } : {}),
|
||||
},
|
||||
};
|
||||
if (thoughtSignature) {
|
||||
part.thoughtSignature = thoughtSignature;
|
||||
}
|
||||
parts.push(part);
|
||||
}
|
||||
const effectiveSignature = thoughtSignature || (isGemini3 ? SKIP_THOUGHT_SIGNATURE : undefined);
|
||||
const part: Part = {
|
||||
functionCall: {
|
||||
name: block.name,
|
||||
args: block.arguments ?? {},
|
||||
...(requiresToolCallId(model.id) ? { id: block.id } : {}),
|
||||
},
|
||||
...(effectiveSignature && { thoughtSignature: effectiveSignature }),
|
||||
};
|
||||
parts.push(part);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue