Skip to content
This repository was archived by the owner on May 15, 2026. It is now read-only.

Commit dc07aaa

Browse files
committed
fix: remove skip_thought_signature_validator bypass for Gemini 3.1+ models
Gemini 3.1+ models reject the synthetic "skip_thought_signature_validator" string that was injected as a fallback when no real thought signature was available. This caused 400 errors ("Thought signature is not valid") after tool calls. Instead of falling back to the bypass string, omit the thoughtSignature field entirely when no real signature exists. This is safe because: - Real signatures from the model are still preserved and round-tripped - Function calls without signatures are accepted by the API - The bypass was only needed for Gemini 3 cross-model history, which works fine without it Closes #12050
1 parent 137d3f4 commit dc07aaa

2 files changed

Lines changed: 40 additions & 5 deletions

File tree

src/api/transform/__tests__/gemini-format.spec.ts

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ describe("convertAnthropicMessageToGemini", () => {
107107
expect(() => convertAnthropicMessageToGemini(anthropicMessage)).toThrow("Unsupported image source type")
108108
})
109109

110-
it("should convert a message with tool use", () => {
110+
it("should convert a message with tool use (no thought signature in history)", () => {
111111
const anthropicMessage: Anthropic.Messages.MessageParam = {
112112
role: "assistant",
113113
content: [
@@ -133,7 +133,40 @@ describe("convertAnthropicMessageToGemini", () => {
133133
name: "calculator",
134134
args: { operation: "add", numbers: [2, 3] },
135135
},
136-
thoughtSignature: "skip_thought_signature_validator",
136+
},
137+
],
138+
},
139+
])
140+
})
141+
142+
it("should attach thoughtSignature to functionCall when a real signature exists", () => {
143+
const anthropicMessage: Anthropic.Messages.MessageParam = {
144+
role: "assistant",
145+
content: [
146+
{ type: "thoughtSignature", thoughtSignature: "real-sig-abc" } as any,
147+
{ type: "text", text: "Let me calculate that for you." },
148+
{
149+
type: "tool_use",
150+
id: "calc-123",
151+
name: "calculator",
152+
input: { operation: "add", numbers: [2, 3] },
153+
},
154+
],
155+
}
156+
157+
const result = convertAnthropicMessageToGemini(anthropicMessage)
158+
159+
expect(result).toEqual([
160+
{
161+
role: "model",
162+
parts: [
163+
{ text: "Let me calculate that for you." },
164+
{
165+
functionCall: {
166+
name: "calculator",
167+
args: { operation: "add", numbers: [2, 3] },
168+
},
169+
thoughtSignature: "real-sig-abc",
137170
},
138171
],
139172
},

src/api/transform/gemini-format.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,13 @@ export function convertAnthropicContentToGemini(
4141

4242
// Determine the signature to attach to function calls.
4343
// If we're in a mode that expects signatures (includeThoughtSignatures is true):
44-
// 1. Use the actual signature if we found one in the history/content.
45-
// 2. Fallback to "skip_thought_signature_validator" if missing (e.g. cross-model history).
44+
// - Use the actual signature if we found one in the history/content.
45+
// - If no real signature exists, omit it (undefined) so the API accepts the request.
46+
// Gemini 3.1+ models reject the synthetic "skip_thought_signature_validator" bypass
47+
// that worked with earlier Gemini 3 models.
4648
let functionCallSignature: string | undefined
4749
if (includeThoughtSignatures) {
48-
functionCallSignature = activeThoughtSignature || "skip_thought_signature_validator"
50+
functionCallSignature = activeThoughtSignature
4951
}
5052

5153
if (typeof content === "string") {

0 commit comments

Comments
 (0)