diff --git a/apps/web/src/app/api/openrouter/embeddings/route.ts b/apps/web/src/app/api/openrouter/embeddings/route.ts index 678f8a5fb4..a6d7d2257b 100644 --- a/apps/web/src/app/api/openrouter/embeddings/route.ts +++ b/apps/web/src/app/api/openrouter/embeddings/route.ts @@ -248,7 +248,7 @@ export async function POST(request: NextRequest): Promise 0 && provider.id === 'vercel') { - requestBodyParsed.model = mapModelIdToVercel(requestBodyParsed.model, false); + requestBodyParsed.model = mapModelIdToVercel(requestBodyParsed.model); } const upstreamBody = buildUpstreamBody(requestBodyParsed, requestedModelLowerCased); diff --git a/apps/web/src/lib/ai-gateway/byok/index.ts b/apps/web/src/lib/ai-gateway/byok/index.ts index f049fae0b6..dd7700b909 100644 --- a/apps/web/src/lib/ai-gateway/byok/index.ts +++ b/apps/web/src/lib/ai-gateway/byok/index.ts @@ -23,7 +23,7 @@ export async function getModelUserByokProviders(modelId: string): Promise VercelUserByokInferenceProviderIdSchema.safeParse(ep.provider_name ?? ep.tag).data) .filter(providerId => providerId !== undefined) ?? []; if (providers.length === 0) { 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 9e7202295b..dc3461671b 100644 --- a/apps/web/src/lib/ai-gateway/providers/model-settings.ts +++ b/apps/web/src/lib/ai-gateway/providers/model-settings.ts @@ -4,7 +4,7 @@ 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 { seed_20_code_free_model } from '@/lib/ai-gateway/providers/seed'; -import { isGrokModel, isGrokToggleableReasoningModel } from '@/lib/ai-gateway/providers/xai'; +import { isGrokModel, isGrok42Model, isGrok45Model } from '@/lib/ai-gateway/providers/xai'; import { isGlmModel } from '@/lib/ai-gateway/providers/zai'; import type { CustomLlmProvider, @@ -101,7 +101,7 @@ export function getModelVariants(model: string): OpenCodeSettings['variants'] { if ( isMinimaxModel(model) || isKimiModel(model) || - isGrokToggleableReasoningModel(model) || + isGrok42Model(model) || isQwenModel(model) || isGemmaModel(model) || model.includes('mimo') @@ -114,7 +114,7 @@ export function getModelVariants(model: string): OpenCodeSettings['variants'] { if (model.startsWith('inception/mercury-2')) { return REASONING_VARIANTS_INSTANT_LOW_MEDIUM_HIGH; } - if (isStepModel(model)) { + if (isStepModel(model) || isGrok45Model(model)) { return REASONING_VARIANTS_LOW_MEDIUM_HIGH; } if (isDeepseekModel(model) || isGlmModel(model)) { diff --git a/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts b/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts index 88297cdd1b..350d5da1b9 100644 --- a/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts +++ b/apps/web/src/lib/ai-gateway/providers/openrouter/sync-providers.ts @@ -187,7 +187,7 @@ function injectExtraUserByokModels( } } for (const model of openRouterModels.values()) { - const vercelModel = vercelModels[mapModelIdToVercel(model.slug, false)]; + const vercelModel = vercelModels[mapModelIdToVercel(model.slug)]; if (!vercelModel) continue; const vercelInferenceProviders = new Set( diff --git a/apps/web/src/lib/ai-gateway/providers/vercel/index.ts b/apps/web/src/lib/ai-gateway/providers/vercel/index.ts index 69041151b8..9df72efcea 100644 --- a/apps/web/src/lib/ai-gateway/providers/vercel/index.ts +++ b/apps/web/src/lib/ai-gateway/providers/vercel/index.ts @@ -11,7 +11,6 @@ import type { VercelInferenceProviderConfig, VercelProviderConfig, } from '@/lib/ai-gateway/providers/openrouter/types'; -import { isReasoningExplicitlyDisabled } from '@/lib/ai-gateway/providers/openrouter/request-helpers'; import { mapModelIdToVercel } from '@/lib/ai-gateway/providers/vercel/mapModelIdToVercel'; import { redisClient } from '@/lib/redis'; import { createCachedFetch } from '@/lib/cached-fetch'; @@ -92,7 +91,7 @@ export async function shouldRouteToVercel( } const vercelModels = await getVercelModelsFromRedis(); - const vercelModelId = mapModelIdToVercel(requestedModel, isReasoningExplicitlyDisabled(request)); + const vercelModelId = mapModelIdToVercel(requestedModel); if (!vercelModels.has(vercelModelId)) { console.debug(`[shouldRouteToVercel] model not found in Vercel model list`); return false; @@ -186,10 +185,7 @@ export function applyVercelSettings( requestToMutate: GatewayRequest, userByok: BYOKResult[] | null ) { - requestToMutate.body.model = mapModelIdToVercel( - requestedModel, - isReasoningExplicitlyDisabled(requestToMutate) - ); + requestToMutate.body.model = mapModelIdToVercel(requestedModel); if (userByok) { if (userByok.length === 0) { diff --git a/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.test.ts b/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.test.ts index ba309d92b3..b1990fae34 100644 --- a/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.test.ts +++ b/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.test.ts @@ -31,11 +31,11 @@ describe('mapModelIdToVercel', () => { ['~google/gemini-flash-latest', GEMINI_FLASH_CURRENT_VERCEL_MODEL_ID], ['~x-ai/grok-latest', GROK_CURRENT_VERCEL_MODEL_ID], ])('maps %s to the current Vercel model id', (input, expected) => { - expect(mapModelIdToVercel(input, false)).toBe(expected); + expect(mapModelIdToVercel(input)).toBe(expected); }); it('does not map a latest alias that is missing the leading tilde', () => { - expect(mapModelIdToVercel('anthropic/claude-opus-latest', false)).toBe( + expect(mapModelIdToVercel('anthropic/claude-opus-latest')).toBe( 'anthropic/claude-opus-latest' ); }); @@ -59,83 +59,54 @@ describe('mapModelIdToVercel', () => { ['qwen/qwen3-30b-a3b', 'alibaba/qwen-3-30b'], ['qwen/qwen3-32b', 'alibaba/qwen-3-32b'], ])('maps %s to %s', (input, expected) => { - expect(mapModelIdToVercel(input, false)).toBe(expected); - }); - }); - - describe('grok 4.20 reasoning/non-reasoning toggle', () => { - it('maps x-ai/grok-4.20 to the reasoning variant when reasoning is not explicitly disabled', () => { - expect(mapModelIdToVercel('x-ai/grok-4.20', false)).toBe('xai/grok-4.20-reasoning'); - }); - - it('maps x-ai/grok-4.20 to the non-reasoning variant when reasoning is explicitly disabled', () => { - expect(mapModelIdToVercel('x-ai/grok-4.20', true)).toBe('xai/grok-4.20-non-reasoning'); - }); - - it('does not rewrite other hardcoded mapping targets when reasoning is disabled', () => { - // Only grok-4.20 has a reasoning/non-reasoning split; other hardcoded - // mappings must pass through unchanged regardless of the flag. - expect(mapModelIdToVercel('mistralai/codestral-2508', true)).toBe('mistral/codestral'); - expect(mapModelIdToVercel('qwen/qwen3-32b', true)).toBe('alibaba/qwen-3-32b'); - }); - - it('does not apply the reasoning toggle to grok models outside the hardcoded mapping', () => { - // A grok id that is not in vercelModelIdMapping should fall through to - // the generic prefix rewrite even when reasoning is explicitly disabled. - expect(mapModelIdToVercel('x-ai/grok-4.20-reasoning', true)).toBe('xai/grok-4.20-reasoning'); + expect(mapModelIdToVercel(input)).toBe(expected); }); }); describe('first-party inference provider inference', () => { it('rewrites the anthropic/ prefix unchanged', () => { - expect(mapModelIdToVercel('anthropic/claude-sonnet-4.5', false)).toBe( - 'anthropic/claude-sonnet-4.5' - ); + expect(mapModelIdToVercel('anthropic/claude-sonnet-4.5')).toBe('anthropic/claude-sonnet-4.5'); }); it('rewrites the mistralai/ prefix to mistral/', () => { // not covered by the hardcoded mapping - expect(mapModelIdToVercel('mistralai/some-new-model', false)).toBe('mistral/some-new-model'); + expect(mapModelIdToVercel('mistralai/some-new-model')).toBe('mistral/some-new-model'); }); it('rewrites the qwen/ prefix to alibaba/', () => { - expect(mapModelIdToVercel('qwen/some-new-qwen-model', false)).toBe( - 'alibaba/some-new-qwen-model' - ); + expect(mapModelIdToVercel('qwen/some-new-qwen-model')).toBe('alibaba/some-new-qwen-model'); }); it('rewrites x-ai/ to xai/', () => { - expect(mapModelIdToVercel('x-ai/some-new-grok', false)).toBe('xai/some-new-grok'); + expect(mapModelIdToVercel('x-ai/some-new-grok')).toBe('xai/some-new-grok'); }); it('rewrites z-ai/ to zai/', () => { - expect(mapModelIdToVercel('z-ai/glm-5.1', false)).toBe('zai/glm-5.1'); + expect(mapModelIdToVercel('z-ai/glm-5.1')).toBe('zai/glm-5.1'); }); it('leaves gpt-oss models unchanged', () => { - expect(mapModelIdToVercel('openai/gpt-oss-20b', false)).toBe('openai/gpt-oss-20b'); + expect(mapModelIdToVercel('openai/gpt-oss-20b')).toBe('openai/gpt-oss-20b'); }); it('leaves a model with an unknown provider prefix unchanged', () => { - expect(mapModelIdToVercel('deepseek/deepseek-v3.2', false)).toBe('deepseek/deepseek-v3.2'); + expect(mapModelIdToVercel('deepseek/deepseek-v3.2')).toBe('deepseek/deepseek-v3.2'); }); it('returns the model id as-is when it contains no slash', () => { - expect(mapModelIdToVercel('some-model-without-slash', false)).toBe( - 'some-model-without-slash' - ); + expect(mapModelIdToVercel('some-model-without-slash')).toBe('some-model-without-slash'); }); }); describe('kilo-exclusive models', () => { it('maps a Vercel-exclusive model to its internal id', () => { - expect(mapModelIdToVercel('meta/muse-spark-1.1', false)).toBe('meta/muse-spark-1.1'); + expect(mapModelIdToVercel('meta/muse-spark-1.1')).toBe('meta/muse-spark-1.1'); }); it('maps an exclusive flagged with vercel-routing to its internal id', () => { // google/gemma-4-26b-a4b-it:free is registered in kiloExclusiveModels // with the 'vercel-routing' flag and internal_id 'google/gemma-4-26b-a4b-it'. - expect(mapModelIdToVercel('google/gemma-4-26b-a4b-it:free', false)).toBe( + expect(mapModelIdToVercel('google/gemma-4-26b-a4b-it:free')).toBe( 'google/gemma-4-26b-a4b-it' ); }); @@ -144,7 +115,7 @@ describe('mapModelIdToVercel', () => { // claude_sonnet_clawsetup_model has gateway 'openrouter' and no // 'vercel-routing' flag, so the mapping must pass the public id through // the generic prefix rewrite instead of substituting internal_id. - expect(mapModelIdToVercel('anthropic/claude-sonnet-4.6:clawsetup', false)).toBe( + expect(mapModelIdToVercel('anthropic/claude-sonnet-4.6:clawsetup')).toBe( 'anthropic/claude-sonnet-4.6:clawsetup' ); }); @@ -153,9 +124,7 @@ describe('mapModelIdToVercel', () => { // minimax_m25_free_model has the 'vercel-routing' flag but status // 'disabled', so it must not be substituted by internal_id and instead // pass the public id through the generic prefix rewrite. - expect(mapModelIdToVercel('minimax/minimax-m2.5:free', false)).toBe( - 'minimax/minimax-m2.5:free' - ); + expect(mapModelIdToVercel('minimax/minimax-m2.5:free')).toBe('minimax/minimax-m2.5:free'); }); }); }); diff --git a/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.ts b/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.ts index 4de0f9c974..10d33b0acb 100644 --- a/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.ts +++ b/apps/web/src/lib/ai-gateway/providers/vercel/mapModelIdToVercel.ts @@ -32,7 +32,6 @@ const vercelModelIdMapping: Record = { 'mistralai/devstral-2512': 'mistral/devstral-2', 'mistralai/mistral-embed-2312': 'mistral/mistral-embed', 'mistralai/codestral-embed-2505': 'mistral/codestral-embed', - 'x-ai/grok-4.20': 'xai/grok-4.20-reasoning', 'mistralai/ministral-14b-2512': 'mistral/ministral-14b', 'mistralai/ministral-3b-2512': 'mistral/ministral-3b', 'mistralai/ministral-8b-2512': 'mistral/ministral-8b', @@ -46,12 +45,10 @@ const vercelModelIdMapping: Record = { 'qwen/qwen3-32b': 'alibaba/qwen-3-32b', }; -export function mapModelIdToVercel(modelId: string, reasoningExplicitlyDisabled: boolean) { +export function mapModelIdToVercel(modelId: string) { const hardcodedVercelId = vercelModelIdMapping[modelId]; if (hardcodedVercelId) { - return hardcodedVercelId === 'xai/grok-4.20-reasoning' && reasoningExplicitlyDisabled - ? 'xai/grok-4.20-non-reasoning' - : hardcodedVercelId; + return hardcodedVercelId; } const internalId = diff --git a/apps/web/src/lib/ai-gateway/providers/xai.ts b/apps/web/src/lib/ai-gateway/providers/xai.ts index 9d340634e0..2dbbee2780 100644 --- a/apps/web/src/lib/ai-gateway/providers/xai.ts +++ b/apps/web/src/lib/ai-gateway/providers/xai.ts @@ -4,6 +4,10 @@ export function isGrokModel(requestedModel: string) { return requestedModel.includes('grok'); } -export function isGrokToggleableReasoningModel(model: string) { +export function isGrok42Model(model: string) { return model.includes('grok-4.2'); } + +export function isGrok45Model(model: string) { + return model.includes('grok-4.5'); +} diff --git a/apps/web/src/routers/byok-router.ts b/apps/web/src/routers/byok-router.ts index a12319b984..48cd0f5469 100644 --- a/apps/web/src/routers/byok-router.ts +++ b/apps/web/src/routers/byok-router.ts @@ -119,7 +119,7 @@ async function fetchSupportedModels(): Promise> { for (const openRouterModel of Object.values(openRouterModelMetadata)) { if (isKiloExclusiveModel(openRouterModel.id)) continue; - const vercelModel = vercelModelMetadata[mapModelIdToVercel(openRouterModel.id, false)]; + const vercelModel = vercelModelMetadata[mapModelIdToVercel(openRouterModel.id)]; if (!vercelModel) continue; if (vercelModel.type !== 'language') continue; for (const endpoint of vercelModel.endpoints) {