From 5bfba818a3f9563a6695251d4f2b36aadfbd4a99 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Fri, 16 Jan 2026 14:30:05 -0500 Subject: [PATCH 1/2] feat: use thoughtSignature bypass for Gemini tool part injection Instead of using text injection for Gemini models, use tool parts with the 'skip_thought_signature_validator' bypass signature. This flows through: ToolPart.metadata -> callProviderMetadata -> providerOptions -> @ai-sdk/google reads providerOptions.google.thoughtSignature. This provides consistent tool part injection across all providers while avoiding Gemini's thought signature validation errors. --- lib/messages/inject.ts | 4 ++-- lib/messages/utils.ts | 24 ++++++------------------ 2 files changed, 8 insertions(+), 20 deletions(-) diff --git a/lib/messages/inject.ts b/lib/messages/inject.ts index 8b67186..48b752f 100644 --- a/lib/messages/inject.ts +++ b/lib/messages/inject.ts @@ -6,7 +6,7 @@ import { loadPrompt } from "../prompts" import { extractParameterKey, buildToolIdList, - createSyntheticAssistantMessageWithToolPart, + createSyntheticAssistantMessage, isIgnoredUserMessage, } from "./utils" import { getFilePathFromParameters, isProtectedFilePath } from "../protected-file-patterns" @@ -150,6 +150,6 @@ export const insertPruneToolContext = ( const userInfo = lastUserMessage.info as UserMessage const variant = state.variant ?? userInfo.variant messages.push( - createSyntheticAssistantMessageWithToolPart(lastUserMessage, prunableToolsContent, variant), + createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant), ) } diff --git a/lib/messages/utils.ts b/lib/messages/utils.ts index fafdccd..219027c 100644 --- a/lib/messages/utils.ts +++ b/lib/messages/utils.ts @@ -12,7 +12,7 @@ const isGeminiModel = (modelID: string): boolean => { return lowerModelID.includes("gemini") } -export const createSyntheticAssistantMessageWithToolPart = ( +export const createSyntheticAssistantMessage = ( baseMessage: WithParts, content: string, variant?: string, @@ -39,24 +39,11 @@ export const createSyntheticAssistantMessageWithToolPart = ( ...(variant !== undefined && { variant }), } - // For Gemini models, inject as text to avoid thought signature requirements - // Gemini 3+ has strict validation requiring thoughtSignature on functionCall parts - if (isGeminiModel(userInfo.model.modelID)) { - return { - info: baseInfo, - parts: [ - { - id: SYNTHETIC_PART_ID, - sessionID: userInfo.sessionID, - messageID: SYNTHETIC_MESSAGE_ID, - type: "text", - text: content, - }, - ], - } - } + // For Gemini models, add thoughtSignature bypass to avoid validation errors + const toolPartMetadata = isGeminiModel(userInfo.model.modelID) + ? { google: { thoughtSignature: "skip_thought_signature_validator" } } + : undefined - // For other models, use tool part for cleaner context return { info: baseInfo, parts: [ @@ -75,6 +62,7 @@ export const createSyntheticAssistantMessageWithToolPart = ( metadata: {}, time: { start: now, end: now }, }, + ...(toolPartMetadata && { metadata: toolPartMetadata }), }, ], } From e3006bc1873de117b3c5a294a362d6b5582cd0e0 Mon Sep 17 00:00:00 2001 From: Daniel Smolsky Date: Fri, 16 Jan 2026 14:32:38 -0500 Subject: [PATCH 2/2] format --- lib/messages/inject.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/messages/inject.ts b/lib/messages/inject.ts index 48b752f..c421826 100644 --- a/lib/messages/inject.ts +++ b/lib/messages/inject.ts @@ -149,7 +149,5 @@ export const insertPruneToolContext = ( const userInfo = lastUserMessage.info as UserMessage const variant = state.variant ?? userInfo.variant - messages.push( - createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant), - ) + messages.push(createSyntheticAssistantMessage(lastUserMessage, prunableToolsContent, variant)) }