From 4ca9bd66cb5f68efcd8622e575e70c995e6b08ec Mon Sep 17 00:00:00 2001 From: jatmn Date: Thu, 16 Jul 2026 14:16:04 -0700 Subject: [PATCH 01/23] feat(kimi): add K3 context variants --- src/integrations/gateways/kimi-code.ts | 3 +++ src/integrations/models/kimi.ts | 1 + src/integrations/runtimeMetadata.test.ts | 19 +++++++++++++++++++ src/integrations/runtimeMetadata.ts | 5 ++++- src/services/api/providerConfig.test.ts | 11 +++++++++++ src/utils/context.test.ts | 20 ++++++++++++++++++++ src/utils/model/modelOptions.ts | 18 ++++++++++++++---- src/utils/model/routeCatalogOptions.test.ts | 9 +++++++++ src/utils/model/routeCatalogOptions.ts | 10 +++++++++- 9 files changed, 90 insertions(+), 6 deletions(-) diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index 85eb5824ba..37d6d3483c 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -33,8 +33,11 @@ export default defineGateway({ catalog: { source: 'static', models: [ + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, + { id: 'k3-1m', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, + { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, ], }, usage: { supported: false }, diff --git a/src/integrations/models/kimi.ts b/src/integrations/models/kimi.ts index e7260d9c57..fb08a18212 100644 --- a/src/integrations/models/kimi.ts +++ b/src/integrations/models/kimi.ts @@ -29,6 +29,7 @@ function kimiModel( } export default [ + kimiModel('k3', 'Kimi K3', 262_144, 32_768), kimiModel('kimi-for-coding', 'Kimi for Coding', 262_144, 32_768), kimiModel('kimi-k2.7-code', 'Kimi K2.7 Code', 262_144, 262_144), kimiModel('kimi-k2.6', 'Kimi K2.6', 262_144, 32_768), diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index 5b1ca29a7e..61f5634651 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -374,9 +374,28 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad expect(result.routeId).toBe('kimi-code') expect(result.descriptor?.catalog?.models?.map(model => model.id)).toEqual([ + 'k3-256k', + 'k3-1m', 'kimi-k2.7-code', 'kimi-for-coding', + 'kimi-for-coding-highspeed', ]) + const k3 = resolveOpenAIShimRuntimeContext({ + model: 'k3-256k', + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, + }) + expect(k3.catalogEntry).toMatchObject({ + id: 'k3-256k', + contextWindow: 262_144, + label: 'Kimi K3 (256K)', + }) + expect(k3.catalogEntry?.reasoning?.levels).toEqual(['max']) + expect(resolveModelRuntimeLimits({ + model: 'k3-1m', + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, + })).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 32_768 }) expect(result.catalogEntry?.id).toBe('kimi-for-coding') expect(result.catalogEntry?.reasoning?.levels).toEqual(['low', 'medium', 'high']) expect(result.catalogEntry?.reasoning?.defaultLevel).toBe('medium') diff --git a/src/integrations/runtimeMetadata.ts b/src/integrations/runtimeMetadata.ts index 6780222214..d5bdcf51aa 100644 --- a/src/integrations/runtimeMetadata.ts +++ b/src/integrations/runtimeMetadata.ts @@ -66,7 +66,10 @@ function matchesCatalogEntryModel( entry: ModelCatalogEntry, modelApiName: string, ): boolean { - if (entry.apiName.trim().toLowerCase() === modelApiName) { + if ( + entry.apiName.trim().toLowerCase() === modelApiName || + entry.id.trim().toLowerCase() === modelApiName + ) { return true } diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index d24d9706c7..d2ed2e1674 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -51,6 +51,17 @@ test('resolveProviderRequest maps explicit route catalog aliases to API model id expect(request.reasoning).toEqual({ effort: 'high' }) }) +test('resolveProviderRequest maps K3 context catalog choices to the Kimi API model', () => { + for (const model of ['k3-256k', 'k3-1m']) { + const request = resolveProviderRequest({ + model, + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: {}, + }) + expect(request.resolvedModel).toBe('k3') + } +}) + test('resolveProviderRequest maps explicit Atlas coding aliases without ambiguity', () => { expect(resolveProviderRequest({ model: 'claude-sonnet-4-6', diff --git a/src/utils/context.test.ts b/src/utils/context.test.ts index 1782a09fa1..e3584bbc2b 100644 --- a/src/utils/context.test.ts +++ b/src/utils/context.test.ts @@ -841,6 +841,26 @@ test('Kimi Code kimi-for-coding uses provider-specific context and output caps', }) }) +test('Kimi Code K3 defaults to the Moderato 256K cap', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('k3-256k')).toBe(262_144) + expect(getModelMaxOutputTokens('k3-256k')).toEqual({ + default: 32_768, + upperLimit: 32_768, + }) +}) + +test('Kimi Code K3 1M catalog choice uses the Allegretto cap', () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + + expect(getContextWindowForModel('k3-1m')).toBe(1_048_576) +}) + test('DashScope glm-5 uses provider-specific context and output caps', () => { process.env.CLAUDE_CODE_USE_OPENAI = '1' delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS diff --git a/src/utils/model/modelOptions.ts b/src/utils/model/modelOptions.ts index 7706c1268e..b40f243717 100644 --- a/src/utils/model/modelOptions.ts +++ b/src/utils/model/modelOptions.ts @@ -938,14 +938,23 @@ function mergeModelOptionsByNormalizedValue( return merged } +function getCatalogOptionValue(entry: { id: string; apiName: string }, entries: readonly { apiName: string }[]): string { + const apiName = entry.apiName.trim() + const duplicateApiName = entries.filter(candidate => + candidate.apiName.trim().toLowerCase() === apiName.toLowerCase(), + ).length > 1 + return duplicateApiName ? entry.id.trim() : apiName +} + function getActiveOpenAIRouteCatalogOptions(): ModelOption[] { const routeId = getActiveOpenAIRouteId() if (!routeId) { return [] } - return getCatalogEntriesForRoute(routeId).flatMap(entry => { - const value = entry.apiName.trim() + const entries = getCatalogEntriesForRoute(routeId) + return entries.flatMap(entry => { + const value = getCatalogOptionValue(entry, entries) if (!value) { return [] } @@ -973,7 +982,8 @@ function getRouteCatalogModelOption(value: ModelSetting): ModelOption | null { return null } - const catalogEntry = getCatalogEntriesForRoute(routeId).find(entry => + const entries = getCatalogEntriesForRoute(routeId) + const catalogEntry = entries.find(entry => normalizeRouteModelOptionKey(entry.apiName) === normalizedValue || normalizeRouteModelOptionKey(entry.id) === normalizedValue || (entry.aliases ?? []).some( @@ -985,7 +995,7 @@ function getRouteCatalogModelOption(value: ModelSetting): ModelOption | null { } return { - value: catalogEntry.apiName, + value: getCatalogOptionValue(catalogEntry, entries), label: catalogEntry.label ?? catalogEntry.apiName, description: catalogEntry.apiName, } diff --git a/src/utils/model/routeCatalogOptions.test.ts b/src/utils/model/routeCatalogOptions.test.ts index c7bb84066d..fd86f08020 100644 --- a/src/utils/model/routeCatalogOptions.test.ts +++ b/src/utils/model/routeCatalogOptions.test.ts @@ -29,6 +29,15 @@ describe('buildRouteCatalogModelOptions', () => { ]) }) + test('keeps duplicate API models selectable by catalog id', () => { + const options = buildRouteCatalogModelOptions('Kimi Code', [ + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)' }, + { id: 'k3-1m', apiName: 'k3', label: 'Kimi K3 (1M)' }, + ]) + + expect(options.map(option => option.value)).toEqual(['k3-256k', 'k3-1m']) + }) + test('surfaces catalog entry notes as a description tag', () => { const options = buildRouteCatalogModelOptions('Gitlawb Opengateway', [ { diff --git a/src/utils/model/routeCatalogOptions.ts b/src/utils/model/routeCatalogOptions.ts index 6f93404df9..f7e0fd84a8 100644 --- a/src/utils/model/routeCatalogOptions.ts +++ b/src/utils/model/routeCatalogOptions.ts @@ -51,9 +51,17 @@ export function buildRouteCatalogModelOptions( ): ModelOption[] { const seen = new Set() const options: ModelOption[] = [] + const apiNameCounts = new Map() + for (const entry of entries) { + const key = entry.apiName.trim().toLowerCase() + apiNameCounts.set(key, (apiNameCounts.get(key) ?? 0) + 1) + } for (const entry of entries) { - const value = entry.apiName.trim() + const apiName = entry.apiName.trim() + const value = (apiNameCounts.get(apiName.toLowerCase()) ?? 0) > 1 + ? entry.id.trim() + : apiName if (!value || seen.has(value.toLowerCase())) { continue } From 696345bc2cfb8d8a3145c49ff8e37972605c832f Mon Sep 17 00:00:00 2001 From: jatmn Date: Thu, 16 Jul 2026 14:25:56 -0700 Subject: [PATCH 02/23] fix(moonshot): default K3 to 1M --- src/integrations/gateways/kimi-code.ts | 4 ++-- src/integrations/models/kimi.ts | 2 +- src/integrations/runtimeMetadata.test.ts | 23 ++++++++++++++------- src/integrations/vendors/moonshot.ts | 1 + src/services/api/providerConfig.test.ts | 2 +- src/utils/context.test.ts | 10 ++++----- src/utils/effort.codex.test.ts | 12 +++++++++++ src/utils/model/routeCatalogOptions.test.ts | 6 +++--- 8 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index 37d6d3483c..31557ccebf 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -33,8 +33,8 @@ export default defineGateway({ catalog: { source: 'static', models: [ - { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, - { id: 'k3-1m', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, + { id: 'k3', apiName: 'k3', label: 'K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, + { id: 'k3-256k', apiName: 'k3', label: 'K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, diff --git a/src/integrations/models/kimi.ts b/src/integrations/models/kimi.ts index fb08a18212..6ac02b2010 100644 --- a/src/integrations/models/kimi.ts +++ b/src/integrations/models/kimi.ts @@ -29,7 +29,7 @@ function kimiModel( } export default [ - kimiModel('k3', 'Kimi K3', 262_144, 32_768), + defineModel({ id: 'k3', label: 'Moonshot K3', vendorId: 'moonshot', classification: ['chat', 'reasoning', 'vision', 'coding'], defaultModel: 'k3', capabilities: kimiCapabilities, contextWindow: 1_048_576, maxOutputTokens: 32_768 }), kimiModel('kimi-for-coding', 'Kimi for Coding', 262_144, 32_768), kimiModel('kimi-k2.7-code', 'Kimi K2.7 Code', 262_144, 262_144), kimiModel('kimi-k2.6', 'Kimi K2.6', 262_144, 32_768), diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index 61f5634651..9514f820d3 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -304,6 +304,14 @@ describe('resolveOpenAIShimRuntimeContext - NVIDIA NIM GLM-5.2 (regression #1950 describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metadata', () => { it('uses Moonshot direct catalog order, limits, and reasoning controls', () => { + expect( + resolveModelRuntimeLimits({ + model: 'k3', + baseUrl: 'https://api.moonshot.ai/v1', + processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, + }), + ).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 32_768 }) + expect( resolveModelRuntimeLimits({ model: 'kimi-k2.7-code', @@ -336,6 +344,7 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad expect(result.routeId).toBe('moonshot') expect(result.descriptor?.catalog?.models?.map(model => model.id)).toEqual([ + 'k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-k2.5', @@ -374,28 +383,28 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad expect(result.routeId).toBe('kimi-code') expect(result.descriptor?.catalog?.models?.map(model => model.id)).toEqual([ + 'k3', 'k3-256k', - 'k3-1m', 'kimi-k2.7-code', 'kimi-for-coding', 'kimi-for-coding-highspeed', ]) const k3 = resolveOpenAIShimRuntimeContext({ - model: 'k3-256k', + model: 'k3', baseUrl: 'https://api.kimi.com/coding/v1', processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, }) expect(k3.catalogEntry).toMatchObject({ - id: 'k3-256k', - contextWindow: 262_144, - label: 'Kimi K3 (256K)', + id: 'k3', + contextWindow: 1_048_576, + label: 'K3 (1M)', }) expect(k3.catalogEntry?.reasoning?.levels).toEqual(['max']) expect(resolveModelRuntimeLimits({ - model: 'k3-1m', + model: 'k3-256k', baseUrl: 'https://api.kimi.com/coding/v1', processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, - })).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 32_768 }) + })).toEqual({ contextWindow: 262_144, maxOutputTokens: 32_768 }) expect(result.catalogEntry?.id).toBe('kimi-for-coding') expect(result.catalogEntry?.reasoning?.levels).toEqual(['low', 'medium', 'high']) expect(result.catalogEntry?.reasoning?.defaultLevel).toBe('medium') diff --git a/src/integrations/vendors/moonshot.ts b/src/integrations/vendors/moonshot.ts index b1ea87a29c..ae61a270ba 100644 --- a/src/integrations/vendors/moonshot.ts +++ b/src/integrations/vendors/moonshot.ts @@ -34,6 +34,7 @@ export default defineVendor({ catalog: { source: 'static', models: [ + { id: 'k3', apiName: 'k3', label: 'Moonshot K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.6', apiName: 'kimi-k2.6', aliases: ['moonshotai/kimi-k2.6'], label: 'Kimi K2.6', modelDescriptorId: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.5', apiName: 'kimi-k2.5', aliases: ['moonshotai/kimi-k2.5'], label: 'Kimi K2.5', modelDescriptorId: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index d2ed2e1674..590f762eac 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -52,7 +52,7 @@ test('resolveProviderRequest maps explicit route catalog aliases to API model id }) test('resolveProviderRequest maps K3 context catalog choices to the Kimi API model', () => { - for (const model of ['k3-256k', 'k3-1m']) { + for (const model of ['k3', 'k3-256k']) { const request = resolveProviderRequest({ model, baseUrl: 'https://api.kimi.com/coding/v1', diff --git a/src/utils/context.test.ts b/src/utils/context.test.ts index e3584bbc2b..7ced3d568d 100644 --- a/src/utils/context.test.ts +++ b/src/utils/context.test.ts @@ -841,24 +841,24 @@ test('Kimi Code kimi-for-coding uses provider-specific context and output caps', }) }) -test('Kimi Code K3 defaults to the Moderato 256K cap', () => { +test('Kimi Code K3 defaults to the Allegretto 1M cap', () => { process.env.CLAUDE_CODE_USE_OPENAI = '1' process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS - expect(getContextWindowForModel('k3-256k')).toBe(262_144) - expect(getModelMaxOutputTokens('k3-256k')).toEqual({ + expect(getContextWindowForModel('k3')).toBe(1_048_576) + expect(getModelMaxOutputTokens('k3')).toEqual({ default: 32_768, upperLimit: 32_768, }) }) -test('Kimi Code K3 1M catalog choice uses the Allegretto cap', () => { +test('Kimi Code K3 256K catalog choice uses the Moderato cap', () => { process.env.CLAUDE_CODE_USE_OPENAI = '1' process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS - expect(getContextWindowForModel('k3-1m')).toBe(1_048_576) + expect(getContextWindowForModel('k3-256k')).toBe(262_144) }) test('DashScope glm-5 uses provider-specific context and output caps', () => { diff --git a/src/utils/effort.codex.test.ts b/src/utils/effort.codex.test.ts index e0ff692f74..8b6edd1ffe 100644 --- a/src/utils/effort.codex.test.ts +++ b/src/utils/effort.codex.test.ts @@ -582,6 +582,7 @@ test('Moonshot direct and Kimi Code catalogs expose verified reasoning controls' const kimiCodeGateway = (await import('../integrations/gateways/kimi-code.js')).default expect(moonshotVendor.catalog?.models?.map(model => model.id)).toEqual([ + 'k3', 'kimi-k2.7-code', 'kimi-k2.6', 'kimi-k2.5', @@ -635,6 +636,17 @@ test('Moonshot direct and Kimi Code catalogs expose verified reasoning controls' catalogEntries: moonshotVendor.catalog?.models ?? [], }) + expect(resolveModelReasoningControl('k3')).toMatchObject({ + supportsReasoning: true, + controllable: true, + source: 'metadata', + levels: ['max'], + defaultLevel: 'max', + wireFormat: 'reasoning_effort', + }) + expect(getAvailableEffortLevels('k3')).toEqual(['max']) + expect(resolveAppliedEffort('k3', undefined)).toBe('max') + expect(resolveModelReasoningControl('kimi-k2.7-code')).toMatchObject({ supportsReasoning: true, controllable: true, diff --git a/src/utils/model/routeCatalogOptions.test.ts b/src/utils/model/routeCatalogOptions.test.ts index fd86f08020..99f5db75ae 100644 --- a/src/utils/model/routeCatalogOptions.test.ts +++ b/src/utils/model/routeCatalogOptions.test.ts @@ -31,11 +31,11 @@ describe('buildRouteCatalogModelOptions', () => { test('keeps duplicate API models selectable by catalog id', () => { const options = buildRouteCatalogModelOptions('Kimi Code', [ - { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)' }, - { id: 'k3-1m', apiName: 'k3', label: 'Kimi K3 (1M)' }, + { id: 'k3', apiName: 'k3', label: 'K3 (1M)' }, + { id: 'k3-256k', apiName: 'k3', label: 'K3 (256K)' }, ]) - expect(options.map(option => option.value)).toEqual(['k3-256k', 'k3-1m']) + expect(options.map(option => option.value)).toEqual(['k3', 'k3-256k']) }) test('surfaces catalog entry notes as a description tag', () => { From a2c1683b59589cb752b335c950e762b0265d60bc Mon Sep 17 00:00:00 2001 From: jatmn Date: Thu, 16 Jul 2026 14:27:38 -0700 Subject: [PATCH 03/23] fix(kimi): restore K3 model name --- src/integrations/gateways/kimi-code.ts | 4 ++-- src/integrations/models/kimi.ts | 2 +- src/integrations/runtimeMetadata.test.ts | 2 +- src/integrations/vendors/moonshot.ts | 2 +- src/utils/model/routeCatalogOptions.test.ts | 4 ++-- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index 31557ccebf..498603944d 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -33,8 +33,8 @@ export default defineGateway({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, - { id: 'k3-256k', apiName: 'k3', label: 'K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, diff --git a/src/integrations/models/kimi.ts b/src/integrations/models/kimi.ts index 6ac02b2010..6303e3e226 100644 --- a/src/integrations/models/kimi.ts +++ b/src/integrations/models/kimi.ts @@ -29,7 +29,7 @@ function kimiModel( } export default [ - defineModel({ id: 'k3', label: 'Moonshot K3', vendorId: 'moonshot', classification: ['chat', 'reasoning', 'vision', 'coding'], defaultModel: 'k3', capabilities: kimiCapabilities, contextWindow: 1_048_576, maxOutputTokens: 32_768 }), + defineModel({ id: 'k3', label: 'Kimi K3', vendorId: 'moonshot', classification: ['chat', 'reasoning', 'vision', 'coding'], defaultModel: 'k3', capabilities: kimiCapabilities, contextWindow: 1_048_576, maxOutputTokens: 32_768 }), kimiModel('kimi-for-coding', 'Kimi for Coding', 262_144, 32_768), kimiModel('kimi-k2.7-code', 'Kimi K2.7 Code', 262_144, 262_144), kimiModel('kimi-k2.6', 'Kimi K2.6', 262_144, 32_768), diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index 9514f820d3..ea3808df79 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -397,7 +397,7 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad expect(k3.catalogEntry).toMatchObject({ id: 'k3', contextWindow: 1_048_576, - label: 'K3 (1M)', + label: 'Kimi K3 (1M)', }) expect(k3.catalogEntry?.reasoning?.levels).toEqual(['max']) expect(resolveModelRuntimeLimits({ diff --git a/src/integrations/vendors/moonshot.ts b/src/integrations/vendors/moonshot.ts index ae61a270ba..6d5ce63900 100644 --- a/src/integrations/vendors/moonshot.ts +++ b/src/integrations/vendors/moonshot.ts @@ -34,7 +34,7 @@ export default defineVendor({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Moonshot K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.6', apiName: 'kimi-k2.6', aliases: ['moonshotai/kimi-k2.6'], label: 'Kimi K2.6', modelDescriptorId: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.5', apiName: 'kimi-k2.5', aliases: ['moonshotai/kimi-k2.5'], label: 'Kimi K2.5', modelDescriptorId: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, diff --git a/src/utils/model/routeCatalogOptions.test.ts b/src/utils/model/routeCatalogOptions.test.ts index 99f5db75ae..f8133a2bc7 100644 --- a/src/utils/model/routeCatalogOptions.test.ts +++ b/src/utils/model/routeCatalogOptions.test.ts @@ -31,8 +31,8 @@ describe('buildRouteCatalogModelOptions', () => { test('keeps duplicate API models selectable by catalog id', () => { const options = buildRouteCatalogModelOptions('Kimi Code', [ - { id: 'k3', apiName: 'k3', label: 'K3 (1M)' }, - { id: 'k3-256k', apiName: 'k3', label: 'K3 (256K)' }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)' }, + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)' }, ]) expect(options.map(option => option.value)).toEqual(['k3', 'k3-256k']) From 06db3401e9269ee0d95ea8f09d7965068f6713c3 Mon Sep 17 00:00:00 2001 From: jatmn Date: Thu, 16 Jul 2026 15:02:34 -0700 Subject: [PATCH 04/23] fix(kimi): preserve K3 route metadata --- src/integrations/gateways/kimi-code.ts | 2 +- src/integrations/models/kimi.test.ts | 8 ++++ src/integrations/models/kimi.ts | 2 +- src/integrations/runtimeMetadata.test.ts | 12 ++++++ src/services/api/client.test.ts | 38 +++++++++++++++++++ src/services/api/client.ts | 18 +++++++-- .../api/openaiShim.compression.test.ts | 23 ++++++++++- src/services/api/openaiShim.ts | 10 +++-- src/utils/model/modelOptions.gateways.test.ts | 11 ++++++ src/utils/providerProfiles.test.ts | 2 +- 10 files changed, 116 insertions(+), 10 deletions(-) create mode 100644 src/integrations/models/kimi.test.ts diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index 498603944d..3a09732369 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -5,7 +5,7 @@ export default defineGateway({ label: 'Moonshot AI - Kimi Code', category: 'hosted', defaultBaseUrl: 'https://api.kimi.com/coding/v1', - defaultModel: 'kimi-for-coding', + defaultModel: 'k3', supportsModelRouting: true, setup: { requiresAuth: true, diff --git a/src/integrations/models/kimi.test.ts b/src/integrations/models/kimi.test.ts new file mode 100644 index 0000000000..5b6ebfc7c9 --- /dev/null +++ b/src/integrations/models/kimi.test.ts @@ -0,0 +1,8 @@ +import { expect, test } from 'bun:test' + +import { getModel, getModelsForBrand } from '../index.js' + +test('K3 belongs to the Kimi brand', () => { + expect(getModel('k3')?.brandId).toBe('kimi') + expect(getModelsForBrand('kimi').map(model => model.id)).toContain('k3') +}) diff --git a/src/integrations/models/kimi.ts b/src/integrations/models/kimi.ts index 6303e3e226..d62bd2f061 100644 --- a/src/integrations/models/kimi.ts +++ b/src/integrations/models/kimi.ts @@ -29,7 +29,7 @@ function kimiModel( } export default [ - defineModel({ id: 'k3', label: 'Kimi K3', vendorId: 'moonshot', classification: ['chat', 'reasoning', 'vision', 'coding'], defaultModel: 'k3', capabilities: kimiCapabilities, contextWindow: 1_048_576, maxOutputTokens: 32_768 }), + kimiModel('k3', 'Kimi K3', 1_048_576, 32_768), kimiModel('kimi-for-coding', 'Kimi for Coding', 262_144, 32_768), kimiModel('kimi-k2.7-code', 'Kimi K2.7 Code', 262_144, 262_144), kimiModel('kimi-k2.6', 'Kimi K2.6', 262_144, 32_768), diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index ea3808df79..ef80a39931 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -405,6 +405,18 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad baseUrl: 'https://api.kimi.com/coding/v1', processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, })).toEqual({ contextWindow: 262_144, maxOutputTokens: 32_768 }) + const highspeed = resolveOpenAIShimRuntimeContext({ + model: 'kimi-for-coding-highspeed', + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, + }) + expect(highspeed.catalogEntry).toMatchObject({ + id: 'kimi-for-coding-highspeed', + apiName: 'kimi-for-coding-highspeed', + contextWindow: 262_144, + maxOutputTokens: 32_768, + }) + expect(highspeed.catalogEntry?.reasoning?.levels).toEqual(['low', 'medium', 'high']) expect(result.catalogEntry?.id).toBe('kimi-for-coding') expect(result.catalogEntry?.reasoning?.levels).toEqual(['low', 'medium', 'high']) expect(result.catalogEntry?.reasoning?.defaultLevel).toBe('medium') diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 503e6bb3d1..a4f4f29356 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2084,6 +2084,44 @@ test('providerOverride Kimi Code clamps unsupported xhigh effort to high', async globalThis.fetch = originalFetch } }) + +test('providerOverride K3 preserves its verified max reasoning effort', async () => { + let requestBody: Record | undefined + + globalThis.fetch = (async (_input, init) => { + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ + id: 'chatcmpl-provider-override-k3', + model: 'k3', + choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }, + }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + + try { + const client = (await getAnthropicClient({ + maxRetries: 0, + effortValue: 'max', + providerOverride: { + model: 'k3', + baseURL: 'https://api.kimi.com/coding/v1', + apiKey: 'kimi-test-key', + }, + })) as unknown as ShimClient + + await client.beta.messages.create({ + model: 'unused', + system: 'test system', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + expect(requestBody?.reasoning_effort).toBe('max') + } finally { + globalThis.fetch = originalFetch + } +}) test('providerOverride Atlas Grok Build does not send reasoning effort', async () => { let requestBody: Record | undefined diff --git a/src/services/api/client.ts b/src/services/api/client.ts index a306722d59..9f53c12357 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -13,6 +13,7 @@ import { convertEffortValueToLevel, type EffortValue, resolveAppliedEffort, + resolveModelReasoningControl, modelSupportsShimReasoningEffort, modelSupportsWireEffort, standardEffortToOpenAI, @@ -441,9 +442,20 @@ export async function getAnthropicClient({ effortContext, ) : undefined - const shimReasoningEffort: OpenAIEffortLevel | undefined = - appliedEffort !== undefined && supportsShimReasoningEffort - ? standardEffortToOpenAI(convertEffortValueToLevel(appliedEffort)) + const appliedEffortLevel = appliedEffort === undefined + ? undefined + : convertEffortValueToLevel(appliedEffort) + const reasoningControl = effortModel + ? resolveModelReasoningControl(effortModel, effortContext) + : undefined + const shimReasoningEffort: OpenAIShimEffortLevel | undefined = + appliedEffortLevel !== undefined && supportsShimReasoningEffort + ? (reasoningControl?.source === 'metadata' && + reasoningControl.wireFormat === 'reasoning_effort' && + appliedEffortLevel === 'max' && + reasoningControl.levels.includes('max') + ? 'max' + : standardEffortToOpenAI(appliedEffortLevel)) : undefined const containerId = process.env.CLAUDE_CODE_CONTAINER_ID const remoteSessionId = process.env.CLAUDE_CODE_REMOTE_SESSION_ID diff --git a/src/services/api/openaiShim.compression.test.ts b/src/services/api/openaiShim.compression.test.ts index f837f8f8b3..a3964e0eb5 100644 --- a/src/services/api/openaiShim.compression.test.ts +++ b/src/services/api/openaiShim.compression.test.ts @@ -215,9 +215,15 @@ afterEach(() => { async function captureRequestBody( messages: Array<{ role: string; content: unknown }>, model: string, + options: { useModelWindow?: boolean } = {}, ): Promise> { setCompressionEnabledForTest(mockState.enabled) - setEffectiveWindowForTest(mockState.effectiveWindow) + if (options.useModelWindow) { + delete process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + } else { + setEffectiveWindowForTest(mockState.effectiveWindow) + } let captured: Record | undefined globalThis.fetch = (async (_input, init) => { @@ -462,6 +468,21 @@ test('FIX: 1M context model with 30 exchanges → only first 5 mid-truncated', a } }) +test('Kimi K3 256K selection uses its own compression window while sending the k3 API name', async () => { + mockState.enabled = true + process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' + const messages = buildLongConversation(50, 5_000) + + const body = await captureRequestBody(messages, 'k3-256k', { + useModelWindow: true, + }) + const toolMessages = getToolMessages(body) + + expect(body.model).toBe('k3') + expect(toolMessages).toHaveLength(50) + expect(toolMessages[0].content).toContain('chars omitted') +}) + // ============================================================================ // FIX: stub preserves tool name and args — model can re-invoke if needed // ============================================================================ diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 8864db539c..39134d4909 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -4317,10 +4317,14 @@ class OpenAIShimMessages { message?: { role?: string; content?: unknown } content?: unknown }> + const runtimeModel = request.requestedModel + const compressedMessages = fastPath.skipToolHistoryCompression + ? rawMessages + : compressToolHistory(rawMessages, runtimeModel) const runtimeShimContext = resolveOpenAIShimRuntimeContext({ processEnv: requestProcessEnv, baseUrl: request.baseUrl, - model: request.resolvedModel, + model: runtimeModel, treatAsLocal: isLocalProviderUrl(request.baseUrl), preferBaseUrlRoute: Boolean(this.providerOverride), }) @@ -4365,7 +4369,7 @@ class OpenAIShimMessages { }), ) - const reasoningControl = resolveModelReasoningControl(request.resolvedModel, { + const reasoningControl = resolveModelReasoningControl(runtimeModel, { routeId: runtimeShimContext.routeId, useRuntimeFallback: false, openaiShimConfig: shimConfig, @@ -4382,7 +4386,7 @@ class OpenAIShimMessages { modelRequiresResponsesApi(request.resolvedModel) && baseUrlSupportsResponsesAutoRoute(request.baseUrl, requestProcessEnv) const reasoningRequestPlan = resolveOpenAIShimReasoningRequestPlan({ - model: request.resolvedModel, + model: runtimeModel, requestedEffort: suppressReasoningForForcedChat ? undefined : request.reasoning?.effort, requestThinkingType: (params.thinking as { type?: string } | undefined)?.type, defaultThinkingType: request.thinking?.type, diff --git a/src/utils/model/modelOptions.gateways.test.ts b/src/utils/model/modelOptions.gateways.test.ts index f0ba54cac4..2e7b38c51e 100644 --- a/src/utils/model/modelOptions.gateways.test.ts +++ b/src/utils/model/modelOptions.gateways.test.ts @@ -105,6 +105,17 @@ test('OpenRouter keeps static catalog entries and the active custom model', asyn expect(values).toContain('deepseek/deepseek-chat') }) +test('Kimi Code keeps context variants distinct in the active route picker', async () => { + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' + process.env.OPENAI_MODEL = 'k3-256k' + process.env.OPENAI_API_KEY = 'sk-kimi-test' + + const options = await getOpenAIModelOptions() + expect(options.find(option => option.value === 'k3')?.label).toBe('Kimi K3 (1M)') + expect(options.find(option => option.value === 'k3-256k')?.label).toBe('Kimi K3 (256K)') +}) + test('custom Anthropic endpoints use the third-party default description', async () => { process.env.ANTHROPIC_BASE_URL = 'https://proxy.example/v1' process.env.ANTHROPIC_MODEL = 'proxy-model' diff --git a/src/utils/providerProfiles.test.ts b/src/utils/providerProfiles.test.ts index 77c69959cd..4afef9e006 100644 --- a/src/utils/providerProfiles.test.ts +++ b/src/utils/providerProfiles.test.ts @@ -2473,7 +2473,7 @@ describe('getProviderPresetDefaults', () => { expect(defaults.provider).toBe('kimi-code') expect(defaults.name).toBe('Moonshot AI - Kimi Code') expect(defaults.baseUrl).toBe('https://api.kimi.com/coding/v1') - expect(defaults.model).toBe('kimi-for-coding') + expect(defaults.model).toBe('k3') expect(defaults.requiresApiKey).toBe(true) }) From 5cbb8f41855e78106fcbb4c88684017638a6d186 Mon Sep 17 00:00:00 2001 From: jatmn Date: Thu, 16 Jul 2026 15:04:08 -0700 Subject: [PATCH 05/23] fix(kimi): recommend the selected context variant --- src/utils/model/routeCatalogOptions.test.ts | 6 ++++-- src/utils/model/routeCatalogOptions.ts | 16 ++++++++++------ 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/utils/model/routeCatalogOptions.test.ts b/src/utils/model/routeCatalogOptions.test.ts index f8133a2bc7..2b0f50cfba 100644 --- a/src/utils/model/routeCatalogOptions.test.ts +++ b/src/utils/model/routeCatalogOptions.test.ts @@ -29,13 +29,15 @@ describe('buildRouteCatalogModelOptions', () => { ]) }) - test('keeps duplicate API models selectable by catalog id', () => { + test('keeps duplicate API models selectable by catalog id and recommends only the selected variant', () => { const options = buildRouteCatalogModelOptions('Kimi Code', [ { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)' }, { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)' }, - ]) + ], 'k3') expect(options.map(option => option.value)).toEqual(['k3', 'k3-256k']) + expect(options[0]?.description).toContain('Recommended') + expect(options[1]?.description).not.toContain('Recommended') }) test('surfaces catalog entry notes as a description tag', () => { diff --git a/src/utils/model/routeCatalogOptions.ts b/src/utils/model/routeCatalogOptions.ts index f7e0fd84a8..84009a436a 100644 --- a/src/utils/model/routeCatalogOptions.ts +++ b/src/utils/model/routeCatalogOptions.ts @@ -4,13 +4,10 @@ import type { ModelOption } from './modelOptions.js' function toDescription( entry: ModelCatalogEntry, routeLabel: string, - routeDefaultModel?: string, + isRouteDefault: boolean, ): string { const parts: string[] = [] - const isRecommended = - entry.default || - (routeDefaultModel !== undefined && - entry.apiName.trim().toLowerCase() === routeDefaultModel.trim().toLowerCase()) + const isRecommended = entry.default || isRouteDefault if (isRecommended) { parts.push('Recommended') @@ -52,6 +49,9 @@ export function buildRouteCatalogModelOptions( const seen = new Set() const options: ModelOption[] = [] const apiNameCounts = new Map() + const normalizedRouteDefault = routeDefaultModel?.trim().toLowerCase() + const routeDefaultMatchesEntryId = normalizedRouteDefault !== undefined && + entries.some(entry => entry.id.trim().toLowerCase() === normalizedRouteDefault) for (const entry of entries) { const key = entry.apiName.trim().toLowerCase() apiNameCounts.set(key, (apiNameCounts.get(key) ?? 0) + 1) @@ -68,7 +68,11 @@ export function buildRouteCatalogModelOptions( seen.add(value.toLowerCase()) const label = entry.label?.trim() || value - const description = toDescription(entry, routeLabel, routeDefaultModel) + const isRouteDefault = normalizedRouteDefault !== undefined && + (routeDefaultMatchesEntryId + ? entry.id.trim().toLowerCase() === normalizedRouteDefault + : apiName.toLowerCase() === normalizedRouteDefault) + const description = toDescription(entry, routeLabel, isRouteDefault) options.push({ value, From 12d4929976b69009fffc55d128af480698be5eb6 Mon Sep 17 00:00:00 2001 From: jatmn Date: Thu, 16 Jul 2026 15:55:10 -0700 Subject: [PATCH 06/23] fix(kimi): preserve max shim reasoning effort --- src/services/api/client.test.ts | 5 ++-- src/services/api/client.ts | 2 +- src/services/api/openaiShim.ts | 11 +++++---- src/services/api/providerConfig.test.ts | 32 +++++++++++++++++++++++++ src/services/api/providerConfig.ts | 23 +++++++++++++++--- src/utils/effort.ts | 13 ++++++---- 6 files changed, 69 insertions(+), 17 deletions(-) diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index a4f4f29356..519fc5f918 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2085,7 +2085,7 @@ test('providerOverride Kimi Code clamps unsupported xhigh effort to high', async } }) -test('providerOverride K3 preserves its verified max reasoning effort', async () => { +test('providerOverride K3 preserves max reasoning from its model query', async () => { let requestBody: Record | undefined globalThis.fetch = (async (_input, init) => { @@ -2101,9 +2101,8 @@ test('providerOverride K3 preserves its verified max reasoning effort', async () try { const client = (await getAnthropicClient({ maxRetries: 0, - effortValue: 'max', providerOverride: { - model: 'k3', + model: 'k3?reasoning=max', baseURL: 'https://api.kimi.com/coding/v1', apiKey: 'kimi-test-key', }, diff --git a/src/services/api/client.ts b/src/services/api/client.ts index 9f53c12357..e747351fde 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -17,7 +17,7 @@ import { modelSupportsShimReasoningEffort, modelSupportsWireEffort, standardEffortToOpenAI, - type OpenAIEffortLevel, + type OpenAIShimEffortLevel, } from 'src/utils/effort.js' import { getUserAgent } from 'src/utils/http.js' import { getSmallFastModel } from 'src/utils/model/model.js' diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 39134d4909..4124ca057f 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -46,6 +46,7 @@ import { isBareMode, isEnvTruthy } from '../../utils/envUtils.js' import { resolveModelReasoningControl, resolveOpenAIShimReasoningRequestPlan, + type OpenAIShimEffortLevel, } from '../../utils/effort.js' import { resolveGeminiCredential } from '../../utils/geminiAuth.js' import { hydrateGeminiAccessTokenFromSecureStorage } from '../../utils/geminiCredentials.js' @@ -3994,12 +3995,12 @@ class OpenAIShimStream { class OpenAIShimMessages { private defaultHeaders: Record - private reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' + private reasoningEffort?: OpenAIShimEffortLevel private providerOverride?: { model: string; baseURL: string; apiKey: string } private credentialPool?: CredentialPool private credentialPoolRaw?: string - constructor(defaultHeaders: Record, reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh', providerOverride?: { model: string; baseURL: string; apiKey: string }) { + constructor(defaultHeaders: Record, reasoningEffort?: OpenAIShimEffortLevel, providerOverride?: { model: string; baseURL: string; apiKey: string }) { this.defaultHeaders = filterAnthropicHeaders(defaultHeaders) this.reasoningEffort = reasoningEffort this.providerOverride = providerOverride @@ -5717,9 +5718,9 @@ class OpenAIShimMessages { class OpenAIShimBeta { messages: OpenAIShimMessages - reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' + reasoningEffort?: OpenAIShimEffortLevel - constructor(defaultHeaders: Record, reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh', providerOverride?: { model: string; baseURL: string; apiKey: string }) { + constructor(defaultHeaders: Record, reasoningEffort?: OpenAIShimEffortLevel, providerOverride?: { model: string; baseURL: string; apiKey: string }) { this.messages = new OpenAIShimMessages(defaultHeaders, reasoningEffort, providerOverride) this.reasoningEffort = reasoningEffort } @@ -5729,7 +5730,7 @@ export function createOpenAIShimClient(options: { defaultHeaders?: Record maxRetries?: number timeout?: number - reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' + reasoningEffort?: OpenAIShimEffortLevel providerOverride?: { model: string; baseURL: string; apiKey: string } }): unknown { hydrateGeminiAccessTokenFromSecureStorage() diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index 590f762eac..88c6e7d202 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -62,6 +62,38 @@ test('resolveProviderRequest maps K3 context catalog choices to the Kimi API mod } }) +test('resolveProviderRequest preserves K3 max reasoning from its model query', () => { + const request = resolveProviderRequest({ + model: 'k3?reasoning=max', + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: {}, + }) + + expect(request.requestedModel).toBe('k3?reasoning=max') + expect(request.resolvedModel).toBe('k3') + expect(request.reasoning).toEqual({ effort: 'max' }) +}) + +test('resolveProviderRequest rejects max reasoning for models without K3 support', () => { + const request = resolveProviderRequest({ + model: 'gpt-4o?reasoning=max', + baseUrl: 'https://api.openai.com/v1', + processEnv: {}, + }) + + expect(request.reasoning).toBeUndefined() +}) + +test('resolveProviderRequest rejects K3 max reasoning outside a Kimi route', () => { + const request = resolveProviderRequest({ + model: 'k3?reasoning=max', + baseUrl: 'https://api.openai.com/v1', + processEnv: {}, + }) + + expect(request.reasoning).toBeUndefined() +}) + test('resolveProviderRequest maps explicit Atlas coding aliases without ambiguity', () => { expect(resolveProviderRequest({ model: 'claude-sonnet-4-6', diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index b51e75db4f..c8d95119e3 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -144,7 +144,7 @@ const CODEX_ALIAS_MODELS: Record< } as const type CodexAlias = keyof typeof CODEX_ALIAS_MODELS -type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' +type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' type ThinkingType = 'enabled' | 'disabled' const OPENAI_CODEX_SHORTCUT_ALIASES = new Set(['codexplan', 'codexspark']) @@ -317,7 +317,7 @@ function resolveRouteCatalogAliasApiName(options: { function parseReasoningEffort(value: string | undefined): ReasoningEffort | undefined { if (!value) return undefined const normalized = value.trim().toLowerCase() - if (normalized === 'low' || normalized === 'medium' || normalized === 'high' || normalized === 'xhigh') { + if (normalized === 'low' || normalized === 'medium' || normalized === 'high' || normalized === 'xhigh' || normalized === 'max') { return normalized } return undefined @@ -1070,6 +1070,16 @@ export function resolveProviderRequest(options?: { model: resolvedModel, treatAsLocal: finalBaseUrl ? isLocalProviderUrl(finalBaseUrl) : false, }) + const explicitBaseUrlRuntimeContext = + isGithubMode + ? null + : resolveOpenAIShimRuntimeContext({ + processEnv, + baseUrl: finalBaseUrl, + model: resolvedModel, + treatAsLocal: finalBaseUrl ? isLocalProviderUrl(finalBaseUrl) : false, + preferBaseUrlRoute: true, + }) const explicitApiFormat = isGithubMode ? undefined @@ -1121,13 +1131,20 @@ export function resolveProviderRequest(options?: { // still flow on every transport, and the older aliases (gpt-5.4/5.5, // codexplan) keep the pre-5.6 legacy behavior of carrying their default // effort everywhere. - const reasoning = options?.reasoningEffortOverride + const requestedReasoning = options?.reasoningEffortOverride ? { effort: options.reasoningEffortOverride } : descriptor.reasoningFromAlias && transport !== 'codex_responses' && /^gpt-5\.6/.test(descriptor.baseModel) ? undefined : descriptor.reasoning + const supportsMaxReasoning = + (explicitBaseUrlRuntimeContext?.routeId === 'kimi-code' && resolvedModel === 'k3') || + (explicitBaseUrlRuntimeContext?.catalogEntry?.reasoning?.wireFormat === 'reasoning_effort' && + explicitBaseUrlRuntimeContext.catalogEntry.reasoning.levels?.includes('max') === true) + const reasoning = requestedReasoning?.effort === 'max' && !supportsMaxReasoning + ? undefined + : requestedReasoning return { transport, diff --git a/src/utils/effort.ts b/src/utils/effort.ts index 9d80ee1734..f6ebbaf98f 100644 --- a/src/utils/effort.ts +++ b/src/utils/effort.ts @@ -44,6 +44,9 @@ export const OPENAI_EFFORT_LEVELS = [ ] as const export type OpenAIEffortLevel = typeof OPENAI_EFFORT_LEVELS[number] +// OpenAI-compatible shims also serve providers such as Kimi that accept the +// provider-specific `max` value in the same `reasoning_effort` wire field. +export type OpenAIShimEffortLevel = OpenAIEffortLevel | 'max' export type EffortValue = EffortLevel | number export type ReasoningControlResolution = { @@ -197,15 +200,15 @@ function normalizeReasoningThinkingType( } function normalizeDeepSeekReasoningEffort( - effort: 'low' | 'medium' | 'high' | 'xhigh', + effort: OpenAIShimEffortLevel, ): 'high' | 'max' { - return effort === 'xhigh' ? 'max' : 'high' + return effort === 'xhigh' || effort === 'max' ? 'max' : 'high' } function normalizeZaiReasoningEffort( - effort: 'low' | 'medium' | 'high' | 'xhigh', + effort: OpenAIShimEffortLevel, ): 'high' | 'max' { - return effort === 'xhigh' ? 'max' : 'high' + return effort === 'xhigh' || effort === 'max' ? 'max' : 'high' } function resolveCompatibilityWireFormat( @@ -580,7 +583,7 @@ export function modelSupportsWireEffort(model: string, context?: ReasoningContro export function resolveOpenAIShimReasoningRequestPlan(options: { model: string - requestedEffort?: OpenAIEffortLevel + requestedEffort?: OpenAIShimEffortLevel requestThinkingType?: string defaultThinkingType?: string thinkingRequestFormat?: OpenAIShimThinkingRequestFormat From 1dc3e777ad23659c3cb5c8a166a0e9d4dd07cd26 Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 07:50:10 -0700 Subject: [PATCH 07/23] fix(kimi): validate K3 reasoning levels --- src/integrations/gateways/kimi-code.ts | 4 +-- src/integrations/runtimeMetadata.test.ts | 2 +- src/integrations/vendors/moonshot.ts | 2 +- src/services/api/client.test.ts | 37 ++++++++++++++++++++++++ src/services/api/providerConfig.test.ts | 25 ++++++++++++++++ src/services/api/providerConfig.ts | 13 +++++++-- src/utils/effort.codex.test.ts | 5 ++-- 7 files changed, 79 insertions(+), 9 deletions(-) diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index 3a09732369..bf7a4d2cb8 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -33,8 +33,8 @@ export default defineGateway({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, - { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index ef80a39931..ece12577fa 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -399,7 +399,7 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad contextWindow: 1_048_576, label: 'Kimi K3 (1M)', }) - expect(k3.catalogEntry?.reasoning?.levels).toEqual(['max']) + expect(k3.catalogEntry?.reasoning?.levels).toEqual(['low', 'high', 'max']) expect(resolveModelRuntimeLimits({ model: 'k3-256k', baseUrl: 'https://api.kimi.com/coding/v1', diff --git a/src/integrations/vendors/moonshot.ts b/src/integrations/vendors/moonshot.ts index 6d5ce63900..b61ed3569d 100644 --- a/src/integrations/vendors/moonshot.ts +++ b/src/integrations/vendors/moonshot.ts @@ -34,7 +34,7 @@ export default defineVendor({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.6', apiName: 'kimi-k2.6', aliases: ['moonshotai/kimi-k2.6'], label: 'Kimi K2.6', modelDescriptorId: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.5', apiName: 'kimi-k2.5', aliases: ['moonshotai/kimi-k2.5'], label: 'Kimi K2.5', modelDescriptorId: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 519fc5f918..756de3a187 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2121,6 +2121,43 @@ test('providerOverride K3 preserves max reasoning from its model query', async ( globalThis.fetch = originalFetch } }) + +test('providerOverride K3 preserves low reasoning from its model query', async () => { + let requestBody: Record | undefined + + globalThis.fetch = (async (_input, init) => { + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ + id: 'chatcmpl-provider-override-k3-low', + model: 'k3', + choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }, + }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + + try { + const client = (await getAnthropicClient({ + maxRetries: 0, + providerOverride: { + model: 'k3?reasoning=low', + baseURL: 'https://api.kimi.com/coding/v1', + apiKey: 'kimi-test-key', + }, + })) as unknown as ShimClient + + await client.beta.messages.create({ + model: 'unused', + system: 'test system', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + expect(requestBody?.reasoning_effort).toBe('low') + } finally { + globalThis.fetch = originalFetch + } +}) test('providerOverride Atlas Grok Build does not send reasoning effort', async () => { let requestBody: Record | undefined diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index 88c6e7d202..a90d836e2d 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -74,6 +74,31 @@ test('resolveProviderRequest preserves K3 max reasoning from its model query', ( expect(request.reasoning).toEqual({ effort: 'max' }) }) +test('resolveProviderRequest preserves K3 documented reasoning levels from its model query', () => { + for (const effort of ['low', 'high', 'max'] as const) { + const request = resolveProviderRequest({ + model: `k3-256k?reasoning=${effort}`, + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: {}, + }) + + expect(request.resolvedModel).toBe('k3') + expect(request.reasoning).toEqual({ effort }) + } +}) + +test('resolveProviderRequest rejects unsupported K3 reasoning from its model query', () => { + for (const effort of ['medium', 'xhigh']) { + const request = resolveProviderRequest({ + model: `k3?reasoning=${effort}`, + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: {}, + }) + + expect(request.reasoning).toBeUndefined() + } +}) + test('resolveProviderRequest rejects max reasoning for models without K3 support', () => { const request = resolveProviderRequest({ model: 'gpt-4o?reasoning=max', diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index c8d95119e3..3e9bbc7329 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -148,6 +148,7 @@ type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' type ThinkingType = 'enabled' | 'disabled' const OPENAI_CODEX_SHORTCUT_ALIASES = new Set(['codexplan', 'codexspark']) +const KIMI_K3_REASONING_LEVELS = new Set(['low', 'high', 'max']) export type ProviderTransport = 'chat_completions' | 'responses' | 'responses_compat' | 'codex_responses' export type OpenAICompatibleApiFormat = 'chat_completions' | 'responses' | 'responses_compat' @@ -1142,9 +1143,15 @@ export function resolveProviderRequest(options?: { (explicitBaseUrlRuntimeContext?.routeId === 'kimi-code' && resolvedModel === 'k3') || (explicitBaseUrlRuntimeContext?.catalogEntry?.reasoning?.wireFormat === 'reasoning_effort' && explicitBaseUrlRuntimeContext.catalogEntry.reasoning.levels?.includes('max') === true) - const reasoning = requestedReasoning?.effort === 'max' && !supportsMaxReasoning - ? undefined - : requestedReasoning + const isKimiCodeK3 = + explicitBaseUrlRuntimeContext?.routeId === 'kimi-code' && resolvedModel === 'k3' + const reasoning = + (requestedReasoning?.effort === 'max' && !supportsMaxReasoning) || + (isKimiCodeK3 && + requestedReasoning?.effort !== undefined && + !KIMI_K3_REASONING_LEVELS.has(requestedReasoning.effort)) + ? undefined + : requestedReasoning return { transport, diff --git a/src/utils/effort.codex.test.ts b/src/utils/effort.codex.test.ts index 8b6edd1ffe..e9f13f2ae8 100644 --- a/src/utils/effort.codex.test.ts +++ b/src/utils/effort.codex.test.ts @@ -640,12 +640,13 @@ test('Moonshot direct and Kimi Code catalogs expose verified reasoning controls' supportsReasoning: true, controllable: true, source: 'metadata', - levels: ['max'], + levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort', }) - expect(getAvailableEffortLevels('k3')).toEqual(['max']) + expect(getAvailableEffortLevels('k3')).toEqual(['low', 'high', 'max']) expect(resolveAppliedEffort('k3', undefined)).toBe('max') + expect(resolveAppliedEffort('k3', 'low')).toBe('low') expect(resolveModelReasoningControl('kimi-k2.7-code')).toMatchObject({ supportsReasoning: true, From a018d6c61404567cd449cb68e012df52ac3868cf Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 08:26:43 -0700 Subject: [PATCH 08/23] fix(kimi): reconcile K3 route metadata --- src/integrations/gateways/kimi-code.ts | 4 +- src/integrations/runtimeMetadata.test.ts | 4 +- src/integrations/vendors/moonshot.ts | 2 +- .../api/openaiShim.compression.test.ts | 57 ++++++++++++------- src/services/api/providerConfig.test.ts | 23 +++++++- src/services/api/providerConfig.ts | 33 +++++++---- src/utils/context.test.ts | 2 +- src/utils/effort.codex.test.ts | 6 +- src/utils/providerProfiles.test.ts | 2 +- 9 files changed, 92 insertions(+), 41 deletions(-) diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index bf7a4d2cb8..bc090b35f3 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -5,7 +5,9 @@ export default defineGateway({ label: 'Moonshot AI - Kimi Code', category: 'hosted', defaultBaseUrl: 'https://api.kimi.com/coding/v1', - defaultModel: 'k3', + // Moderato subscriptions are limited to 256K; users with Allegretto+ can + // explicitly select the 1M variant. + defaultModel: 'k3-256k', supportsModelRouting: true, setup: { requiresAuth: true, diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index ece12577fa..a653975d07 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -306,11 +306,11 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad it('uses Moonshot direct catalog order, limits, and reasoning controls', () => { expect( resolveModelRuntimeLimits({ - model: 'k3', + model: 'kimi-k3', baseUrl: 'https://api.moonshot.ai/v1', processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, }), - ).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 32_768 }) + ).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 1_048_576 }) expect( resolveModelRuntimeLimits({ diff --git a/src/integrations/vendors/moonshot.ts b/src/integrations/vendors/moonshot.ts index b61ed3569d..d5086d228d 100644 --- a/src/integrations/vendors/moonshot.ts +++ b/src/integrations/vendors/moonshot.ts @@ -34,7 +34,7 @@ export default defineVendor({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, + { id: 'k3', apiName: 'kimi-k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 1_048_576, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.6', apiName: 'kimi-k2.6', aliases: ['moonshotai/kimi-k2.6'], label: 'Kimi K2.6', modelDescriptorId: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.5', apiName: 'kimi-k2.5', aliases: ['moonshotai/kimi-k2.5'], label: 'Kimi K2.5', modelDescriptorId: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, diff --git a/src/services/api/openaiShim.compression.test.ts b/src/services/api/openaiShim.compression.test.ts index a3964e0eb5..52eda437a3 100644 --- a/src/services/api/openaiShim.compression.test.ts +++ b/src/services/api/openaiShim.compression.test.ts @@ -217,29 +217,46 @@ async function captureRequestBody( model: string, options: { useModelWindow?: boolean } = {}, ): Promise> { - setCompressionEnabledForTest(mockState.enabled) - if (options.useModelWindow) { - delete process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW - delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS - } else { - setEffectiveWindowForTest(mockState.effectiveWindow) - } - let captured: Record | undefined + const originalAutoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW + const originalMaxOutputTokens = process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + const originalFetch = globalThis.fetch + try { + setCompressionEnabledForTest(mockState.enabled) + if (options.useModelWindow) { + delete process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + } else { + setEffectiveWindowForTest(mockState.effectiveWindow) + } + let captured: Record | undefined - globalThis.fetch = (async (_input, init) => { - captured = JSON.parse(String(init?.body)) - return makeFakeResponse() - }) as FetchType + globalThis.fetch = (async (_input, init) => { + captured = JSON.parse(String(init?.body)) + return makeFakeResponse() + }) as FetchType - const client = createOpenAIShimClient({}) as OpenAIShimClient - await client.beta.messages.create({ - model, - system: 'system prompt', - messages, - }) + const client = createOpenAIShimClient({}) as OpenAIShimClient + await client.beta.messages.create({ + model, + system: 'system prompt', + messages, + }) - if (!captured) throw new Error('request not captured') - return captured + if (!captured) throw new Error('request not captured') + return captured + } finally { + if (originalAutoCompactWindow === undefined) { + delete process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW + } else { + process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW = originalAutoCompactWindow + } + if (originalMaxOutputTokens === undefined) { + delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS + } else { + process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS = originalMaxOutputTokens + } + globalThis.fetch = originalFetch + } } async function captureResponsesRequestBody( diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index a90d836e2d..02271a2564 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -87,14 +87,33 @@ test('resolveProviderRequest preserves K3 documented reasoning levels from its m } }) -test('resolveProviderRequest rejects unsupported K3 reasoning from its model query', () => { - for (const effort of ['medium', 'xhigh']) { +test('resolveProviderRequest canonicalizes Kimi Code K3 reasoning aliases', () => { + for (const [effort, expected] of [['medium', 'high'], ['xhigh', 'max']] as const) { const request = resolveProviderRequest({ model: `k3?reasoning=${effort}`, baseUrl: 'https://api.kimi.com/coding/v1', processEnv: {}, }) + expect(request.reasoning).toEqual({ effort: expected }) + } +}) + +test('resolveProviderRequest uses the direct Moonshot K3 API name and only permits max reasoning', () => { + const max = resolveProviderRequest({ + model: 'k3?reasoning=max', + baseUrl: 'https://api.moonshot.ai/v1', + processEnv: {}, + }) + expect(max.resolvedModel).toBe('kimi-k3') + expect(max.reasoning).toEqual({ effort: 'max' }) + + for (const effort of ['low', 'medium', 'high', 'xhigh']) { + const request = resolveProviderRequest({ + model: `k3?reasoning=${effort}`, + baseUrl: 'https://api.moonshot.ai/v1', + processEnv: {}, + }) expect(request.reasoning).toBeUndefined() } }) diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index 3e9bbc7329..3593ba0b53 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -148,7 +148,13 @@ type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' type ThinkingType = 'enabled' | 'disabled' const OPENAI_CODEX_SHORTCUT_ALIASES = new Set(['codexplan', 'codexspark']) -const KIMI_K3_REASONING_LEVELS = new Set(['low', 'high', 'max']) +const KIMI_CODE_K3_REASONING_ALIASES: Record = { + low: 'low', + medium: 'high', + high: 'high', + xhigh: 'max', + max: 'max', +} export type ProviderTransport = 'chat_completions' | 'responses' | 'responses_compat' | 'codex_responses' export type OpenAICompatibleApiFormat = 'chat_completions' | 'responses' | 'responses_compat' @@ -1139,19 +1145,26 @@ export function resolveProviderRequest(options?: { /^gpt-5\.6/.test(descriptor.baseModel) ? undefined : descriptor.reasoning - const supportsMaxReasoning = - (explicitBaseUrlRuntimeContext?.routeId === 'kimi-code' && resolvedModel === 'k3') || - (explicitBaseUrlRuntimeContext?.catalogEntry?.reasoning?.wireFormat === 'reasoning_effort' && - explicitBaseUrlRuntimeContext.catalogEntry.reasoning.levels?.includes('max') === true) const isKimiCodeK3 = explicitBaseUrlRuntimeContext?.routeId === 'kimi-code' && resolvedModel === 'k3' + const normalizedReasoning = + isKimiCodeK3 && requestedReasoning?.effort !== undefined + ? { effort: KIMI_CODE_K3_REASONING_ALIASES[requestedReasoning.effort] } + : requestedReasoning + const catalogReasoningLevels = + explicitBaseUrlRuntimeContext?.catalogEntry?.modelDescriptorId === 'k3' && + explicitBaseUrlRuntimeContext.catalogEntry.reasoning?.wireFormat === 'reasoning_effort' + ? explicitBaseUrlRuntimeContext.catalogEntry.reasoning.levels + : undefined + const supportsMaxReasoning = + catalogReasoningLevels?.includes('max') === true const reasoning = - (requestedReasoning?.effort === 'max' && !supportsMaxReasoning) || - (isKimiCodeK3 && - requestedReasoning?.effort !== undefined && - !KIMI_K3_REASONING_LEVELS.has(requestedReasoning.effort)) + (normalizedReasoning?.effort === 'max' && !supportsMaxReasoning) || + (catalogReasoningLevels !== undefined && + normalizedReasoning?.effort !== undefined && + !catalogReasoningLevels.includes(normalizedReasoning.effort)) ? undefined - : requestedReasoning + : normalizedReasoning return { transport, diff --git a/src/utils/context.test.ts b/src/utils/context.test.ts index 7ced3d568d..cec4c1d110 100644 --- a/src/utils/context.test.ts +++ b/src/utils/context.test.ts @@ -841,7 +841,7 @@ test('Kimi Code kimi-for-coding uses provider-specific context and output caps', }) }) -test('Kimi Code K3 defaults to the Allegretto 1M cap', () => { +test('Kimi Code K3 1M choice uses the Allegretto cap', () => { process.env.CLAUDE_CODE_USE_OPENAI = '1' process.env.OPENAI_BASE_URL = 'https://api.kimi.com/coding/v1' delete process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS diff --git a/src/utils/effort.codex.test.ts b/src/utils/effort.codex.test.ts index e9f13f2ae8..50da469328 100644 --- a/src/utils/effort.codex.test.ts +++ b/src/utils/effort.codex.test.ts @@ -640,13 +640,13 @@ test('Moonshot direct and Kimi Code catalogs expose verified reasoning controls' supportsReasoning: true, controllable: true, source: 'metadata', - levels: ['low', 'high', 'max'], + levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort', }) - expect(getAvailableEffortLevels('k3')).toEqual(['low', 'high', 'max']) + expect(getAvailableEffortLevels('k3')).toEqual(['max']) expect(resolveAppliedEffort('k3', undefined)).toBe('max') - expect(resolveAppliedEffort('k3', 'low')).toBe('low') + expect(resolveAppliedEffort('k3', 'low')).toBe('max') expect(resolveModelReasoningControl('kimi-k2.7-code')).toMatchObject({ supportsReasoning: true, diff --git a/src/utils/providerProfiles.test.ts b/src/utils/providerProfiles.test.ts index 4afef9e006..1c69283574 100644 --- a/src/utils/providerProfiles.test.ts +++ b/src/utils/providerProfiles.test.ts @@ -2473,7 +2473,7 @@ describe('getProviderPresetDefaults', () => { expect(defaults.provider).toBe('kimi-code') expect(defaults.name).toBe('Moonshot AI - Kimi Code') expect(defaults.baseUrl).toBe('https://api.kimi.com/coding/v1') - expect(defaults.model).toBe('k3') + expect(defaults.model).toBe('k3-256k') expect(defaults.requiresApiKey).toBe(true) }) From 3918aadaec959924a62f5120663edaa8d33caf1a Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 09:08:12 -0700 Subject: [PATCH 09/23] test(integrations): accept catalog default ids --- src/integrations/index.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/integrations/index.test.ts b/src/integrations/index.test.ts index 66b203c92e..23fd0c485a 100644 --- a/src/integrations/index.test.ts +++ b/src/integrations/index.test.ts @@ -99,6 +99,7 @@ describe('loaded registry validation', () => { const defaultModel = gateway.defaultModel?.trim() return !(gateway.catalog?.models ?? []).some( entry => + entry.id === defaultModel || entry.apiName === defaultModel || entry.modelDescriptorId === defaultModel, ) From f241893ca788cc2da997ce6f9fc4f4732a6311e5 Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 09:53:42 -0700 Subject: [PATCH 10/23] fix(kimi): preserve K3 runtime limits and effort aliases --- src/integrations/runtimeMetadata.test.ts | 2 +- src/integrations/vendors/moonshot.ts | 2 +- src/services/api/client.test.ts | 15 +++++++++++++++ src/utils/effort.ts | 8 ++++++++ 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index a653975d07..9da44ccdb3 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -310,7 +310,7 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad baseUrl: 'https://api.moonshot.ai/v1', processEnv: { CLAUDE_CODE_USE_OPENAI: '1' }, }), - ).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 1_048_576 }) + ).toEqual({ contextWindow: 1_048_576, maxOutputTokens: 32_768 }) expect( resolveModelRuntimeLimits({ diff --git a/src/integrations/vendors/moonshot.ts b/src/integrations/vendors/moonshot.ts index d5086d228d..27daca267e 100644 --- a/src/integrations/vendors/moonshot.ts +++ b/src/integrations/vendors/moonshot.ts @@ -34,7 +34,7 @@ export default defineVendor({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'kimi-k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 1_048_576, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, + { id: 'k3', apiName: 'kimi-k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.6', apiName: 'kimi-k2.6', aliases: ['moonshotai/kimi-k2.6'], label: 'Kimi K2.6', modelDescriptorId: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.5', apiName: 'kimi-k2.5', aliases: ['moonshotai/kimi-k2.5'], label: 'Kimi K2.5', modelDescriptorId: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 756de3a187..60f24c716b 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2122,6 +2122,21 @@ test('providerOverride K3 preserves max reasoning from its model query', async ( } }) +test('providerOverride K3 maps xhigh effort to max', async () => { + let requestBody: Record | undefined + globalThis.fetch = (async (_input, init) => { + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ id: 'chatcmpl-provider-override-k3-xhigh', model: 'k3', choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 } }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + try { + const client = (await getAnthropicClient({ maxRetries: 0, effortValue: 'xhigh', providerOverride: { model: 'k3', baseURL: 'https://api.kimi.com/coding/v1', apiKey: 'kimi-test-key' } })) as unknown as ShimClient + await client.beta.messages.create({ model: 'unused', system: 'test system', messages: [{ role: 'user', content: 'hello' }], max_tokens: 64, stream: false }) + expect(requestBody?.reasoning_effort).toBe('max') + } finally { + globalThis.fetch = originalFetch + } +}) + test('providerOverride K3 preserves low reasoning from its model query', async () => { let requestBody: Record | undefined diff --git a/src/utils/effort.ts b/src/utils/effort.ts index f6ebbaf98f..0cb6a92280 100644 --- a/src/utils/effort.ts +++ b/src/utils/effort.ts @@ -965,6 +965,14 @@ export function resolveAppliedEffort( const resolved = envOverride ?? appStateEffortValue ?? getDefaultEffortForModel(model, context) const control = resolveModelReasoningControl(model, context) + if ( + resolved === 'xhigh' && + control.source === 'metadata' && + control.wireFormat === 'reasoning_effort' && + control.levels.includes('max') + ) { + return 'max' + } if ( typeof resolved === 'string' && (control.source === 'metadata' || control.source === 'capability' || control.source === 'compat') && From c2596a4c8f49ee9e261d17542f23a64f298e184f Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 10:21:18 -0700 Subject: [PATCH 11/23] fix(kimi): preserve override context limits --- docs/integrations/overview.md | 13 +++++++++ src/services/api/client.test.ts | 18 ++++++++++--- src/services/api/compressToolHistory.ts | 6 ++++- src/services/api/openaiShim.ts | 13 ++++++--- src/services/api/providerConfig.test.ts | 13 +++++++++ src/services/api/providerConfig.ts | 1 + src/services/compact/autoCompact.test.ts | 34 ++++++++++++++++++++++++ src/services/compact/autoCompact.ts | 18 ++++++++++--- src/utils/context.ts | 9 ++++--- 9 files changed, 111 insertions(+), 14 deletions(-) diff --git a/docs/integrations/overview.md b/docs/integrations/overview.md index fa28a55b90..05de2f64dd 100644 --- a/docs/integrations/overview.md +++ b/docs/integrations/overview.md @@ -126,6 +126,19 @@ fields. Only add `reasoning` metadata when the exact route/model request shape, accepted levels, and disable behavior have been verified. See `docs/integrations/reasoning-effort.md`. +### Moonshot and Kimi K3 catalogs + +The direct Moonshot API exposes Kimi K3 as `kimi-k3`, with a 1,048,576-token +context window, 32,768 maximum output tokens, and `reasoning_effort: "max"` as +its only supported reasoning control. The Kimi Code subscription endpoint uses +the `k3` API model for two catalog selections: `k3` provides the 1M Allegretto+ +window, while `k3-256k` keeps Moderato+ sessions within their 256K limit. + +Kimi Code also lists `kimi-for-coding-highspeed` for eligible Allegretto+ +subscriptions. It uses six times the speed and three times the quota; retain +the selected catalog ID in client-side routing so its route-specific limits and +capabilities are not lost when the outbound API model is normalized. + ## Descriptor Authoring Pattern Normal descriptor files should: diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 60f24c716b..89daecae9c 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2087,8 +2087,10 @@ test('providerOverride Kimi Code clamps unsupported xhigh effort to high', async test('providerOverride K3 preserves max reasoning from its model query', async () => { let requestBody: Record | undefined + let requestUrl: string | undefined - globalThis.fetch = (async (_input, init) => { + globalThis.fetch = (async (input, init) => { + requestUrl = String(input) requestBody = JSON.parse(String(init?.body)) return new Response(JSON.stringify({ id: 'chatcmpl-provider-override-k3', @@ -2117,6 +2119,8 @@ test('providerOverride K3 preserves max reasoning from its model query', async ( }) expect(requestBody?.reasoning_effort).toBe('max') + expect(requestBody?.model).toBe('k3') + expect(requestUrl).toStartWith('https://api.kimi.com/coding/v1/') } finally { globalThis.fetch = originalFetch } @@ -2124,7 +2128,9 @@ test('providerOverride K3 preserves max reasoning from its model query', async ( test('providerOverride K3 maps xhigh effort to max', async () => { let requestBody: Record | undefined - globalThis.fetch = (async (_input, init) => { + let requestUrl: string | undefined + globalThis.fetch = (async (input, init) => { + requestUrl = String(input) requestBody = JSON.parse(String(init?.body)) return new Response(JSON.stringify({ id: 'chatcmpl-provider-override-k3-xhigh', model: 'k3', choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 } }), { headers: { 'Content-Type': 'application/json' } }) }) as FetchType @@ -2132,6 +2138,8 @@ test('providerOverride K3 maps xhigh effort to max', async () => { const client = (await getAnthropicClient({ maxRetries: 0, effortValue: 'xhigh', providerOverride: { model: 'k3', baseURL: 'https://api.kimi.com/coding/v1', apiKey: 'kimi-test-key' } })) as unknown as ShimClient await client.beta.messages.create({ model: 'unused', system: 'test system', messages: [{ role: 'user', content: 'hello' }], max_tokens: 64, stream: false }) expect(requestBody?.reasoning_effort).toBe('max') + expect(requestBody?.model).toBe('k3') + expect(requestUrl).toStartWith('https://api.kimi.com/coding/v1/') } finally { globalThis.fetch = originalFetch } @@ -2139,8 +2147,10 @@ test('providerOverride K3 maps xhigh effort to max', async () => { test('providerOverride K3 preserves low reasoning from its model query', async () => { let requestBody: Record | undefined + let requestUrl: string | undefined - globalThis.fetch = (async (_input, init) => { + globalThis.fetch = (async (input, init) => { + requestUrl = String(input) requestBody = JSON.parse(String(init?.body)) return new Response(JSON.stringify({ id: 'chatcmpl-provider-override-k3-low', @@ -2169,6 +2179,8 @@ test('providerOverride K3 preserves low reasoning from its model query', async ( }) expect(requestBody?.reasoning_effort).toBe('low') + expect(requestBody?.model).toBe('k3') + expect(requestUrl).toStartWith('https://api.kimi.com/coding/v1/') } finally { globalThis.fetch = originalFetch } diff --git a/src/services/api/compressToolHistory.ts b/src/services/api/compressToolHistory.ts index 84dc2674cf..1fceda9362 100644 --- a/src/services/api/compressToolHistory.ts +++ b/src/services/api/compressToolHistory.ts @@ -391,6 +391,7 @@ export function compressToolHistory( options: { effectiveContextWindowSize?: number textBlockSeparator?: string + runtimeLimits?: { contextWindow?: number; maxOutputTokens?: number } } = {}, ): T[] { // Master kill-switch. Returns the original reference so callers skip a @@ -401,7 +402,10 @@ export function compressToolHistory( if (!compressionEnabled) return messages const tiers = getTiers( - options.effectiveContextWindowSize ?? getEffectiveContextWindowSize(model), + options.effectiveContextWindowSize ?? getEffectiveContextWindowSize( + model, + options.runtimeLimits, + ), ) const textBlockSeparator = options.textBlockSeparator ?? '\n\n' diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 4124ca057f..310e476a97 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -4319,9 +4319,6 @@ class OpenAIShimMessages { content?: unknown }> const runtimeModel = request.requestedModel - const compressedMessages = fastPath.skipToolHistoryCompression - ? rawMessages - : compressToolHistory(rawMessages, runtimeModel) const runtimeShimContext = resolveOpenAIShimRuntimeContext({ processEnv: requestProcessEnv, baseUrl: request.baseUrl, @@ -4329,6 +4326,16 @@ class OpenAIShimMessages { treatAsLocal: isLocalProviderUrl(request.baseUrl), preferBaseUrlRoute: Boolean(this.providerOverride), }) + const compressedMessages = fastPath.skipToolHistoryCompression + ? rawMessages + : compressToolHistory(rawMessages, runtimeModel, { + runtimeLimits: runtimeShimContext.catalogEntry + ? { + contextWindow: runtimeShimContext.catalogEntry.contextWindow, + maxOutputTokens: runtimeShimContext.catalogEntry.maxOutputTokens, + } + : undefined, + }) const shimConfig = runtimeShimContext.openaiShimConfig // When endpointPath is overridden, the body format must match the target // API contract rather than request.transport from providerConfig. diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index 02271a2564..40d4c7fb83 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -62,6 +62,19 @@ test('resolveProviderRequest maps K3 context catalog choices to the Kimi API mod } }) +test('resolveProviderRequest maps K3 context choices against an explicit Kimi base URL', () => { + const request = resolveProviderRequest({ + model: 'k3-256k', + baseUrl: 'https://api.kimi.com/coding/v1', + processEnv: { + CLAUDE_CODE_USE_OPENAI: '1', + OPENAI_BASE_URL: 'https://api.openai.com/v1', + }, + }) + + expect(request.resolvedModel).toBe('k3') +}) + test('resolveProviderRequest preserves K3 max reasoning from its model query', () => { const request = resolveProviderRequest({ model: 'k3?reasoning=max', diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index 3593ba0b53..a6c055903e 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -305,6 +305,7 @@ function resolveRouteCatalogAliasApiName(options: { baseUrl: options.baseUrl, model: options.model, treatAsLocal: options.baseUrl ? isLocalProviderUrl(options.baseUrl) : false, + preferBaseUrlRoute: options.baseUrl !== undefined, }) const routeId = runtimeShimContext.routeId if (!routeId || routeId === 'anthropic' || routeId === 'openai') { diff --git a/src/services/compact/autoCompact.test.ts b/src/services/compact/autoCompact.test.ts index 6e8c35e912..7e32ce7ed1 100644 --- a/src/services/compact/autoCompact.test.ts +++ b/src/services/compact/autoCompact.test.ts @@ -268,6 +268,40 @@ describe('getEffectiveContextWindowSize', () => { restoreEnv() } }) + + test('uses explicit route runtime limits instead of ambient provider state', async () => { + const { getEffectiveContextWindowSize } = await importAutoCompact() + + expect(getEffectiveContextWindowSize('k3-256k', { + contextWindow: 262_144, + maxOutputTokens: 32_768, + })).toBe(242_144) + }) + + test('keeps internal context caps above explicit route runtime limits', async () => { + const { getEffectiveContextWindowSize } = await importAutoCompact() + process.env.USER_TYPE = 'ant' + process.env.CLAUDE_CODE_MAX_CONTEXT_TOKENS = '100000' + + expect(getEffectiveContextWindowSize('k3-256k', { + contextWindow: 262_144, + maxOutputTokens: 32_768, + })).toBe(80_000) + }) + + test('keeps session context caps above explicit route runtime limits', async () => { + const { getEffectiveContextWindowSize } = await importAutoCompact() + realContext.setSessionContextWindowOverride('k3-256k', 150_000) + + try { + expect(getEffectiveContextWindowSize('k3-256k', { + contextWindow: 262_144, + maxOutputTokens: 32_768, + })).toBe(130_000) + } finally { + realContext.clearSessionContextWindowOverride('k3-256k') + } + }) }) describe('getAutoCompactThreshold', () => { diff --git a/src/services/compact/autoCompact.ts b/src/services/compact/autoCompact.ts index e7723eba77..b01cbc5826 100644 --- a/src/services/compact/autoCompact.ts +++ b/src/services/compact/autoCompact.ts @@ -35,12 +35,24 @@ import { trySessionMemoryCompaction } from './sessionMemoryCompact.js' const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000 // Returns the context window size minus the max output tokens for the model -export function getEffectiveContextWindowSize(model: string): number { +export function getEffectiveContextWindowSize( + model: string, + runtimeLimits?: { contextWindow?: number; maxOutputTokens?: number }, +): number { const reservedTokensForSummary = Math.min( - getMaxOutputTokensForModel(model), + runtimeLimits?.maxOutputTokens === undefined + ? getMaxOutputTokensForModel(model) + : Math.min( + runtimeLimits.maxOutputTokens, + getMaxOutputTokensForModel(model), + ), MAX_OUTPUT_TOKENS_FOR_SUMMARY, ) - let contextWindow = getContextWindowForModel(model, getSdkBetas()) + let contextWindow = getContextWindowForModel( + model, + getSdkBetas(), + runtimeLimits, + ) const autoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW if (autoCompactWindow) { diff --git a/src/utils/context.ts b/src/utils/context.ts index 2a37b5cd3c..80ee6e83c7 100644 --- a/src/utils/context.ts +++ b/src/utils/context.ts @@ -223,6 +223,7 @@ function warnUnknownIntegrationRuntimeLimits(model: string): void { export function getContextWindowForModel( model: string, betas?: string[], + runtimeLimits?: { contextWindow?: number }, ): number { // Allow override via environment variable (internal-only) // This takes precedence over all other context window resolution, including 1M detection, @@ -254,13 +255,13 @@ export function getContextWindowForModel( // Unknown models get a conservative 128k default. This was previously 8k, // but that caused auto-compact to fire on every turn because the effective // context (8k minus output reservation) became negative (issue #635). - if (shouldUseIntegrationRuntimeLimits()) { - const runtimeLimits = resolveModelRuntimeLimits({ + if (runtimeLimits?.contextWindow !== undefined || shouldUseIntegrationRuntimeLimits()) { + const resolvedRuntimeLimits = runtimeLimits ?? resolveModelRuntimeLimits({ model, activeProfileProvider: getAppliedActiveProfileProvider(), }) - if (runtimeLimits.contextWindow !== undefined) { - return runtimeLimits.contextWindow + if (resolvedRuntimeLimits.contextWindow !== undefined) { + return resolvedRuntimeLimits.contextWindow } warnUnknownIntegrationRuntimeLimits(model) return OPENAI_FALLBACK_CONTEXT_WINDOW From 1a31297bb2556b895086ce53f9e0d908e5139971 Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 12:05:07 -0700 Subject: [PATCH 12/23] fix(kimi): canonicalize K3 reasoning to max --- docs/integrations/overview.md | 6 ++-- src/integrations/gateways/kimi-code.ts | 4 +-- src/integrations/runtimeMetadata.test.ts | 2 +- src/services/api/client.test.ts | 45 ++++++++++++++++++++++-- src/services/api/providerConfig.test.ts | 18 ++-------- src/services/api/providerConfig.ts | 6 ++-- 6 files changed, 55 insertions(+), 26 deletions(-) diff --git a/docs/integrations/overview.md b/docs/integrations/overview.md index 05de2f64dd..72f2f6f293 100644 --- a/docs/integrations/overview.md +++ b/docs/integrations/overview.md @@ -130,9 +130,9 @@ accepted levels, and disable behavior have been verified. See The direct Moonshot API exposes Kimi K3 as `kimi-k3`, with a 1,048,576-token context window, 32,768 maximum output tokens, and `reasoning_effort: "max"` as -its only supported reasoning control. The Kimi Code subscription endpoint uses -the `k3` API model for two catalog selections: `k3` provides the 1M Allegretto+ -window, while `k3-256k` keeps Moderato+ sessions within their 256K limit. +its only supported reasoning control. Kimi Code K3 uses the same `max` control +for two catalog selections: `k3` provides the 1M Allegretto+ window, while +`k3-256k` keeps Moderato+ sessions within their 256K limit. Kimi Code also lists `kimi-for-coding-highspeed` for eligible Allegretto+ subscriptions. It uses six times the speed and three times the quota; retain diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index bc090b35f3..902ce22671 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -35,8 +35,8 @@ export default defineGateway({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, - { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index 9da44ccdb3..26982f3195 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -399,7 +399,7 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad contextWindow: 1_048_576, label: 'Kimi K3 (1M)', }) - expect(k3.catalogEntry?.reasoning?.levels).toEqual(['low', 'high', 'max']) + expect(k3.catalogEntry?.reasoning?.levels).toEqual(['max']) expect(resolveModelRuntimeLimits({ model: 'k3-256k', baseUrl: 'https://api.kimi.com/coding/v1', diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 89daecae9c..8bc936bc20 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2145,7 +2145,7 @@ test('providerOverride K3 maps xhigh effort to max', async () => { } }) -test('providerOverride K3 preserves low reasoning from its model query', async () => { +test('providerOverride K3 canonicalizes model-query reasoning to max', async () => { let requestBody: Record | undefined let requestUrl: string | undefined @@ -2178,13 +2178,54 @@ test('providerOverride K3 preserves low reasoning from its model query', async ( stream: false, }) - expect(requestBody?.reasoning_effort).toBe('low') + expect(requestBody?.reasoning_effort).toBe('max') expect(requestBody?.model).toBe('k3') expect(requestUrl).toStartWith('https://api.kimi.com/coding/v1/') } finally { globalThis.fetch = originalFetch } }) + +test('providerOverride direct Moonshot K3 sends max reasoning', async () => { + let requestBody: Record | undefined + let requestUrl: string | undefined + + globalThis.fetch = (async (input, init) => { + requestUrl = String(input) + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ + id: 'chatcmpl-provider-override-moonshot-k3', + model: 'kimi-k3', + choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }, + }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + + try { + const client = (await getAnthropicClient({ + maxRetries: 0, + providerOverride: { + model: 'kimi-k3?reasoning=max', + baseURL: 'https://api.moonshot.ai/v1', + apiKey: 'moonshot-test-key', + }, + })) as unknown as ShimClient + + await client.beta.messages.create({ + model: 'unused', + system: 'test system', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + expect(requestBody?.reasoning_effort).toBe('max') + expect(requestBody?.model).toBe('kimi-k3') + expect(requestUrl).toStartWith('https://api.moonshot.ai/v1/') + } finally { + globalThis.fetch = originalFetch + } +}) test('providerOverride Atlas Grok Build does not send reasoning effort', async () => { let requestBody: Record | undefined diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index 40d4c7fb83..68eff338f2 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -87,8 +87,8 @@ test('resolveProviderRequest preserves K3 max reasoning from its model query', ( expect(request.reasoning).toEqual({ effort: 'max' }) }) -test('resolveProviderRequest preserves K3 documented reasoning levels from its model query', () => { - for (const effort of ['low', 'high', 'max'] as const) { +test('resolveProviderRequest canonicalizes K3 model-query reasoning to max', () => { + for (const effort of ['low', 'medium', 'high', 'xhigh', 'max'] as const) { const request = resolveProviderRequest({ model: `k3-256k?reasoning=${effort}`, baseUrl: 'https://api.kimi.com/coding/v1', @@ -96,19 +96,7 @@ test('resolveProviderRequest preserves K3 documented reasoning levels from its m }) expect(request.resolvedModel).toBe('k3') - expect(request.reasoning).toEqual({ effort }) - } -}) - -test('resolveProviderRequest canonicalizes Kimi Code K3 reasoning aliases', () => { - for (const [effort, expected] of [['medium', 'high'], ['xhigh', 'max']] as const) { - const request = resolveProviderRequest({ - model: `k3?reasoning=${effort}`, - baseUrl: 'https://api.kimi.com/coding/v1', - processEnv: {}, - }) - - expect(request.reasoning).toEqual({ effort: expected }) + expect(request.reasoning).toEqual({ effort: 'max' }) } }) diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index a6c055903e..029994ae91 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -149,9 +149,9 @@ type ThinkingType = 'enabled' | 'disabled' const OPENAI_CODEX_SHORTCUT_ALIASES = new Set(['codexplan', 'codexspark']) const KIMI_CODE_K3_REASONING_ALIASES: Record = { - low: 'low', - medium: 'high', - high: 'high', + low: 'max', + medium: 'max', + high: 'max', xhigh: 'max', max: 'max', } From 08ad24fb84cfe4fa1b6d19103897fe93c584ebab Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 12:07:45 -0700 Subject: [PATCH 13/23] fix(moonshot): preserve K3 query reasoning --- src/services/api/client.test.ts | 4 ++-- src/services/api/providerConfig.test.ts | 17 +++++------------ src/services/api/providerConfig.ts | 14 +++++++------- 3 files changed, 14 insertions(+), 21 deletions(-) diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 8bc936bc20..d19cb107d4 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2186,7 +2186,7 @@ test('providerOverride K3 canonicalizes model-query reasoning to max', async () } }) -test('providerOverride direct Moonshot K3 sends max reasoning', async () => { +test('providerOverride direct Moonshot K3 canonicalizes model-query reasoning to max', async () => { let requestBody: Record | undefined let requestUrl: string | undefined @@ -2205,7 +2205,7 @@ test('providerOverride direct Moonshot K3 sends max reasoning', async () => { const client = (await getAnthropicClient({ maxRetries: 0, providerOverride: { - model: 'kimi-k3?reasoning=max', + model: 'kimi-k3?reasoning=low', baseURL: 'https://api.moonshot.ai/v1', apiKey: 'moonshot-test-key', }, diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index 68eff338f2..db3aa1a47c 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -100,22 +100,15 @@ test('resolveProviderRequest canonicalizes K3 model-query reasoning to max', () } }) -test('resolveProviderRequest uses the direct Moonshot K3 API name and only permits max reasoning', () => { - const max = resolveProviderRequest({ - model: 'k3?reasoning=max', - baseUrl: 'https://api.moonshot.ai/v1', - processEnv: {}, - }) - expect(max.resolvedModel).toBe('kimi-k3') - expect(max.reasoning).toEqual({ effort: 'max' }) - - for (const effort of ['low', 'medium', 'high', 'xhigh']) { +test('resolveProviderRequest maps direct Moonshot K3 reasoning to max', () => { + for (const effort of ['low', 'medium', 'high', 'xhigh', 'max'] as const) { const request = resolveProviderRequest({ - model: `k3?reasoning=${effort}`, + model: `kimi-k3?reasoning=${effort}`, baseUrl: 'https://api.moonshot.ai/v1', processEnv: {}, }) - expect(request.reasoning).toBeUndefined() + expect(request.resolvedModel).toBe('kimi-k3') + expect(request.reasoning).toEqual({ effort: 'max' }) } }) diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index 029994ae91..6f5f10c539 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -148,7 +148,7 @@ type ReasoningEffort = 'low' | 'medium' | 'high' | 'xhigh' | 'max' type ThinkingType = 'enabled' | 'disabled' const OPENAI_CODEX_SHORTCUT_ALIASES = new Set(['codexplan', 'codexspark']) -const KIMI_CODE_K3_REASONING_ALIASES: Record = { +const KIMI_K3_REASONING_ALIASES: Record = { low: 'max', medium: 'max', high: 'max', @@ -1146,17 +1146,17 @@ export function resolveProviderRequest(options?: { /^gpt-5\.6/.test(descriptor.baseModel) ? undefined : descriptor.reasoning - const isKimiCodeK3 = - explicitBaseUrlRuntimeContext?.routeId === 'kimi-code' && resolvedModel === 'k3' - const normalizedReasoning = - isKimiCodeK3 && requestedReasoning?.effort !== undefined - ? { effort: KIMI_CODE_K3_REASONING_ALIASES[requestedReasoning.effort] } - : requestedReasoning const catalogReasoningLevels = explicitBaseUrlRuntimeContext?.catalogEntry?.modelDescriptorId === 'k3' && explicitBaseUrlRuntimeContext.catalogEntry.reasoning?.wireFormat === 'reasoning_effort' ? explicitBaseUrlRuntimeContext.catalogEntry.reasoning.levels : undefined + const isMaxOnlyK3 = + catalogReasoningLevels?.length === 1 && catalogReasoningLevels.includes('max') + const normalizedReasoning = + isMaxOnlyK3 && requestedReasoning?.effort !== undefined + ? { effort: KIMI_K3_REASONING_ALIASES[requestedReasoning.effort] } + : requestedReasoning const supportsMaxReasoning = catalogReasoningLevels?.includes('max') === true const reasoning = From 765efa7ff5162e5133ffee7ffe8a90c715f06b6d Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 12:15:00 -0700 Subject: [PATCH 14/23] fix(effort): scope K3 xhigh normalization --- src/utils/effort.codex.test.ts | 19 +++++++++++++++++++ src/utils/effort.ts | 1 + 2 files changed, 20 insertions(+) diff --git a/src/utils/effort.codex.test.ts b/src/utils/effort.codex.test.ts index 50da469328..16b91d6db2 100644 --- a/src/utils/effort.codex.test.ts +++ b/src/utils/effort.codex.test.ts @@ -647,6 +647,25 @@ test('Moonshot direct and Kimi Code catalogs expose verified reasoning controls' expect(getAvailableEffortLevels('k3')).toEqual(['max']) expect(resolveAppliedEffort('k3', undefined)).toBe('max') expect(resolveAppliedEffort('k3', 'low')).toBe('max') + expect(resolveAppliedEffort('k3', 'xhigh')).toBe('max') + + const { resolveAppliedEffort: resolveHicapAppliedEffort } = + await importFreshEffortModule({ + provider: 'openai', + supportsCodexReasoningEffort: false, + routeId: 'hicap', + catalogEntries: [{ + id: 'hicap-claude-opus-4.8', + apiName: 'claude-opus-4.8', + capabilities: { supportsReasoning: true }, + reasoning: { + mode: 'levels', + levels: ['low', 'medium', 'high', 'xhigh', 'max'], + wireFormat: 'reasoning_effort', + }, + }], + }) + expect(resolveHicapAppliedEffort('claude-opus-4.8', 'xhigh')).toBe('xhigh') expect(resolveModelReasoningControl('kimi-k2.7-code')).toMatchObject({ supportsReasoning: true, diff --git a/src/utils/effort.ts b/src/utils/effort.ts index 0cb6a92280..f208691df7 100644 --- a/src/utils/effort.ts +++ b/src/utils/effort.ts @@ -969,6 +969,7 @@ export function resolveAppliedEffort( resolved === 'xhigh' && control.source === 'metadata' && control.wireFormat === 'reasoning_effort' && + control.levels.length === 1 && control.levels.includes('max') ) { return 'max' From 14f2425e6706a8daf87090c74d74c55931cba9fc Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 12:17:58 -0700 Subject: [PATCH 15/23] fix(kimi): scope max-only override effort --- src/services/api/client.test.ts | 77 +++++++++++++++++++++++++++++++++ src/services/api/client.ts | 18 +++++--- 2 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index d19cb107d4..d3b2989cbc 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2145,6 +2145,83 @@ test('providerOverride K3 maps xhigh effort to max', async () => { } }) +test('providerOverride K3 sends its max default without an explicit effort', async () => { + let requestBody: Record | undefined + const originalFetch = globalThis.fetch + + try { + globalThis.fetch = (async (_input, init) => { + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ + id: 'chatcmpl-provider-override-k3-default', + model: 'k3', + choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }, + }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + + const client = (await getAnthropicClient({ + maxRetries: 0, + providerOverride: { + model: 'k3', + baseURL: 'https://api.kimi.com/coding/v1', + apiKey: 'kimi-test-key', + }, + })) as unknown as ShimClient + + await client.beta.messages.create({ + model: 'unused', + system: 'test system', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + expect(requestBody?.reasoning_effort).toBe('max') + } finally { + globalThis.fetch = originalFetch + } +}) + +test('providerOverride Hicap keeps multi-level max as xhigh on the wire', async () => { + let requestBody: Record | undefined + const originalFetch = globalThis.fetch + + try { + globalThis.fetch = (async (_input, init) => { + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ + id: 'chatcmpl-provider-override-hicap-max', + model: 'claude-opus-4.8', + choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }, + }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + + const client = (await getAnthropicClient({ + maxRetries: 0, + effortValue: 'max', + providerOverride: { + model: 'claude-opus-4.8', + baseURL: 'https://api.hicap.ai/v1', + apiKey: 'hicap-test-key', + }, + })) as unknown as ShimClient + + await client.beta.messages.create({ + model: 'unused', + system: 'test system', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + expect(requestBody?.reasoning_effort).toBe('xhigh') + } finally { + globalThis.fetch = originalFetch + } +}) + test('providerOverride K3 canonicalizes model-query reasoning to max', async () => { let requestBody: Record | undefined let requestUrl: string | undefined diff --git a/src/services/api/client.ts b/src/services/api/client.ts index e747351fde..b1ab55c39e 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -435,7 +435,16 @@ export async function getAnthropicClient({ ) : modelSupportsWireEffort(effortModel) : false - const appliedEffort = effortModel && effortValue !== undefined + const reasoningControl = effortModel + ? resolveModelReasoningControl(effortModel, effortContext) + : undefined + const maxOnlyReasoningControl = + reasoningControl?.source === 'metadata' && + reasoningControl.wireFormat === 'reasoning_effort' && + reasoningControl.levels.length === 1 && + reasoningControl.levels.includes('max') + const appliedEffort = effortModel && + (effortValue !== undefined || maxOnlyReasoningControl) ? resolveAppliedEffort( effortModel, effortValue, @@ -445,16 +454,13 @@ export async function getAnthropicClient({ const appliedEffortLevel = appliedEffort === undefined ? undefined : convertEffortValueToLevel(appliedEffort) - const reasoningControl = effortModel - ? resolveModelReasoningControl(effortModel, effortContext) - : undefined const shimReasoningEffort: OpenAIShimEffortLevel | undefined = appliedEffortLevel !== undefined && supportsShimReasoningEffort ? (reasoningControl?.source === 'metadata' && reasoningControl.wireFormat === 'reasoning_effort' && appliedEffortLevel === 'max' && - reasoningControl.levels.includes('max') - ? 'max' + maxOnlyReasoningControl + ? 'max' : standardEffortToOpenAI(appliedEffortLevel)) : undefined const containerId = process.env.CLAUDE_CODE_CONTAINER_ID From 0c7462efc636532c161b5bd5479076982d5c49c9 Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 12:22:31 -0700 Subject: [PATCH 16/23] docs(kimi): qualify HighSpeed plan claim --- docs/integrations/overview.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/integrations/overview.md b/docs/integrations/overview.md index 72f2f6f293..3d8fb03eba 100644 --- a/docs/integrations/overview.md +++ b/docs/integrations/overview.md @@ -135,9 +135,11 @@ for two catalog selections: `k3` provides the 1M Allegretto+ window, while `k3-256k` keeps Moderato+ sessions within their 256K limit. Kimi Code also lists `kimi-for-coding-highspeed` for eligible Allegretto+ -subscriptions. It uses six times the speed and three times the quota; retain -the selected catalog ID in client-side routing so its route-specific limits and -capabilities are not lost when the outbound API model is normalized. +subscriptions. [Kimi documents](https://www.kimi.com/code/docs/en/kimi-code/models.html) +it as HighSpeed with approximately 6× output speed and 3× quota usage; plan +availability can vary. Retain the selected catalog ID in client-side routing so +its route-specific limits and capabilities are not lost when the outbound API +model is normalized. ## Descriptor Authoring Pattern From 067b98d6e1ec060f24c4160aa256af2a4c2b0bf6 Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 12:27:08 -0700 Subject: [PATCH 17/23] fix(kimi): preserve documented K3 effort levels --- docs/integrations/overview.md | 6 +++--- src/integrations/gateways/kimi-code.ts | 4 ++-- src/integrations/runtimeMetadata.test.ts | 2 +- src/integrations/vendors/moonshot.ts | 2 +- src/services/api/client.test.ts | 12 ++++++------ src/services/api/client.ts | 11 ++++++----- src/services/api/providerConfig.test.ts | 12 ++++++------ src/services/api/providerConfig.ts | 12 ++++++------ src/utils/effort.codex.test.ts | 6 +++--- src/utils/effort.ts | 4 +++- 10 files changed, 37 insertions(+), 34 deletions(-) diff --git a/docs/integrations/overview.md b/docs/integrations/overview.md index 3d8fb03eba..d348ad0350 100644 --- a/docs/integrations/overview.md +++ b/docs/integrations/overview.md @@ -129,9 +129,9 @@ accepted levels, and disable behavior have been verified. See ### Moonshot and Kimi K3 catalogs The direct Moonshot API exposes Kimi K3 as `kimi-k3`, with a 1,048,576-token -context window, 32,768 maximum output tokens, and `reasoning_effort: "max"` as -its only supported reasoning control. Kimi Code K3 uses the same `max` control -for two catalog selections: `k3` provides the 1M Allegretto+ window, while +context window, 32,768 maximum output tokens, and `reasoning_effort` levels +`low`, `high`, and `max` (defaulting to `max`). Kimi Code K3 uses the same +controls for two catalog selections: `k3` provides the 1M Allegretto+ window, while `k3-256k` keeps Moderato+ sessions within their 256K limit. Kimi Code also lists `kimi-for-coding-highspeed` for eligible Allegretto+ diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index 902ce22671..bc090b35f3 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -35,8 +35,8 @@ export default defineGateway({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, - { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, diff --git a/src/integrations/runtimeMetadata.test.ts b/src/integrations/runtimeMetadata.test.ts index 26982f3195..9da44ccdb3 100644 --- a/src/integrations/runtimeMetadata.test.ts +++ b/src/integrations/runtimeMetadata.test.ts @@ -399,7 +399,7 @@ describe('resolveOpenAIShimRuntimeContext - Moonshot and Kimi Code catalog metad contextWindow: 1_048_576, label: 'Kimi K3 (1M)', }) - expect(k3.catalogEntry?.reasoning?.levels).toEqual(['max']) + expect(k3.catalogEntry?.reasoning?.levels).toEqual(['low', 'high', 'max']) expect(resolveModelRuntimeLimits({ model: 'k3-256k', baseUrl: 'https://api.kimi.com/coding/v1', diff --git a/src/integrations/vendors/moonshot.ts b/src/integrations/vendors/moonshot.ts index 27daca267e..088068a228 100644 --- a/src/integrations/vendors/moonshot.ts +++ b/src/integrations/vendors/moonshot.ts @@ -34,7 +34,7 @@ export default defineVendor({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'kimi-k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, + { id: 'k3', apiName: 'kimi-k3', label: 'Kimi K3', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.6', apiName: 'kimi-k2.6', aliases: ['moonshotai/kimi-k2.6'], label: 'Kimi K2.6', modelDescriptorId: 'kimi-k2.6', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-k2.5', apiName: 'kimi-k2.5', aliases: ['moonshotai/kimi-k2.5'], label: 'Kimi K2.5', modelDescriptorId: 'kimi-k2.5', contextWindow: 262_144, maxOutputTokens: 262_144, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index d3b2989cbc..c9d3ec3f6b 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2145,7 +2145,7 @@ test('providerOverride K3 maps xhigh effort to max', async () => { } }) -test('providerOverride K3 sends its max default without an explicit effort', async () => { +test('providerOverride K3 leaves its default reasoning to the provider', async () => { let requestBody: Record | undefined const originalFetch = globalThis.fetch @@ -2177,7 +2177,7 @@ test('providerOverride K3 sends its max default without an explicit effort', asy stream: false, }) - expect(requestBody?.reasoning_effort).toBe('max') + expect(requestBody?.reasoning_effort).toBeUndefined() } finally { globalThis.fetch = originalFetch } @@ -2222,7 +2222,7 @@ test('providerOverride Hicap keeps multi-level max as xhigh on the wire', async } }) -test('providerOverride K3 canonicalizes model-query reasoning to max', async () => { +test('providerOverride K3 preserves supported model-query reasoning', async () => { let requestBody: Record | undefined let requestUrl: string | undefined @@ -2255,7 +2255,7 @@ test('providerOverride K3 canonicalizes model-query reasoning to max', async () stream: false, }) - expect(requestBody?.reasoning_effort).toBe('max') + expect(requestBody?.reasoning_effort).toBe('low') expect(requestBody?.model).toBe('k3') expect(requestUrl).toStartWith('https://api.kimi.com/coding/v1/') } finally { @@ -2263,7 +2263,7 @@ test('providerOverride K3 canonicalizes model-query reasoning to max', async () } }) -test('providerOverride direct Moonshot K3 canonicalizes model-query reasoning to max', async () => { +test('providerOverride direct Moonshot K3 preserves supported model-query reasoning', async () => { let requestBody: Record | undefined let requestUrl: string | undefined @@ -2296,7 +2296,7 @@ test('providerOverride direct Moonshot K3 canonicalizes model-query reasoning to stream: false, }) - expect(requestBody?.reasoning_effort).toBe('max') + expect(requestBody?.reasoning_effort).toBe('low') expect(requestBody?.model).toBe('kimi-k3') expect(requestUrl).toStartWith('https://api.moonshot.ai/v1/') } finally { diff --git a/src/services/api/client.ts b/src/services/api/client.ts index b1ab55c39e..b7b1bfa121 100644 --- a/src/services/api/client.ts +++ b/src/services/api/client.ts @@ -438,13 +438,14 @@ export async function getAnthropicClient({ const reasoningControl = effortModel ? resolveModelReasoningControl(effortModel, effortContext) : undefined - const maxOnlyReasoningControl = + const k3ReasoningControl = reasoningControl?.source === 'metadata' && reasoningControl.wireFormat === 'reasoning_effort' && - reasoningControl.levels.length === 1 && + reasoningControl.levels.length === 3 && + reasoningControl.levels.includes('low') && + reasoningControl.levels.includes('high') && reasoningControl.levels.includes('max') - const appliedEffort = effortModel && - (effortValue !== undefined || maxOnlyReasoningControl) + const appliedEffort = effortModel && effortValue !== undefined ? resolveAppliedEffort( effortModel, effortValue, @@ -459,7 +460,7 @@ export async function getAnthropicClient({ ? (reasoningControl?.source === 'metadata' && reasoningControl.wireFormat === 'reasoning_effort' && appliedEffortLevel === 'max' && - maxOnlyReasoningControl + k3ReasoningControl ? 'max' : standardEffortToOpenAI(appliedEffortLevel)) : undefined diff --git a/src/services/api/providerConfig.test.ts b/src/services/api/providerConfig.test.ts index db3aa1a47c..369b87e161 100644 --- a/src/services/api/providerConfig.test.ts +++ b/src/services/api/providerConfig.test.ts @@ -87,8 +87,8 @@ test('resolveProviderRequest preserves K3 max reasoning from its model query', ( expect(request.reasoning).toEqual({ effort: 'max' }) }) -test('resolveProviderRequest canonicalizes K3 model-query reasoning to max', () => { - for (const effort of ['low', 'medium', 'high', 'xhigh', 'max'] as const) { +test('resolveProviderRequest canonicalizes K3 model-query reasoning aliases', () => { + for (const [effort, expected] of [['low', 'low'], ['medium', 'high'], ['high', 'high'], ['xhigh', 'max'], ['max', 'max']] as const) { const request = resolveProviderRequest({ model: `k3-256k?reasoning=${effort}`, baseUrl: 'https://api.kimi.com/coding/v1', @@ -96,19 +96,19 @@ test('resolveProviderRequest canonicalizes K3 model-query reasoning to max', () }) expect(request.resolvedModel).toBe('k3') - expect(request.reasoning).toEqual({ effort: 'max' }) + expect(request.reasoning).toEqual({ effort: expected }) } }) -test('resolveProviderRequest maps direct Moonshot K3 reasoning to max', () => { - for (const effort of ['low', 'medium', 'high', 'xhigh', 'max'] as const) { +test('resolveProviderRequest maps direct Moonshot K3 reasoning aliases', () => { + for (const [effort, expected] of [['low', 'low'], ['medium', 'high'], ['high', 'high'], ['xhigh', 'max'], ['max', 'max']] as const) { const request = resolveProviderRequest({ model: `kimi-k3?reasoning=${effort}`, baseUrl: 'https://api.moonshot.ai/v1', processEnv: {}, }) expect(request.resolvedModel).toBe('kimi-k3') - expect(request.reasoning).toEqual({ effort: 'max' }) + expect(request.reasoning).toEqual({ effort: expected }) } }) diff --git a/src/services/api/providerConfig.ts b/src/services/api/providerConfig.ts index 6f5f10c539..dc023a3d4f 100644 --- a/src/services/api/providerConfig.ts +++ b/src/services/api/providerConfig.ts @@ -149,9 +149,9 @@ type ThinkingType = 'enabled' | 'disabled' const OPENAI_CODEX_SHORTCUT_ALIASES = new Set(['codexplan', 'codexspark']) const KIMI_K3_REASONING_ALIASES: Record = { - low: 'max', - medium: 'max', - high: 'max', + low: 'low', + medium: 'high', + high: 'high', xhigh: 'max', max: 'max', } @@ -1151,10 +1151,10 @@ export function resolveProviderRequest(options?: { explicitBaseUrlRuntimeContext.catalogEntry.reasoning?.wireFormat === 'reasoning_effort' ? explicitBaseUrlRuntimeContext.catalogEntry.reasoning.levels : undefined - const isMaxOnlyK3 = - catalogReasoningLevels?.length === 1 && catalogReasoningLevels.includes('max') + const isK3 = catalogReasoningLevels?.includes('max') && + explicitBaseUrlRuntimeContext?.catalogEntry?.modelDescriptorId === 'k3' const normalizedReasoning = - isMaxOnlyK3 && requestedReasoning?.effort !== undefined + isK3 && requestedReasoning?.effort !== undefined ? { effort: KIMI_K3_REASONING_ALIASES[requestedReasoning.effort] } : requestedReasoning const supportsMaxReasoning = diff --git a/src/utils/effort.codex.test.ts b/src/utils/effort.codex.test.ts index 16b91d6db2..e0436fbcb1 100644 --- a/src/utils/effort.codex.test.ts +++ b/src/utils/effort.codex.test.ts @@ -640,13 +640,13 @@ test('Moonshot direct and Kimi Code catalogs expose verified reasoning controls' supportsReasoning: true, controllable: true, source: 'metadata', - levels: ['max'], + levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort', }) - expect(getAvailableEffortLevels('k3')).toEqual(['max']) + expect(getAvailableEffortLevels('k3')).toEqual(['low', 'high', 'max']) expect(resolveAppliedEffort('k3', undefined)).toBe('max') - expect(resolveAppliedEffort('k3', 'low')).toBe('max') + expect(resolveAppliedEffort('k3', 'low')).toBe('low') expect(resolveAppliedEffort('k3', 'xhigh')).toBe('max') const { resolveAppliedEffort: resolveHicapAppliedEffort } = diff --git a/src/utils/effort.ts b/src/utils/effort.ts index f208691df7..0a94a6a3e5 100644 --- a/src/utils/effort.ts +++ b/src/utils/effort.ts @@ -969,7 +969,9 @@ export function resolveAppliedEffort( resolved === 'xhigh' && control.source === 'metadata' && control.wireFormat === 'reasoning_effort' && - control.levels.length === 1 && + control.levels.length === 3 && + control.levels.includes('low') && + control.levels.includes('high') && control.levels.includes('max') ) { return 'max' From 6c2e81b7695936240ef96dd7aa9e5ad69e321e3d Mon Sep 17 00:00:00 2001 From: jatmn Date: Fri, 17 Jul 2026 13:11:25 -0700 Subject: [PATCH 18/23] fix(kimi): preserve disabled thinking for K3 --- src/services/api/client.test.ts | 37 +++++++++++++++++++++++++++++++++ src/services/api/openaiShim.ts | 12 +++++++++++ 2 files changed, 49 insertions(+) diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index c9d3ec3f6b..d6aa3daa9d 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2263,6 +2263,43 @@ test('providerOverride K3 preserves supported model-query reasoning', async () = } }) +test('providerOverride K3 preserves disabled thinking on the wire', async () => { + let requestBody: Record | undefined + globalThis.fetch = (async (_input, init) => { + requestBody = JSON.parse(String(init?.body)) + return new Response(JSON.stringify({ + id: 'chatcmpl-provider-override-k3-thinking-disabled', + model: 'k3', + choices: [{ message: { role: 'assistant', content: 'ok' }, finish_reason: 'stop' }], + usage: { prompt_tokens: 8, completion_tokens: 3, total_tokens: 11 }, + }), { headers: { 'Content-Type': 'application/json' } }) + }) as FetchType + + try { + const client = (await getAnthropicClient({ + maxRetries: 0, + providerOverride: { + model: 'k3?thinking=disabled', + baseURL: 'https://api.kimi.com/coding/v1', + apiKey: 'kimi-test-key', + }, + })) as unknown as ShimClient + await client.beta.messages.create({ + model: 'unused', + system: 'test system', + messages: [{ role: 'user', content: 'hello' }], + max_tokens: 64, + stream: false, + }) + + expect(requestBody?.model).toBe('k3') + expect(requestBody?.thinking).toEqual({ type: 'disabled' }) + expect(requestBody?.reasoning_effort).toBeUndefined() + } finally { + globalThis.fetch = originalFetch + } +}) + test('providerOverride direct Moonshot K3 preserves supported model-query reasoning', async () => { let requestBody: Record | undefined let requestUrl: string | undefined diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 310e476a97..bbe630cb0e 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -4417,6 +4417,18 @@ class OpenAIShimMessages { if (reasoningRequestPlan.wireFormat === 'reasoning_effort' && reasoningRequestPlan.reasoningEffort) { body.reasoning_effort = reasoningRequestPlan.reasoningEffort } + // Kimi K3 uses the standard reasoning_effort field for levels, but its + // documented no-thinking mode is expressed separately. Without this the + // provider silently routes a disabled-thinking K3 request to K2.6. + if ( + ((runtimeShimContext.routeId === 'kimi-code' && request.resolvedModel === 'k3') || + (runtimeShimContext.routeId === 'moonshot' && request.resolvedModel === 'kimi-k3')) && + ((params.thinking as { type?: string } | undefined)?.type === 'disabled' || + request.thinking?.type === 'disabled') + ) { + body.thinking = { type: 'disabled' } + delete body.reasoning_effort + } // Convert max_tokens to max_completion_tokens for OpenAI API compatibility. // Azure OpenAI requires max_completion_tokens and does not accept max_tokens. // Ensure max_tokens is a valid positive number before using it. From 60dc56c44c9a4b26c612c955057c335bc5ea2d20 Mon Sep 17 00:00:00 2001 From: jatmn Date: Sat, 18 Jul 2026 18:36:50 -0700 Subject: [PATCH 19/23] fix(kimi): avoid disabled-thinking fallback --- src/services/api/client.test.ts | 4 ++-- src/services/api/openaiShim.ts | 12 ------------ 2 files changed, 2 insertions(+), 14 deletions(-) diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index d6aa3daa9d..36ef97f7d1 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2263,7 +2263,7 @@ test('providerOverride K3 preserves supported model-query reasoning', async () = } }) -test('providerOverride K3 preserves disabled thinking on the wire', async () => { +test('providerOverride K3 does not silently downgrade when thinking is disabled', async () => { let requestBody: Record | undefined globalThis.fetch = (async (_input, init) => { requestBody = JSON.parse(String(init?.body)) @@ -2293,7 +2293,7 @@ test('providerOverride K3 preserves disabled thinking on the wire', async () => }) expect(requestBody?.model).toBe('k3') - expect(requestBody?.thinking).toEqual({ type: 'disabled' }) + expect(requestBody?.thinking).toBeUndefined() expect(requestBody?.reasoning_effort).toBeUndefined() } finally { globalThis.fetch = originalFetch diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index bbe630cb0e..310e476a97 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -4417,18 +4417,6 @@ class OpenAIShimMessages { if (reasoningRequestPlan.wireFormat === 'reasoning_effort' && reasoningRequestPlan.reasoningEffort) { body.reasoning_effort = reasoningRequestPlan.reasoningEffort } - // Kimi K3 uses the standard reasoning_effort field for levels, but its - // documented no-thinking mode is expressed separately. Without this the - // provider silently routes a disabled-thinking K3 request to K2.6. - if ( - ((runtimeShimContext.routeId === 'kimi-code' && request.resolvedModel === 'k3') || - (runtimeShimContext.routeId === 'moonshot' && request.resolvedModel === 'kimi-k3')) && - ((params.thinking as { type?: string } | undefined)?.type === 'disabled' || - request.thinking?.type === 'disabled') - ) { - body.thinking = { type: 'disabled' } - delete body.reasoning_effort - } // Convert max_tokens to max_completion_tokens for OpenAI API compatibility. // Azure OpenAI requires max_completion_tokens and does not accept max_tokens. // Ensure max_tokens is a valid positive number before using it. From 96a96930912bebb86211f95780e1b695babea3a9 Mon Sep 17 00:00:00 2001 From: jatmn Date: Sat, 18 Jul 2026 18:45:40 -0700 Subject: [PATCH 20/23] fix(compact): honor route runtime output limits --- src/services/compact/autoCompact.test.ts | 3 +++ src/services/compact/autoCompact.ts | 7 +------ 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/services/compact/autoCompact.test.ts b/src/services/compact/autoCompact.test.ts index 7e32ce7ed1..aefa9e9343 100644 --- a/src/services/compact/autoCompact.test.ts +++ b/src/services/compact/autoCompact.test.ts @@ -271,6 +271,9 @@ describe('getEffectiveContextWindowSize', () => { test('uses explicit route runtime limits instead of ambient provider state', async () => { const { getEffectiveContextWindowSize } = await importAutoCompact() + process.env.CLAUDE_CODE_USE_OPENAI = '1' + process.env.OPENAI_BASE_URL = 'https://api.openai.com/v1' + process.env.OPENAI_MODEL = 'gpt-4o' expect(getEffectiveContextWindowSize('k3-256k', { contextWindow: 262_144, diff --git a/src/services/compact/autoCompact.ts b/src/services/compact/autoCompact.ts index b01cbc5826..b185cded64 100644 --- a/src/services/compact/autoCompact.ts +++ b/src/services/compact/autoCompact.ts @@ -40,12 +40,7 @@ export function getEffectiveContextWindowSize( runtimeLimits?: { contextWindow?: number; maxOutputTokens?: number }, ): number { const reservedTokensForSummary = Math.min( - runtimeLimits?.maxOutputTokens === undefined - ? getMaxOutputTokensForModel(model) - : Math.min( - runtimeLimits.maxOutputTokens, - getMaxOutputTokensForModel(model), - ), + runtimeLimits?.maxOutputTokens ?? getMaxOutputTokensForModel(model), MAX_OUTPUT_TOKENS_FOR_SUMMARY, ) let contextWindow = getContextWindowForModel( From b06ab5fe36d0ab97ae1f22fa0e44d3b1cd7f5a4b Mon Sep 17 00:00:00 2001 From: jatmn Date: Mon, 20 Jul 2026 07:03:30 -0700 Subject: [PATCH 21/23] fix(rebase): preserve transport compression routing --- src/services/api/openaiShim.ts | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 310e476a97..6262c87841 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -4326,16 +4326,6 @@ class OpenAIShimMessages { treatAsLocal: isLocalProviderUrl(request.baseUrl), preferBaseUrlRoute: Boolean(this.providerOverride), }) - const compressedMessages = fastPath.skipToolHistoryCompression - ? rawMessages - : compressToolHistory(rawMessages, runtimeModel, { - runtimeLimits: runtimeShimContext.catalogEntry - ? { - contextWindow: runtimeShimContext.catalogEntry.contextWindow, - maxOutputTokens: runtimeShimContext.catalogEntry.maxOutputTokens, - } - : undefined, - }) const shimConfig = runtimeShimContext.openaiShimConfig // When endpointPath is overridden, the body format must match the target // API contract rather than request.transport from providerConfig. @@ -4354,9 +4344,15 @@ class OpenAIShimMessages { rawMessages, () => fastPath.skipToolHistoryCompression ? rawMessages - : compressToolHistory(rawMessages, request.resolvedModel, { + : compressToolHistory(rawMessages, runtimeModel, { textBlockSeparator: effectiveTransport === 'chat_completions' ? '\n\n' : '\n', + runtimeLimits: runtimeShimContext.catalogEntry + ? { + contextWindow: runtimeShimContext.catalogEntry.contextWindow, + maxOutputTokens: runtimeShimContext.catalogEntry.maxOutputTokens, + } + : undefined, }), ) const useNativeOllamaChat = From b63adf043ffabfecdd75f5c4f0cb75635d0036b6 Mon Sep 17 00:00:00 2001 From: jatmn Date: Mon, 20 Jul 2026 07:10:46 -0700 Subject: [PATCH 22/23] fix(kimi): preserve route defaults and runtime controls --- src/integrations/gateways/kimi-code.ts | 10 ++++----- src/services/api/client.test.ts | 4 ++-- src/services/api/openaiShim.ts | 24 +++++++++++++++------- src/utils/effort.ts | 28 ++++++++++++++++++++++---- src/utils/providerProfiles.test.ts | 2 +- 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/integrations/gateways/kimi-code.ts b/src/integrations/gateways/kimi-code.ts index bc090b35f3..f7bff79f83 100644 --- a/src/integrations/gateways/kimi-code.ts +++ b/src/integrations/gateways/kimi-code.ts @@ -5,9 +5,9 @@ export default defineGateway({ label: 'Moonshot AI - Kimi Code', category: 'hosted', defaultBaseUrl: 'https://api.kimi.com/coding/v1', - // Moderato subscriptions are limited to 256K; users with Allegretto+ can - // explicitly select the 1M variant. - defaultModel: 'k3-256k', + // K3 requires a Moderato subscription. Keep the all-members K2.7 model as + // the default; eligible users can explicitly select either K3 variant. + defaultModel: 'kimi-for-coding', supportsModelRouting: true, setup: { requiresAuth: true, @@ -35,8 +35,8 @@ export default defineGateway({ catalog: { source: 'static', models: [ - { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Allegretto+' }, - { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort' }, notes: 'Moderato+' }, + { id: 'k3', apiName: 'k3', label: 'Kimi K3 (1M)', modelDescriptorId: 'k3', contextWindow: 1_048_576, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort', disableFormat: 'thinking_type_disabled' }, notes: 'Allegretto+' }, + { id: 'k3-256k', apiName: 'k3', label: 'Kimi K3 (256K)', modelDescriptorId: 'k3', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'high', 'max'], defaultLevel: 'max', wireFormat: 'reasoning_effort', disableFormat: 'thinking_type_disabled' }, notes: 'Moderato+' }, { id: 'kimi-k2.7-code', apiName: 'kimi-k2.7-code', aliases: ['moonshotai/kimi-k2.7-code'], label: 'Kimi K2.7 Code', modelDescriptorId: 'kimi-k2.7-code', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding', apiName: 'kimi-for-coding', label: 'Kimi for Coding', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' } }, { id: 'kimi-for-coding-highspeed', apiName: 'kimi-for-coding-highspeed', label: 'Kimi K2.7 Code HighSpeed (Allegretto+)', modelDescriptorId: 'kimi-for-coding', contextWindow: 262_144, maxOutputTokens: 32_768, capabilities: { supportsVision: true, supportsFunctionCalling: true, supportsJsonMode: true, supportsReasoning: true }, reasoning: { mode: 'levels', levels: ['low', 'medium', 'high'], defaultLevel: 'medium', wireFormat: 'reasoning_effort' }, notes: 'Requires Allegretto or above; 6x speed and 3x quota usage.' }, diff --git a/src/services/api/client.test.ts b/src/services/api/client.test.ts index 36ef97f7d1..f1297e0e91 100644 --- a/src/services/api/client.test.ts +++ b/src/services/api/client.test.ts @@ -2263,7 +2263,7 @@ test('providerOverride K3 preserves supported model-query reasoning', async () = } }) -test('providerOverride K3 does not silently downgrade when thinking is disabled', async () => { +test('providerOverride K3 forwards disabled thinking to the Kimi API', async () => { let requestBody: Record | undefined globalThis.fetch = (async (_input, init) => { requestBody = JSON.parse(String(init?.body)) @@ -2293,7 +2293,7 @@ test('providerOverride K3 does not silently downgrade when thinking is disabled' }) expect(requestBody?.model).toBe('k3') - expect(requestBody?.thinking).toBeUndefined() + expect(requestBody?.thinking).toEqual({ type: 'disabled' }) expect(requestBody?.reasoning_effort).toBeUndefined() } finally { globalThis.fetch = originalFetch diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 6262c87841..973314e5f9 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -55,7 +55,10 @@ import { refreshCopilotTokenOn401, } from '../../utils/githubModelsCredentials.js' import { resolveXaiAccessToken } from '../../utils/xaiCredentials.js' -import { resolveOpenAIShimRuntimeContext } from '../../integrations/runtimeMetadata.js' +import { + resolveModelRuntimeLimits, + resolveOpenAIShimRuntimeContext, +} from '../../integrations/runtimeMetadata.js' import { getRouteDescriptor, isLongcatBaseUrl, @@ -4326,6 +4329,11 @@ class OpenAIShimMessages { treatAsLocal: isLocalProviderUrl(request.baseUrl), preferBaseUrlRoute: Boolean(this.providerOverride), }) + const runtimeLimits = resolveModelRuntimeLimits({ + model: runtimeModel, + baseUrl: request.baseUrl, + processEnv: requestProcessEnv, + }) const shimConfig = runtimeShimContext.openaiShimConfig // When endpointPath is overridden, the body format must match the target // API contract rather than request.transport from providerConfig. @@ -4347,12 +4355,7 @@ class OpenAIShimMessages { : compressToolHistory(rawMessages, runtimeModel, { textBlockSeparator: effectiveTransport === 'chat_completions' ? '\n\n' : '\n', - runtimeLimits: runtimeShimContext.catalogEntry - ? { - contextWindow: runtimeShimContext.catalogEntry.contextWindow, - maxOutputTokens: runtimeShimContext.catalogEntry.maxOutputTokens, - } - : undefined, + runtimeLimits, }), ) const useNativeOllamaChat = @@ -4413,6 +4416,13 @@ class OpenAIShimMessages { if (reasoningRequestPlan.wireFormat === 'reasoning_effort' && reasoningRequestPlan.reasoningEffort) { body.reasoning_effort = reasoningRequestPlan.reasoningEffort } + if ( + reasoningRequestPlan.wireFormat === 'reasoning_effort' && + reasoningRequestPlan.thinkingType === 'disabled' + ) { + body.thinking = { type: 'disabled' } + delete body.reasoning_effort + } // Convert max_tokens to max_completion_tokens for OpenAI API compatibility. // Azure OpenAI requires max_completion_tokens and does not accept max_tokens. // Ensure max_tokens is a valid positive number before using it. diff --git a/src/utils/effort.ts b/src/utils/effort.ts index 0a94a6a3e5..9c756683c5 100644 --- a/src/utils/effort.ts +++ b/src/utils/effort.ts @@ -56,6 +56,7 @@ export type ReasoningControlResolution = { levels: EffortLevel[] defaultLevel?: EffortValue wireFormat?: ReasoningWireFormat + disableFormat?: ReasoningControlMetadata['disableFormat'] source: 'metadata' | 'capability' | 'compat' | 'legacy' | 'none' } @@ -408,6 +409,7 @@ function resolveMetadataReasoningControl( levels, defaultLevel: normalizeReasoningDefaultLevel(reasoning.defaultLevel, levels), wireFormat, + disableFormat: reasoning.disableFormat, source: 'metadata', } } @@ -589,7 +591,7 @@ export function resolveOpenAIShimReasoningRequestPlan(options: { thinkingRequestFormat?: OpenAIShimThinkingRequestFormat routeId?: string | null useRuntimeFallback?: boolean - reasoningControl?: Pick + reasoningControl?: Pick }): OpenAIShimReasoningRequestPlan { const metadataWireFormat = options.reasoningControl?.source === 'metadata' ? options.reasoningControl.wireFormat @@ -660,9 +662,27 @@ export function resolveOpenAIShimReasoningRequestPlan(options: { } return { - reasoningEffort: options.requestedEffort, - wireFormat: options.requestedEffort ? 'reasoning_effort' : undefined, - source: options.requestedEffort ? 'legacy' : 'none', + thinkingType: + (requestedThinkingType ?? defaultThinkingType) === 'disabled' && + options.reasoningControl?.disableFormat === 'thinking_type_disabled' + ? 'disabled' + : undefined, + reasoningEffort: + (requestedThinkingType ?? defaultThinkingType) === 'disabled' + ? undefined + : options.requestedEffort, + wireFormat: + options.requestedEffort || + ((requestedThinkingType ?? defaultThinkingType) === 'disabled' && + options.reasoningControl?.disableFormat === 'thinking_type_disabled') + ? 'reasoning_effort' + : undefined, + source: + options.requestedEffort || + ((requestedThinkingType ?? defaultThinkingType) === 'disabled' && + options.reasoningControl?.disableFormat === 'thinking_type_disabled') + ? 'metadata' + : 'none', } } // @[MODEL LAUNCH]: Add the new model to the allowlist if it supports 'max' effort. diff --git a/src/utils/providerProfiles.test.ts b/src/utils/providerProfiles.test.ts index 1c69283574..77c69959cd 100644 --- a/src/utils/providerProfiles.test.ts +++ b/src/utils/providerProfiles.test.ts @@ -2473,7 +2473,7 @@ describe('getProviderPresetDefaults', () => { expect(defaults.provider).toBe('kimi-code') expect(defaults.name).toBe('Moonshot AI - Kimi Code') expect(defaults.baseUrl).toBe('https://api.kimi.com/coding/v1') - expect(defaults.model).toBe('k3-256k') + expect(defaults.model).toBe('kimi-for-coding') expect(defaults.requiresApiKey).toBe(true) }) From 67aa6435d7ca552d90062daced53de75b79761e5 Mon Sep 17 00:00:00 2001 From: jatmn Date: Mon, 20 Jul 2026 07:12:47 -0700 Subject: [PATCH 23/23] fix(kimi): retain selected runtime limits for overrides --- src/services/api/openaiShim.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/services/api/openaiShim.ts b/src/services/api/openaiShim.ts index 973314e5f9..b426a34b05 100644 --- a/src/services/api/openaiShim.ts +++ b/src/services/api/openaiShim.ts @@ -4333,6 +4333,7 @@ class OpenAIShimMessages { model: runtimeModel, baseUrl: request.baseUrl, processEnv: requestProcessEnv, + activeProfileProvider: runtimeShimContext.routeId ?? undefined, }) const shimConfig = runtimeShimContext.openaiShimConfig // When endpointPath is overridden, the body format must match the target