From e08405ffbf55415e5de898a1bf7aa2e384c51855 Mon Sep 17 00:00:00 2001 From: Mark Henderson Date: Mon, 27 Apr 2026 17:28:49 -0700 Subject: [PATCH] Fix thought_signature: it's a sibling of functionCall, not nested inside it The Gemini REST API returns thoughtSignature as a sibling part field: { "functionCall": {...}, "thoughtSignature": "..." } not inside functionCall. We were reading part.functionCall.thought_signature (always undefined) and writing fc.thought_signature inside the functionCall object (also wrong). Now correctly reads part.thoughtSignature and writes part.thoughtSignature when building history. Made-with: Cursor --- lib/ai/gemini-chat.ts | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/lib/ai/gemini-chat.ts b/lib/ai/gemini-chat.ts index 0ccb3f3b..d693e9c5 100644 --- a/lib/ai/gemini-chat.ts +++ b/lib/ai/gemini-chat.ts @@ -55,20 +55,22 @@ function toGeminiContents(messages: ChatMessage[]) { if (msg.content) parts.push({ text: msg.content }); if (msg.toolCalls?.length) { for (const tc of msg.toolCalls) { - const fc: any = { name: tc.name, args: tc.args, id: tc.id }; - if (tc.thoughtSignature) fc.thought_signature = tc.thoughtSignature; - parts.push({ functionCall: fc }); + // thoughtSignature is a SIBLING of functionCall in the part object, + // not nested inside it. See: ai.google.dev/gemini-api/docs/thought-signatures + const part: any = { functionCall: { name: tc.name, args: tc.args, id: tc.id } }; + if (tc.thoughtSignature) part.thoughtSignature = tc.thoughtSignature; + parts.push(part); } } if (parts.length) contents.push({ role: 'model', parts }); } else if (msg.role === 'tool') { - const fr: any = { - name: msg.toolName || 'unknown', - id: msg.toolCallId, - response: { content: msg.content }, + const part = { + functionResponse: { + name: msg.toolName || 'unknown', + id: msg.toolCallId, + response: { content: msg.content }, + }, }; - if (msg.thoughtSignature) fr.thought_signature = msg.thoughtSignature; - const part = { functionResponse: fr }; const last = contents[contents.length - 1]; if (last?.role === 'user') { last.parts.push(part); @@ -147,7 +149,8 @@ export async function callGeminiChat(opts: { id: part.functionCall.id || `tc-${Date.now()}-${Math.random().toString(36).slice(2)}`, name: part.functionCall.name, args: part.functionCall.args ?? {}, - thoughtSignature: part.functionCall.thought_signature, + // thoughtSignature is a SIBLING of functionCall in the part, not inside it + thoughtSignature: part.thoughtSignature, }); } }