diff --git a/apps/web/src/lib/ai-gateway/auto-model/resolution.test.ts b/apps/web/src/lib/ai-gateway/auto-model/resolution.test.ts index 78131ced9b..5500d8794d 100644 --- a/apps/web/src/lib/ai-gateway/auto-model/resolution.test.ts +++ b/apps/web/src/lib/ai-gateway/auto-model/resolution.test.ts @@ -178,7 +178,7 @@ describe('resolveAutoModel — kilo-auto/efficient branch', () => { }); it('applies complete catalog settings for variant xhigh (distinct from max)', async () => { - // Claude catalog: xhigh → effort xhigh + verbosity xhigh; max → effort xhigh + verbosity max + // Claude catalog: xhigh → effort xhigh + verbosity xhigh; max → effort max + verbosity max const claudeModel = 'anthropic/claude-sonnet-5'; const xhighResult = await resolveAutoModel( { @@ -219,7 +219,7 @@ describe('resolveAutoModel — kilo-auto/efficient branch', () => { kind: 'ok', resolved: { model: claudeModel, - reasoning: { enabled: true, effort: 'xhigh' }, + reasoning: { enabled: true, effort: 'max' }, verbosity: 'max', }, }); @@ -246,7 +246,7 @@ describe('resolveAutoModel — kilo-auto/efficient branch', () => { kind: 'ok', resolved: { model: claudeModel, - reasoning: { enabled: true, effort: 'xhigh' }, + reasoning: { enabled: true, effort: 'max' }, verbosity: 'max', }, }); diff --git a/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.test.ts b/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.test.ts index 6f619e11b6..a55b5a758a 100644 --- a/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.test.ts +++ b/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.test.ts @@ -4,7 +4,7 @@ import { orderOpenCodeSettings, orderOpenCodeVariants } from './order-opencode-v const variant = { reasoning: { enabled: true } }; describe('orderOpenCodeVariants', () => { - it('orders reasoning effort variants from least to most intensive', () => { + it('orders reasoning effort variants from most to least intensive', () => { const variants = { max: variant, medium: variant, @@ -16,13 +16,13 @@ describe('orderOpenCodeVariants', () => { }; expect(Object.keys(orderOpenCodeVariants(variants))).toEqual([ - 'none', - 'minimal', - 'low', - 'medium', - 'high', - 'xhigh', 'max', + 'xhigh', + 'high', + 'medium', + 'low', + 'minimal', + 'none', ]); expect(Object.keys(variants)).toEqual([ 'max', @@ -42,12 +42,12 @@ describe('orderOpenCodeVariants', () => { ]); }); - it('orders instant and effort variants from least to most intensive', () => { + it('orders instant and effort variants consistently with fallback variants', () => { expect( Object.keys( orderOpenCodeVariants({ high: variant, medium: variant, instant: variant, low: variant }) ) - ).toEqual(['instant', 'low', 'medium', 'high']); + ).toEqual(['instant', 'high', 'medium', 'low']); }); it('orders arbitrary and mixed variant names alphabetically', () => { @@ -66,7 +66,7 @@ describe('orderOpenCodeSettings', () => { expect(orderOpenCodeSettings(settings)).toEqual({ ai_sdk_provider: 'anthropic', - variants: { low: variant, high: variant }, + variants: { high: variant, low: variant }, }); }); diff --git a/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.ts b/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.ts index 102b3d137f..82eaafa34c 100644 --- a/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.ts +++ b/apps/web/src/lib/ai-gateway/custom-llm/order-opencode-variants.ts @@ -1,9 +1,9 @@ import type { OpenCodeSettings } from '@kilocode/db/schema-types'; const VARIANT_ORDERS = [ - ['none', 'minimal', 'low', 'medium', 'high', 'xhigh', 'max'], + ['max', 'xhigh', 'high', 'medium', 'low', 'minimal', 'none'], ['instant', 'thinking'], - ['instant', 'low', 'medium', 'high'], + ['instant', 'high', 'medium', 'low'], ] as const; function compareNames(a: string, b: string): number { diff --git a/apps/web/src/lib/ai-gateway/providers/variants.test.ts b/apps/web/src/lib/ai-gateway/providers/variants.test.ts new file mode 100644 index 0000000000..dc191be584 --- /dev/null +++ b/apps/web/src/lib/ai-gateway/providers/variants.test.ts @@ -0,0 +1,40 @@ +import { describe, expect, test } from '@jest/globals'; + +import { + getFallbackModelVariants, + REASONING_VARIANTS_BINARY, + REASONING_VARIANTS_NONE_MEDIUM_HIGH, +} from '@/lib/ai-gateway/providers/variants'; + +describe('getFallbackModelVariants', () => { + test.each([ + 'google/gemma-4-26b-a4b-it', + 'poolside/laguna-s-2.1:free', + 'inclusionai/ling-3.0-flash:free', + ])('returns binary variants for %s', model => { + expect(getFallbackModelVariants(model)).toBe(REASONING_VARIANTS_BINARY); + }); + + test('returns the latest Nemotron family variants', () => { + expect(getFallbackModelVariants('nvidia/nemotron-3-super-120b-a12b:free')).toBe( + REASONING_VARIANTS_NONE_MEDIUM_HIGH + ); + }); + + test('uses max reasoning effort for the Claude max variant', () => { + expect(getFallbackModelVariants('anthropic/claude-opus-5')).toMatchObject({ + max: { reasoning: { enabled: true, effort: 'max' }, verbosity: 'max' }, + }); + }); + + test('orders OpenAI variants from most to least intensive', () => { + expect(Object.keys(getFallbackModelVariants('openai/gpt-5.6-sol') ?? {})).toEqual([ + 'max', + 'xhigh', + 'high', + 'medium', + 'low', + 'none', + ]); + }); +}); diff --git a/apps/web/src/lib/ai-gateway/providers/variants.ts b/apps/web/src/lib/ai-gateway/providers/variants.ts index 1031c69057..7e83382cdf 100644 --- a/apps/web/src/lib/ai-gateway/providers/variants.ts +++ b/apps/web/src/lib/ai-gateway/providers/variants.ts @@ -64,6 +64,12 @@ export const REASONING_VARIANTS_NONE_HIGH_XHIGH = { none: { reasoning: { enabled: false, effort: 'none' } }, } as const; +export const REASONING_VARIANTS_NONE_MEDIUM_HIGH = { + high: { reasoning: { enabled: true, effort: 'high' } }, + medium: { reasoning: { enabled: true, effort: 'medium' } }, + none: { reasoning: { enabled: false, effort: 'none' } }, +} as const; + export const REASONING_VARIANTS_NONE_LOW_HIGH_MAX = { max: { reasoning: { enabled: true, effort: 'max' } }, high: { reasoning: { enabled: true, effort: 'high' } }, @@ -72,7 +78,7 @@ export const REASONING_VARIANTS_NONE_LOW_HIGH_MAX = { } as const; const REASONING_VARIANTS_CLAUDE = { - max: { reasoning: { enabled: true, effort: 'xhigh' }, verbosity: 'max' }, + max: { reasoning: { enabled: true, effort: 'max' }, verbosity: 'max' }, xhigh: { reasoning: { enabled: true, effort: 'xhigh' }, verbosity: 'xhigh' }, high: { reasoning: { enabled: true, effort: 'high' }, verbosity: 'high' }, medium: { reasoning: { enabled: true, effort: 'medium' }, verbosity: 'medium' }, @@ -94,6 +100,9 @@ export function getFallbackModelVariants(model: string): OpenCodeSettings['varia if (isDeepseekModel(model)) { return REASONING_VARIANTS_NONE_LOW_HIGH_MAX; } + if (model.includes('gemma')) { + return REASONING_VARIANTS_BINARY; + } if (isGeminiModel(model)) { return REASONING_VARIANTS_MINIMAL_LOW_MEDIUM_HIGH; } @@ -106,6 +115,12 @@ export function getFallbackModelVariants(model: string): OpenCodeSettings['varia if (isKimiModel(model)) { return REASONING_VARIANTS_MAX_HIGH_LOW_NONE; } + if (model.includes('laguna')) { + return REASONING_VARIANTS_BINARY; + } + if (model.includes('ling-')) { + return REASONING_VARIANTS_BINARY; + } if (model.includes('mercury')) { return REASONING_VARIANTS_INSTANT_LOW_MEDIUM_HIGH; } @@ -121,10 +136,14 @@ export function getFallbackModelVariants(model: string): OpenCodeSettings['varia if (isMuseModel(model)) { return REASONING_VARIANTS_NONE_MINIMAL_LOW_MEDIUM_HIGH_XHIGH; } + if (model.includes('nemotron')) { + return REASONING_VARIANTS_NONE_MEDIUM_HIGH; + } if (isOpenAiModel(model)) { return Object.fromEntries( ReasoningEffortSchema.options .filter(e => e !== 'minimal') + .reverse() .map(effort => [effort, { reasoning: { enabled: effort !== 'none', effort } }]) ); }