fix: correct Gemini 3 thought signature injection format via OpenRouter (#10640)

This commit is contained in:
Daniel 2026-01-12 17:05:00 -05:00 committed by GitHub
parent 55b732485b
commit f439496147
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -220,7 +220,8 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
// even if you don't request them. This is not the default for
// other providers (including Gemini), so we need to explicitly disable
// them unless the user has explicitly configured reasoning.
// Note: Gemini 3 models use reasoning_details format and should not be excluded.
// Note: Gemini 3 models use reasoning_details format with thought signatures,
// but we handle this via skip_thought_signature_validator injection below.
if (
(modelId === "google/gemini-2.5-pro-preview" || modelId === "google/gemini-2.5-pro") &&
typeof reasoning === "undefined"
@ -250,8 +251,13 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
const isNativeProtocol = toolProtocol === TOOL_PROTOCOL.NATIVE
const isGemini = modelId.startsWith("google/gemini")
// For Gemini with native protocol: inject fake reasoning.encrypted blocks for tool calls
// This is required when switching from other models to Gemini to satisfy API validation
// For Gemini with native protocol: inject fake reasoning.encrypted block for tool calls
// This is required when switching from other models to Gemini to satisfy API validation.
// Per OpenRouter documentation (conversation with Toven, Nov 2025):
// - Create ONE reasoning_details entry per assistant message with tool calls
// - Set `id` to the FIRST tool call's ID from the tool_calls array
// - Set `data` to "skip_thought_signature_validator" to bypass signature validation
// - Set `index` to 0
if (isNativeProtocol && isGemini) {
openAiMessages = openAiMessages.map((msg) => {
if (msg.role === "assistant") {
@ -263,17 +269,19 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
const hasEncrypted = existingDetails?.some((d) => d.type === "reasoning.encrypted") ?? false
if (!hasEncrypted) {
const fakeEncrypted = toolCalls.map((tc, idx) => ({
id: tc.id,
// Create ONE fake encrypted block with the FIRST tool call's ID
// This is the documented format from OpenRouter for skipping thought signature validation
const fakeEncrypted = {
type: "reasoning.encrypted",
data: "skip_thought_signature_validator",
id: toolCalls[0].id,
format: "google-gemini-v1",
index: (existingDetails?.length ?? 0) + idx,
}))
index: 0,
}
return {
...msg,
reasoning_details: [...(existingDetails ?? []), ...fakeEncrypted],
reasoning_details: [...(existingDetails ?? []), fakeEncrypted],
}
}
}