From 9971c60f8bdb1343272c7cb57c00e16e0061e185 Mon Sep 17 00:00:00 2001 From: Felipe Barcelos Date: Thu, 29 Jan 2026 13:06:49 -0300 Subject: [PATCH] Implement thought_signature preservation in transformations This commit adds full support for Gemini 3.0's thought_signature requirement in tool calling. Changes: - Added transformOpenAIResponseToClaude() to capture thought_signature from Gemini responses - Added addThoughtSignaturesToToolResults() to inject signatures back into tool results - Signatures are preserved in a Map structure (tool_call_id -> signature) - Handles both OpenAI format (role='tool') and Claude format (content.type='tool_result') This fixes the 400 error "Function call is missing a thought_signature" when using Gemini 3.0 models with tool calling. Reference: https://ai.google.dev/gemini-api/docs/thought-signatures --- src/transform.ts | 59 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 58 insertions(+), 1 deletion(-) diff --git a/src/transform.ts b/src/transform.ts index 4bc7649..c0c0e91 100644 --- a/src/transform.ts +++ b/src/transform.ts @@ -388,5 +388,62 @@ export function transformOpenAIToClaude(claudeRequestInput: any): { claudeReques claudeRequest: req, droppedParams: dropped, isO3Model + } +} + +/** + * Transform OpenAI response back to Claude format, preserving thought_signature for Gemini 3.0 + */ +export function transformOpenAIResponseToClaude(openAIResponse: any, thoughtSignatures?: Map): any { + if (!openAIResponse.choices || !Array.isArray(openAIResponse.choices)) { + return openAIResponse + } + + // Process each choice + for (const choice of openAIResponse.choices) { + if (!choice.message) continue + + const message = choice.message + + // Handle tool_calls with thought_signature preservation + if (message.tool_calls && Array.isArray(message.tool_calls)) { + for (const toolCall of message.tool_calls) { + // Store thought_signature if present (Gemini 3.0 specific) + if (toolCall.function && thoughtSignatures) { + const thoughtSig = (toolCall.function as any).thought_signature || + (toolCall as any).thought_signature + if (thoughtSig) { + thoughtSignatures.set(toolCall.id, thoughtSig) + } + } + } + } + } + + return openAIResponse +} + +/** + * Add thought_signature to tool results for Gemini 3.0 compatibility + */ +export function addThoughtSignaturesToToolResults(messages: any[], thoughtSignatures: Map): void { + if (!messages || !Array.isArray(messages)) return + + for (const message of messages) { + if (message.role === 'tool' && message.tool_call_id && thoughtSignatures.has(message.tool_call_id)) { + // Add thought_signature to the tool result + (message as any).thought_signature = thoughtSignatures.get(message.tool_call_id) + } + + // Also handle content array format (Claude format) + if (message.content && Array.isArray(message.content)) { + for (const content of message.content) { + if (content.type === 'tool_result' && content.tool_use_id && thoughtSignatures.has(content.tool_use_id)) { + content.thought_signature = thoughtSignatures.get(content.tool_use_id) + } + } + } } -} \ No newline at end of file + + } +}