mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
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:
parent
be8ac86165
commit
a53db60a0d
2 changed files with 86 additions and 6 deletions
|
|
@ -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",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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") {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue