Skip to content
Open
Show file tree
Hide file tree
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
1 change: 1 addition & 0 deletions packages/types/src/provider-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down
73 changes: 73 additions & 0 deletions src/api/providers/__tests__/openrouter.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})
})
61 changes: 43 additions & 18 deletions src/api/providers/openrouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,15 +317,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,
},
}),
...(this.buildProviderOptions() && { provider: this.buildProviderOptions() }),
...(reasoning && { reasoning }),
tools: this.convertToolsForOpenAI(metadata?.tools),
tool_choice: metadata?.tool_choice,
Expand Down Expand Up @@ -574,6 +566,47 @@ 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()

Expand All @@ -583,15 +616,7 @@ export class OpenRouterHandler extends BaseProvider implements SingleCompletionH
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,
},
}),
...(this.buildProviderOptions() && { provider: this.buildProviderOptions() }),
...(reasoning && { reasoning }),
}

Expand Down
12 changes: 12 additions & 0 deletions webview-ui/src/components/settings/providers/OpenRouter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ export const OpenRouter = ({
)}
</div>
)}
<div>
<Checkbox
checked={apiConfiguration?.openRouterExcludeLowQuantization ?? false}
onChange={(checked: boolean) => {
setApiConfigurationField("openRouterExcludeLowQuantization", checked)
}}>
{t("settings:providers.openRouter.excludeLowQuantization.label")}
</Checkbox>
<div className="text-sm text-vscode-descriptionForeground mt-1 ml-6">
{t("settings:providers.openRouter.excludeLowQuantization.description")}
</div>
</div>
<ModelPicker
apiConfiguration={apiConfiguration}
setApiConfigurationField={setApiConfigurationField}
Expand Down
4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/ca/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/de/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/en/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,10 @@
"title": "OpenRouter Provider Routing",
"description": "OpenRouter routes requests to the best available providers for your model. By default, requests are load balanced across the top providers to maximize uptime. However, you can choose a specific provider to use for this model.",
"learnMore": "Learn more about provider routing"
},
"excludeLowQuantization": {
"label": "Exclude low-bit quantization (FP4/FP6/Int4)",
"description": "Only allow higher precision providers (FP8/FP16/BF16/Int8). Helps prevent broken CJK (Korean/Chinese/Japanese) encoding from aggressively quantized models."
}
},
"customModel": {
Expand Down
4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/es/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/fr/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/hi/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/id/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/it/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/ja/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/ko/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/nl/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/pl/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/pt-BR/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/ru/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions webview-ui/src/i18n/locales/tr/settings.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading