diff --git a/packages/types/src/provider-settings.ts b/packages/types/src/provider-settings.ts index 859792d7c3..c38b4e75be 100644 --- a/packages/types/src/provider-settings.ts +++ b/packages/types/src/provider-settings.ts @@ -206,6 +206,7 @@ const openRouterSchema = baseProviderSettingsSchema.extend({ openRouterModelId: z.string().optional(), openRouterBaseUrl: z.string().optional(), openRouterSpecificProvider: z.string().optional(), + openRouterExcludeLowQuantization: z.boolean().optional(), }) const bedrockSchema = apiModelIdProviderModelSchema.extend({ diff --git a/src/api/providers/__tests__/openrouter.spec.ts b/src/api/providers/__tests__/openrouter.spec.ts index e03abea635..6797ca588b 100644 --- a/src/api/providers/__tests__/openrouter.spec.ts +++ b/src/api/providers/__tests__/openrouter.spec.ts @@ -698,4 +698,77 @@ describe("OpenRouterHandler", () => { ) }) }) + + describe("buildProviderOptions", () => { + it("returns undefined when no specific provider and no quantization filter", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "anthropic/claude-sonnet-4", + }) + const result = (handler as any).buildProviderOptions() + expect(result).toBeUndefined() + }) + + it("returns provider routing when specific provider is set", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "anthropic/claude-sonnet-4", + openRouterSpecificProvider: "Anthropic", + }) + const result = (handler as any).buildProviderOptions() + expect(result).toEqual({ + order: ["Anthropic"], + only: ["Anthropic"], + allow_fallbacks: false, + }) + }) + + it("returns quantizations when excludeLowQuantization is enabled", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "anthropic/claude-sonnet-4", + openRouterExcludeLowQuantization: true, + }) + const result = (handler as any).buildProviderOptions() + expect(result).toEqual({ + quantizations: ["fp16", "bf16", "fp8", "int8"], + }) + }) + + it("combines specific provider and quantization filter", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "anthropic/claude-sonnet-4", + openRouterSpecificProvider: "Anthropic", + openRouterExcludeLowQuantization: true, + }) + const result = (handler as any).buildProviderOptions() + expect(result).toEqual({ + order: ["Anthropic"], + only: ["Anthropic"], + allow_fallbacks: false, + quantizations: ["fp16", "bf16", "fp8", "int8"], + }) + }) + + it("returns undefined when specific provider is the default", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "anthropic/claude-sonnet-4", + openRouterSpecificProvider: "[default]", + }) + const result = (handler as any).buildProviderOptions() + expect(result).toBeUndefined() + }) + + it("returns undefined when excludeLowQuantization is false", () => { + const handler = new OpenRouterHandler({ + openRouterApiKey: "test-key", + openRouterModelId: "anthropic/claude-sonnet-4", + openRouterExcludeLowQuantization: false, + }) + const result = (handler as any).buildProviderOptions() + expect(result).toBeUndefined() + }) + }) }) diff --git a/src/api/providers/openrouter.ts b/src/api/providers/openrouter.ts index 7fcc24b15f..ef82cf2537 100644 --- a/src/api/providers/openrouter.ts +++ b/src/api/providers/openrouter.ts @@ -309,6 +309,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH } // https://openrouter.ai/docs/transforms + const providerOptions = this.buildProviderOptions() const completionParams: OpenRouterChatCompletionParams = { model: modelId, ...(maxTokens && maxTokens > 0 && { max_tokens: maxTokens }), @@ -317,15 +318,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH messages: openAiMessages, stream: true, stream_options: { include_usage: true }, - // Only include provider if openRouterSpecificProvider is not "[default]". - ...(this.options.openRouterSpecificProvider && - this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME && { - provider: { - order: [this.options.openRouterSpecificProvider], - only: [this.options.openRouterSpecificProvider], - allow_fallbacks: false, - }, - }), + ...(providerOptions && { provider: providerOptions }), ...(reasoning && { reasoning }), tools: this.convertToolsForOpenAI(metadata?.tools), tool_choice: metadata?.tool_choice, @@ -574,24 +567,58 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH return { id, info, topP: isDeepSeekR1 ? 0.95 : undefined, ...params } } + /** + * Build the `provider` options object for OpenRouter requests. + * Combines specific provider routing and quantization filtering. + */ + private buildProviderOptions(): + | { + order?: string[] + only?: string[] + allow_fallbacks?: boolean + quantizations?: string[] + } + | undefined { + const hasSpecificProvider = + this.options.openRouterSpecificProvider && + this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME + const excludeLowQuantization = this.options.openRouterExcludeLowQuantization + + if (!hasSpecificProvider && !excludeLowQuantization) { + return undefined + } + + const provider: { + order?: string[] + only?: string[] + allow_fallbacks?: boolean + quantizations?: string[] + } = {} + + if (hasSpecificProvider) { + provider.order = [this.options.openRouterSpecificProvider!] + provider.only = [this.options.openRouterSpecificProvider!] + provider.allow_fallbacks = false + } + + if (excludeLowQuantization) { + provider.quantizations = ["fp16", "bf16", "fp8", "int8"] + } + + return provider + } + async completePrompt(prompt: string) { let { id: modelId, maxTokens, temperature, reasoning } = await this.fetchModel() + const providerOptions = this.buildProviderOptions() const completionParams: OpenRouterChatCompletionParams = { model: modelId, max_tokens: maxTokens, temperature, messages: [{ role: "user", content: prompt }], stream: false, - // Only include provider if openRouterSpecificProvider is not "[default]". - ...(this.options.openRouterSpecificProvider && - this.options.openRouterSpecificProvider !== OPENROUTER_DEFAULT_PROVIDER_NAME && { - provider: { - order: [this.options.openRouterSpecificProvider], - only: [this.options.openRouterSpecificProvider], - allow_fallbacks: false, - }, - }), + ...(providerOptions && { provider: providerOptions }), ...(reasoning && { reasoning }), } diff --git a/webview-ui/src/components/settings/providers/OpenRouter.tsx b/webview-ui/src/components/settings/providers/OpenRouter.tsx index 2dba8c8459..8ada9cc6bb 100644 --- a/webview-ui/src/components/settings/providers/OpenRouter.tsx +++ b/webview-ui/src/components/settings/providers/OpenRouter.tsx @@ -103,6 +103,18 @@ export const OpenRouter = ({ )} )} +
+ { + setApiConfigurationField("openRouterExcludeLowQuantization", checked) + }}> + {t("settings:providers.openRouter.excludeLowQuantization.label")} + +
+ {t("settings:providers.openRouter.excludeLowQuantization.description")} +
+