Skip to content
Open
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
30 changes: 28 additions & 2 deletions src/api/providers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ type OpenRouterChatCompletionParams = OpenAI.Chat.ChatCompletionCreateParams & {
include_reasoning?: boolean
// https://openrouter.ai/docs/use-cases/reasoning-tokens
reasoning?: OpenRouterReasoningParams
// OpenRouter vendor-specific additional body for special templates (e.g., DeepSeek V3.1 Terminus)
extra_body?: Record<string, unknown>
}

// See `OpenAI.Chat.Completions.ChatCompletionChunk["usage"]`
Expand Down Expand Up @@ -140,6 +142,16 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
}

const transforms = (this.options.openRouterUseMiddleOutTransform ?? true) ? ["middle-out"] : undefined
const isDeepSeekV31Terminus = modelId.includes("DeepSeek-V3.1-Terminus")
// DeepSeek V3.1 Terminus uses chat_template_kwargs.thinking via extra_body on OpenRouter
const extra_body = isDeepSeekV31Terminus
? {
chat_template_kwargs: {
// Default OFF unless explicitly requested via reasoning settings
thinking: Boolean(reasoning) && !(reasoning as any)?.exclude,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type assertion (reasoning as any)?.exclude is unnecessary. Since reasoning is of type OpenRouterReasoningParams | undefined and OpenRouterReasoningParams already has exclude?: boolean as a property, you can safely use reasoning?.exclude instead. Removing the as any cast improves type safety and maintainability.

Suggested change
thinking: Boolean(reasoning) && !(reasoning as any)?.exclude,
thinking: Boolean(reasoning) && !reasoning?.exclude,

},
}
: undefined

// https://openrouter.ai/docs/transforms
const completionParams: OpenRouterChatCompletionParams = {
Expand All @@ -160,7 +172,9 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
},
}),
...(transforms && { transforms }),
...(reasoning && { reasoning }),
...(extra_body && { extra_body }),
// Do not pass OpenRouter "reasoning" param for DeepSeek V3.1 Terminus; use extra_body instead
...(!isDeepSeekV31Terminus && reasoning ? { reasoning } : {}),
}

let stream
Expand Down Expand Up @@ -248,6 +262,16 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
async completePrompt(prompt: string) {
let { id: modelId, maxTokens, temperature, reasoning } = await this.fetchModel()

const isDeepSeekV31Terminus = modelId.includes("DeepSeek-V3.1-Terminus")
const extra_body = isDeepSeekV31Terminus
? {
chat_template_kwargs: {
// Default OFF unless explicitly requested via reasoning settings
thinking: Boolean(reasoning) && !(reasoning as any)?.exclude,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The type assertion (reasoning as any)?.exclude is unnecessary here as well. Since reasoning is of type OpenRouterReasoningParams | undefined and OpenRouterReasoningParams already has exclude?: boolean as a property, you can safely use reasoning?.exclude instead.

Suggested change
thinking: Boolean(reasoning) && !(reasoning as any)?.exclude,
thinking: Boolean(reasoning) && !reasoning?.exclude,

},
}
: undefined

const completionParams: OpenRouterChatCompletionParams = {
model: modelId,
max_tokens: maxTokens,
Expand All @@ -263,7 +287,9 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
allow_fallbacks: false,
},
}),
...(reasoning && { reasoning }),
...(extra_body && { extra_body }),
// Do not pass OpenRouter "reasoning" param for DeepSeek V3.1 Terminus; use extra_body instead
...(!isDeepSeekV31Terminus && reasoning ? { reasoning } : {}),
}

let response
Expand Down