From 4fe0e2eb31291037719d88037c35c4826ca30a34 Mon Sep 17 00:00:00 2001 From: Christiaan Arnoldus Date: Wed, 5 Aug 2026 14:34:45 +0200 Subject: [PATCH] refactor(ai-gateway): derive model variants from catalog --- .../lib/ai-gateway/auto-model/resolution.ts | 16 +- .../providers/direct-byok/byteplus-coding.ts | 8 +- .../providers/direct-byok/index.test.ts | 13 +- .../ai-gateway/providers/direct-byok/index.ts | 5 +- .../providers/direct-byok/sync-direct-byok.ts | 8 +- .../ai-gateway/providers/model-settings.ts | 159 +++++------------- .../ai-gateway/providers/openrouter/index.ts | 2 +- .../ai-gateway/providers/openrouter/types.ts | 2 +- .../src/lib/ai-gateway/providers/variants.ts | 126 ++++++++++++++ .../lib/code-reviews/core/model-variants.ts | 4 +- 10 files changed, 194 insertions(+), 149 deletions(-) create mode 100644 apps/web/src/lib/ai-gateway/providers/variants.ts diff --git a/apps/web/src/lib/ai-gateway/auto-model/resolution.ts b/apps/web/src/lib/ai-gateway/auto-model/resolution.ts index cae60d5981..44d3d04ea5 100644 --- a/apps/web/src/lib/ai-gateway/auto-model/resolution.ts +++ b/apps/web/src/lib/ai-gateway/auto-model/resolution.ts @@ -42,7 +42,6 @@ import { } from '@/lib/organizations/organization-auto-model'; import { getModelVariants } from '@/lib/ai-gateway/providers/model-settings'; import type { OpenCodeVariant } from '@kilocode/db/schema-types'; -import type { OpenRouterReasoningConfig } from '@/lib/ai-gateway/providers/openrouter/types'; type ResolveAutoModelParams = { model: string; @@ -238,23 +237,20 @@ async function resolveOrganizationAutoModel( * falls back to balanced rather than serving implicit defaults. When `variant` * is absent, preserve legacy effort-only behavior for rolling deploys. */ -function resolveEfficientDecisionModel(decision: AutoRoutingDecision): ResolvedAutoModel | null { +async function resolveEfficientDecisionModel( + decision: AutoRoutingDecision +): Promise { // `variant` is only on the benchmark decision branch of the discriminated // union; coding-plan defaults never carry it. if ('variant' in decision && decision.variant != null) { - const variants = getModelVariants(decision.model); + const variants = await getModelVariants(decision.model); const variantSettings: OpenCodeVariant | undefined = variants?.[decision.variant]; if (!variantSettings) { return null; } - // Catalog variants are the source of truth; cast into ResolvedAutoModel's - // OpenRouter-shaped fields (catalog effort may include values like `max` - // beyond ChatCompletionReasoningEffort). return { model: decision.model, - ...(variantSettings.reasoning - ? { reasoning: { ...variantSettings.reasoning } as OpenRouterReasoningConfig } - : {}), + ...(variantSettings.reasoning ? { reasoning: { ...variantSettings.reasoning } } : {}), ...(variantSettings.verbosity ? { verbosity: variantSettings.verbosity } : {}), }; } @@ -307,7 +303,7 @@ export async function resolveAutoModel( if (model === KILO_AUTO_EFFICIENT_MODEL.id || model === KILO_AUTO_BALANCED_MODEL.id) { const decision = params.efficientDecision ? await params.efficientDecision() : null; if (decision && !isVirtualAutoModelId(decision.model)) { - const resolvedFromDecision = resolveEfficientDecisionModel(decision); + const resolvedFromDecision = await resolveEfficientDecisionModel(decision); if (resolvedFromDecision) { return { kind: 'ok', resolved: resolvedFromDecision }; } diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/byteplus-coding.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/byteplus-coding.ts index 89704437b1..9429a9c5f2 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/byteplus-coding.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/byteplus-coding.ts @@ -1,12 +1,12 @@ -import { - REASONING_VARIANTS_BINARY, - REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH, -} from '@/lib/ai-gateway/providers/model-settings'; import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; import type { DirectByokModel, DirectByokProvider, } from '@/lib/ai-gateway/providers/direct-byok/types'; +import { + REASONING_VARIANTS_BINARY, + REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH, +} from '@/lib/ai-gateway/providers/variants'; export const BYTEPLUS_CODING_PROVIDER_ID = 'byteplus-coding'; diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/index.test.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/index.test.ts index 21a4f6c060..8bdd9b9ab9 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/index.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/index.test.ts @@ -15,7 +15,10 @@ jest.mock('@/lib/ai-gateway/models', () => ({ jest.mock('@/lib/ai-gateway/providers/model-settings', () => ({ getAiSdkProvider: jest.fn(), - getModelVariants: jest.fn(), +})); + +jest.mock('@/lib/ai-gateway/providers/variants', () => ({ + getFallbackModelVariants: jest.fn(), })); jest.mock('./direct-byok-definitions', () => ({ @@ -124,12 +127,12 @@ describe('getDirectByokModel', () => { test('falls back to model-name variants when synced variants are unavailable', async () => { const { getDirectByokModelsForUser } = await loadDirectByokModule(); const { getBYOKforUser } = await import('@/lib/ai-gateway/byok'); - const { getModelVariants } = await import('@/lib/ai-gateway/providers/model-settings'); + const { getFallbackModelVariants } = await import('@/lib/ai-gateway/providers/variants'); const fallback = { thinking: { reasoning: { enabled: true, effort: 'high' as const } } }; jest .mocked(getBYOKforUser) .mockResolvedValueOnce([{ providerId: 'chutes-byok', decryptedAPIKey: 'test-key' }]); - jest.mocked(getModelVariants).mockReturnValue(fallback); + jest.mocked(getFallbackModelVariants).mockReturnValue(fallback); const models = await getDirectByokModelsForUser('user-id'); @@ -137,7 +140,7 @@ describe('getDirectByokModel', () => { high: { reasoning: { enabled: true, effort: 'high' } }, }); expect(models[1].opencode.variants).toBe(fallback); - expect(getModelVariants).toHaveBeenCalledTimes(1); - expect(getModelVariants).toHaveBeenCalledWith('chutes-byok/non-reasoning-model'); + expect(getFallbackModelVariants).toHaveBeenCalledTimes(1); + expect(getFallbackModelVariants).toHaveBeenCalledWith('chutes-byok/non-reasoning-model'); }); }); diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/index.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/index.ts index fed7b39ffe..7234190b66 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/index.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/index.ts @@ -11,7 +11,8 @@ import { readDb } from '@/lib/drizzle'; import { preferredModels } from '@/lib/ai-gateway/models'; import { createOpenAICompatible } from '@ai-sdk/openai-compatible'; import type { OpenCodeSettings } from '@kilocode/db'; -import { getAiSdkProvider, getModelVariants } from '@/lib/ai-gateway/providers/model-settings'; +import { getAiSdkProvider } from '@/lib/ai-gateway/providers/model-settings'; +import { getFallbackModelVariants } from '@/lib/ai-gateway/providers/variants'; export function formatDirectByokModelId(provider: DirectByokProvider, model: DirectByokModel) { return (provider.id + '/' + model.id).toLowerCase(); @@ -62,7 +63,7 @@ function convertModel( hasUserByokAvailable: true, opencode: { ai_sdk_provider: getAiSdkProvider(id, provider.id) ?? provider.default_ai_sdk_provider, - variants: model.variants ?? getModelVariants(id), + variants: model.variants ?? getFallbackModelVariants(id), } satisfies OpenCodeSettings, }; } diff --git a/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts b/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts index ce64f1f891..fbc668b683 100644 --- a/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts +++ b/apps/web/src/lib/ai-gateway/providers/direct-byok/sync-direct-byok.ts @@ -11,11 +11,11 @@ import { VerbositySchema, type OpenCodeSettings, } from '@kilocode/db/schema-types'; +import { getAiSdkProvider } from '@/lib/ai-gateway/providers/model-settings'; import { - getAiSdkProvider, - getModelVariants, + getFallbackModelVariants, REASONING_VARIANTS_BINARY, -} from '@/lib/ai-gateway/providers/model-settings'; +} from '@/lib/ai-gateway/providers/variants'; const DEFAULT_CONTENT_LENGTH = 200_000; const DEFAULT_MAX_COMPLETION_TOKENS = 32_000; @@ -196,7 +196,7 @@ export function parseModelsDevProviderModels( const aiSdkProvider = getAiSdkProvider(modelId, providerId); const variants = modelsDevReasoningOptionsToVariants(model.reasoning_options ?? []) ?? - getModelVariants(modelId); + getFallbackModelVariants(modelId); return { id: model.id, name: shortenDisplayName(model.name), diff --git a/apps/web/src/lib/ai-gateway/providers/model-settings.ts b/apps/web/src/lib/ai-gateway/providers/model-settings.ts index 40cd36ceb3..db02f7fe0b 100644 --- a/apps/web/src/lib/ai-gateway/providers/model-settings.ts +++ b/apps/web/src/lib/ai-gateway/providers/model-settings.ts @@ -1,134 +1,51 @@ import { isClaudeModel } from '@/lib/ai-gateway/providers/anthropic.constants'; -import { isGemini3Model, isGemmaModel } from '@/lib/ai-gateway/providers/google'; -import { isKimiModel } from '@/lib/ai-gateway/providers/moonshotai'; import { isOpenAiModel } from '@/lib/ai-gateway/providers/openai'; import { isQwenModel } from '@/lib/ai-gateway/providers/qwen'; -import { isGrokModel, isGrok42Model, isGrok45Model } from '@/lib/ai-gateway/providers/xai'; -import { isGlmModel } from '@/lib/ai-gateway/providers/zai'; +import { isGrokModel } from '@/lib/ai-gateway/providers/xai'; import type { CustomLlmProvider, OpenCodePrompt, OpenCodeSettings, + OpenCodeVariant, } from '@kilocode/db/schema-types'; -import { isStepModel } from '@/lib/ai-gateway/providers/stepfun'; -import { ReasoningEffortSchema } from '@kilocode/db/schema-types'; -import { isDeepseekModel } from '@/lib/ai-gateway/providers/deepseek'; +import { VerbositySchema } from '@kilocode/db/schema-types'; import { isMinimaxModel } from '@/lib/ai-gateway/providers/minimax'; import type { DirectUserByokInferenceProviderId } from '@/lib/ai-gateway/providers/openrouter/inference-provider-id'; import { isMuseModel } from '@/lib/ai-gateway/providers/meta'; +import { getOpenRouterModelsMetadataFromDatabase } from '@/lib/ai-gateway/providers/gateway-models-cache'; +import { + getFallbackModelVariants, + REASONING_VARIANTS_THINKING_ONLY, + REASONING_VARIANTS_BINARY, +} from '@/lib/ai-gateway/providers/variants'; -const REASONING_VARIANTS_THINKING_ONLY = { - thinking: { reasoning: { enabled: true, effort: 'high' } }, -} as const; - -export const REASONING_VARIANTS_BINARY = { - instant: { reasoning: { enabled: false, effort: 'none' } }, - ...REASONING_VARIANTS_THINKING_ONLY, -} as const; - -export const REASONING_VARIANTS_LOW_MEDIUM_HIGH = { - low: { reasoning: { enabled: true, effort: 'low' } }, - medium: { reasoning: { enabled: true, effort: 'medium' } }, - high: { reasoning: { enabled: true, effort: 'high' } }, -} as const; - -export const REASONING_VARIANTS_MAX_HIGH_LOW = { - max: { reasoning: { enabled: true, effort: 'max' } }, - high: { reasoning: { enabled: true, effort: 'high' } }, - low: { reasoning: { enabled: true, effort: 'low' } }, -} as const; - -export const REASONING_VARIANTS_XHIGH_MEDIUM_LOW = { - xhigh: { reasoning: { enabled: true, effort: 'xhigh' } }, - medium: { reasoning: { enabled: true, effort: 'medium' } }, - low: { reasoning: { enabled: true, effort: 'low' } }, -} as const; - -export const REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH = { - minimal: { reasoning: { enabled: true, effort: 'minimal' } }, - ...REASONING_VARIANTS_LOW_MEDIUM_HIGH, -} as const; - -export const REASONING_VARIANTS_NONE_MINIMAL_LOW_MEDIUM_HIGH = { - none: { reasoning: { enabled: false, effort: 'none' } }, - ...REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH, -} as const; - -export const REASONING_VARIANTS_NONE_HIGH_XHIGH = { - none: { reasoning: { enabled: false, effort: 'none' } }, - high: { reasoning: { enabled: true, effort: 'high' } }, - xhigh: { reasoning: { enabled: true, effort: 'xhigh' } }, -} as const; - -const REASONING_VARIANTS_CLAUDE = { - none: { reasoning: { enabled: false, effort: 'none' } }, - low: { reasoning: { enabled: true, effort: 'low' }, verbosity: 'low' }, - medium: { reasoning: { enabled: true, effort: 'medium' }, verbosity: 'medium' }, - high: { reasoning: { enabled: true, effort: 'high' }, verbosity: 'high' }, - xhigh: { reasoning: { enabled: true, effort: 'xhigh' }, verbosity: 'xhigh' }, - max: { reasoning: { enabled: true, effort: 'xhigh' }, verbosity: 'max' }, -} as const; - -export const REASONING_VARIANTS_INSTANT_LOW_MEDIUM_HIGH = { - instant: REASONING_VARIANTS_BINARY.instant, - ...REASONING_VARIANTS_LOW_MEDIUM_HIGH, -} as const; +export async function getOpenRouterDerivedModelVariants( + model: string +): Promise { + const models = await getOpenRouterModelsMetadataFromDatabase(); + const reasoning = models[model]?.reasoning; + if (!reasoning) { + return undefined; + } + if (!reasoning.supported_efforts?.length) { + return reasoning.mandatory ? REASONING_VARIANTS_THINKING_ONLY : REASONING_VARIANTS_BINARY; + } + const useAnthropicProvider = getAiSdkProvider(model, null) === 'anthropic'; + const variants: [string, OpenCodeVariant][] = reasoning.supported_efforts.map(effort => [ + effort, + { + reasoning: { enabled: true, effort }, + verbosity: useAnthropicProvider ? VerbositySchema.safeParse(effort).data : undefined, + }, + ]); + if (!reasoning.mandatory) { + variants.push(['none', { reasoning: { enabled: false, effort: 'none' } }]); + } + return Object.fromEntries(variants); +} -export function getModelVariants(model: string): OpenCodeSettings['variants'] { - if (isClaudeModel(model)) { - return REASONING_VARIANTS_CLAUDE; - } - if (model.includes('codex') || isGemini3Model(model)) { - return Object.fromEntries( - ReasoningEffortSchema.options - .filter(e => e !== 'none' && e !== 'minimal' && e !== 'max') - .map(effort => [effort, { reasoning: { enabled: true, effort } }]) - ); - } - if (isOpenAiModel(model)) { - return Object.fromEntries( - ReasoningEffortSchema.options - .filter(e => e !== 'minimal') - .map(effort => [effort, { reasoning: { enabled: effort !== 'none', effort } }]) - ); - } - if (model.includes('mistral-medium-3-5')) { - return REASONING_VARIANTS_BINARY; - } - if (model.includes('kimi-k2.7-code')) { - return REASONING_VARIANTS_THINKING_ONLY; - } - if (model.includes('kimi-k2')) { - return REASONING_VARIANTS_BINARY; - } - if (isKimiModel(model)) { - return REASONING_VARIANTS_MAX_HIGH_LOW; - } - if (model.includes('qwen3.8') && (model.includes('plus') || model.includes('max'))) { - return REASONING_VARIANTS_XHIGH_MEDIUM_LOW; - } - if ( - isMinimaxModel(model) || - isGrok42Model(model) || - isQwenModel(model) || - isGemmaModel(model) || - model.includes('mimo') - ) { - return REASONING_VARIANTS_BINARY; - } - if (model.startsWith('inception/mercury-2')) { - return REASONING_VARIANTS_INSTANT_LOW_MEDIUM_HIGH; - } - if (isStepModel(model) || isGrok45Model(model)) { - return REASONING_VARIANTS_LOW_MEDIUM_HIGH; - } - if (isDeepseekModel(model) || isGlmModel(model)) { - return REASONING_VARIANTS_NONE_HIGH_XHIGH; - } - if (isMuseModel(model)) { - return REASONING_VARIANTS_NONE_MINIMAL_LOW_MEDIUM_HIGH; - } - return undefined; +export async function getModelVariants(model: string): Promise { + return (await getOpenRouterDerivedModelVariants(model)) ?? getFallbackModelVariants(model); } export function getAiSdkProvider( @@ -162,9 +79,11 @@ function getOpenCodePrompt(model: string): OpenCodePrompt | undefined { return undefined; } -export function getGatewayOpenCodeSettings(model: string): OpenCodeSettings | undefined { +export async function getGatewayOpenCodeSettings( + model: string +): Promise { const ai_sdk_provider = getAiSdkProvider(model, null); - const variants = getModelVariants(model); + const variants = await getModelVariants(model); const prompt = getOpenCodePrompt(model); return { ai_sdk_provider, variants, prompt }; } diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts index 1f789a8f82..242afc3381 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/index.ts @@ -165,7 +165,7 @@ async function enhancedModelList(models: OpenRouterModel[]) { preferredIndex: preferredIndex >= 0 ? preferredIndex : undefined, isFree: model.isFree ?? isFree, mayTrainOnYourPrompts: model.mayTrainOnYourPrompts ?? isFree, - opencode: model.opencode ?? getGatewayOpenCodeSettings(model.id), + opencode: model.opencode ?? (await getGatewayOpenCodeSettings(model.id)), architecture: addPdf ? { ...model.architecture, diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/types.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/types.ts index 00ad400ef4..989e549031 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/types.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/types.ts @@ -37,7 +37,7 @@ export function isDataCollectionExplicitlyDisallowed( } export type OpenRouterReasoningConfig = { - effort?: OpenAI.Chat.Completions.ChatCompletionReasoningEffort | 'none'; + effort?: OpenAI.Chat.Completions.ChatCompletionReasoningEffort; max_tokens?: number; exclude?: boolean; enabled?: boolean; diff --git a/apps/web/src/lib/ai-gateway/providers/variants.ts b/apps/web/src/lib/ai-gateway/providers/variants.ts new file mode 100644 index 0000000000..2140a055fe --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/variants.ts @@ -0,0 +1,126 @@ +import { isClaudeModel } from '@/lib/ai-gateway/providers/anthropic.constants'; +import { isDeepseekModel } from '@/lib/ai-gateway/providers/deepseek'; +import { isGemini3Model, isGemmaModel } from '@/lib/ai-gateway/providers/google'; +import { isMuseModel } from '@/lib/ai-gateway/providers/meta'; +import { isMinimaxModel } from '@/lib/ai-gateway/providers/minimax'; +import { isKimiModel } from '@/lib/ai-gateway/providers/moonshotai'; +import { isOpenAiModel } from '@/lib/ai-gateway/providers/openai'; +import { isQwenModel } from '@/lib/ai-gateway/providers/qwen'; +import { isStepModel } from '@/lib/ai-gateway/providers/stepfun'; +import { isGrok42Model, isGrok45Model } from '@/lib/ai-gateway/providers/xai'; +import { isGlmModel } from '@/lib/ai-gateway/providers/zai'; +import { type OpenCodeSettings, ReasoningEffortSchema } from '@kilocode/db/schema-types'; + +export const REASONING_VARIANTS_THINKING_ONLY = { + thinking: { reasoning: { enabled: true, effort: 'high' } }, +} as const; + +export const REASONING_VARIANTS_BINARY = { + instant: { reasoning: { enabled: false, effort: 'none' } }, + ...REASONING_VARIANTS_THINKING_ONLY, +} as const; + +export const REASONING_VARIANTS_LOW_MEDIUM_HIGH = { + low: { reasoning: { enabled: true, effort: 'low' } }, + medium: { reasoning: { enabled: true, effort: 'medium' } }, + high: { reasoning: { enabled: true, effort: 'high' } }, +} as const; + +export const REASONING_VARIANTS_MAX_HIGH_LOW = { + max: { reasoning: { enabled: true, effort: 'max' } }, + high: { reasoning: { enabled: true, effort: 'high' } }, + low: { reasoning: { enabled: true, effort: 'low' } }, +} as const; + +export const REASONING_VARIANTS_XHIGH_MEDIUM_LOW = { + xhigh: { reasoning: { enabled: true, effort: 'xhigh' } }, + medium: { reasoning: { enabled: true, effort: 'medium' } }, + low: { reasoning: { enabled: true, effort: 'low' } }, +} as const; + +export const REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH = { + minimal: { reasoning: { enabled: true, effort: 'minimal' } }, + ...REASONING_VARIANTS_LOW_MEDIUM_HIGH, +} as const; + +export const REASONING_VARIANTS_NONE_MINIMAL_LOW_MEDIUM_HIGH = { + none: { reasoning: { enabled: false, effort: 'none' } }, + ...REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH, +} as const; + +export const REASONING_VARIANTS_NONE_HIGH_XHIGH = { + none: { reasoning: { enabled: false, effort: 'none' } }, + high: { reasoning: { enabled: true, effort: 'high' } }, + xhigh: { reasoning: { enabled: true, effort: 'xhigh' } }, +} as const; + +const REASONING_VARIANTS_CLAUDE = { + none: { reasoning: { enabled: false, effort: 'none' } }, + low: { reasoning: { enabled: true, effort: 'low' }, verbosity: 'low' }, + medium: { reasoning: { enabled: true, effort: 'medium' }, verbosity: 'medium' }, + high: { reasoning: { enabled: true, effort: 'high' }, verbosity: 'high' }, + xhigh: { reasoning: { enabled: true, effort: 'xhigh' }, verbosity: 'xhigh' }, + max: { reasoning: { enabled: true, effort: 'xhigh' }, verbosity: 'max' }, +} as const; + +export const REASONING_VARIANTS_INSTANT_LOW_MEDIUM_HIGH = { + instant: REASONING_VARIANTS_BINARY.instant, + ...REASONING_VARIANTS_LOW_MEDIUM_HIGH, +} as const; + +export function getFallbackModelVariants(model: string): OpenCodeSettings['variants'] { + if (isClaudeModel(model)) { + return REASONING_VARIANTS_CLAUDE; + } + if (model.includes('codex') || isGemini3Model(model)) { + return Object.fromEntries( + ReasoningEffortSchema.options + .filter(e => e !== 'none' && e !== 'minimal' && e !== 'max') + .map(effort => [effort, { reasoning: { enabled: true, effort } }]) + ); + } + if (isOpenAiModel(model)) { + return Object.fromEntries( + ReasoningEffortSchema.options + .filter(e => e !== 'minimal') + .map(effort => [effort, { reasoning: { enabled: effort !== 'none', effort } }]) + ); + } + if (model.includes('mistral-medium-3-5')) { + return REASONING_VARIANTS_BINARY; + } + if (model.includes('kimi-k2.7-code')) { + return REASONING_VARIANTS_THINKING_ONLY; + } + if (model.includes('kimi-k2')) { + return REASONING_VARIANTS_BINARY; + } + if (isKimiModel(model)) { + return REASONING_VARIANTS_MAX_HIGH_LOW; + } + if (model.includes('qwen3.8') && (model.includes('plus') || model.includes('max'))) { + return REASONING_VARIANTS_XHIGH_MEDIUM_LOW; + } + if ( + isMinimaxModel(model) || + isGrok42Model(model) || + isQwenModel(model) || + isGemmaModel(model) || + model.includes('mimo') + ) { + return REASONING_VARIANTS_BINARY; + } + if (model.startsWith('inception/mercury-2')) { + return REASONING_VARIANTS_INSTANT_LOW_MEDIUM_HIGH; + } + if (isStepModel(model) || isGrok45Model(model)) { + return REASONING_VARIANTS_LOW_MEDIUM_HIGH; + } + if (isDeepseekModel(model) || isGlmModel(model)) { + return REASONING_VARIANTS_NONE_HIGH_XHIGH; + } + if (isMuseModel(model)) { + return REASONING_VARIANTS_NONE_MINIMAL_LOW_MEDIUM_HIGH; + } + return undefined; +} diff --git a/apps/web/src/lib/code-reviews/core/model-variants.ts b/apps/web/src/lib/code-reviews/core/model-variants.ts index c08681eaa6..3fa718b15a 100644 --- a/apps/web/src/lib/code-reviews/core/model-variants.ts +++ b/apps/web/src/lib/code-reviews/core/model-variants.ts @@ -1,8 +1,8 @@ -import { getModelVariants } from '@/lib/ai-gateway/providers/model-settings'; +import { getFallbackModelVariants } from '@/lib/ai-gateway/providers/variants'; /** Returns the ordered list of thinking-effort variant names available for a model, or [] if the model has no variants. */ export function getAvailableThinkingEfforts(modelSlug: string): string[] { - const variants = getModelVariants(modelSlug); + const variants = getFallbackModelVariants(modelSlug); return variants ? Object.keys(variants) : []; }