Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions src/api/providers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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") {
Expand All @@ -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],
}
}
}
Expand Down
Loading