fix: Restore fallback thoughtSignature for cross-model tool_use scenarios

- Only use fallback when tool_use blocks exist without thoughtSignature
- Fallback is needed when switching from non-thinking models to Gemini 3
- Added tests for both cross-model (fallback) and non-thinking (no signature) scenarios
This commit is contained in:
Roo Code 2025-12-24 20:13:13 +00:00
parent be8ac86165
commit a53db60a0d
2 changed files with 86 additions and 6 deletions

View file

@ -107,7 +107,7 @@ describe("convertAnthropicMessageToGemini", () => {
expect(() => convertAnthropicMessageToGemini(anthropicMessage)).toThrow("Unsupported image source type")
})
it("should convert a message with tool use without thoughtSignature when none exists", () => {
it("should use fallback thoughtSignature for tool_use when includeThoughtSignatures is true but no signature exists (cross-model scenario)", () => {
const anthropicMessage: Anthropic.Messages.MessageParam = {
role: "assistant",
content: [
@ -121,6 +121,74 @@ describe("convertAnthropicMessageToGemini", () => {
],
}
// Default includeThoughtSignatures is true, so fallback should be used
const result = convertAnthropicMessageToGemini(anthropicMessage)
expect(result).toEqual([
{
role: "model",
parts: [
{ text: "Let me calculate that for you." },
{
functionCall: {
name: "calculator",
args: { operation: "add", numbers: [2, 3] },
},
thoughtSignature: "skip_thought_signature_validator",
},
],
},
])
})
it("should NOT include thoughtSignature for tool_use when includeThoughtSignatures is false", () => {
const anthropicMessage: Anthropic.Messages.MessageParam = {
role: "assistant",
content: [
{ type: "text", text: "Let me calculate that for you." },
{
type: "tool_use",
id: "calc-123",
name: "calculator",
input: { operation: "add", numbers: [2, 3] },
},
],
}
// With includeThoughtSignatures false, no signature should be included
const result = convertAnthropicMessageToGemini(anthropicMessage, { includeThoughtSignatures: false })
expect(result).toEqual([
{
role: "model",
parts: [
{ text: "Let me calculate that for you." },
{
functionCall: {
name: "calculator",
args: { operation: "add", numbers: [2, 3] },
},
},
],
},
])
})
it("should use real thoughtSignature when present in content", () => {
const anthropicMessage: Anthropic.Messages.MessageParam = {
role: "assistant",
content: [
{ type: "text", text: "Let me calculate that for you." },
{
type: "tool_use",
id: "calc-123",
name: "calculator",
input: { operation: "add", numbers: [2, 3] },
},
{ type: "thoughtSignature", thoughtSignature: "real-signature-abc123" } as any,
],
}
const result = convertAnthropicMessageToGemini(anthropicMessage)
expect(result).toEqual([
@ -133,6 +201,7 @@ describe("convertAnthropicMessageToGemini", () => {
name: "calculator",
args: { operation: "add", numbers: [2, 3] },
},
thoughtSignature: "real-signature-abc123",
},
],
},

View file

@ -30,23 +30,34 @@ export function convertAnthropicContentToGemini(
const includeThoughtSignatures = options?.includeThoughtSignatures ?? true
const toolIdToName = options?.toolIdToName
// First pass: find thoughtSignature if it exists in the content blocks
// First pass: find thoughtSignature and check for tool_use blocks
let activeThoughtSignature: string | undefined
let hasToolUseBlocks = false
if (Array.isArray(content)) {
const sigBlock = content.find((block) => isThoughtSignatureContentBlock(block)) as ThoughtSignatureContentBlock
if (sigBlock?.thoughtSignature) {
activeThoughtSignature = sigBlock.thoughtSignature
}
// Check if this message contains tool_use blocks
hasToolUseBlocks = content.some((block) => "type" in block && (block as { type: string }).type === "tool_use")
}
// Determine the signature to attach to function calls.
// If we're in a mode that expects signatures (includeThoughtSignatures is true):
// 1. Use the actual signature if we found one in the history/content.
// 2. Don't use a fallback - only include thoughtSignature if we have a real one.
// The fallback "skip_thought_signature_validator" causes 400 errors with Gemini 3 models.
// 2. If there are tool_use blocks but no signature (cross-model history scenario),
// use the fallback "skip_thought_signature_validator" to satisfy Gemini 3's validation.
// See: https://ai.google.dev/gemini-api/docs/thought-signatures#faqs
// 3. If there are no tool_use blocks, don't include any signature (nothing to attach it to).
let functionCallSignature: string | undefined
if (includeThoughtSignatures && activeThoughtSignature) {
functionCallSignature = activeThoughtSignature
if (includeThoughtSignatures) {
if (activeThoughtSignature) {
functionCallSignature = activeThoughtSignature
} else if (hasToolUseBlocks) {
// Cross-model scenario: tool_use blocks exist but no thoughtSignature was captured.
// This happens when switching from a non-thinking model to a thinking model.
functionCallSignature = "skip_thought_signature_validator"
}
}
if (typeof content === "string") {