From 248e12de9d16de62b1f73a41ef6ae1a7b62beb04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E5=AD=90=E6=98=82?= Date: Tue, 2 Jun 2026 17:31:50 +0800 Subject: [PATCH 1/5] fix(config): avoid Kimi env capabilities for non-Kimi providers --- .changeset/env-model-provider-capabilities.md | 5 +++++ docs/en/configuration/env-vars.md | 2 +- docs/zh/configuration/env-vars.md | 2 +- packages/agent-core/src/config/env-model.ts | 10 +++++++--- packages/agent-core/test/config/env-model.test.ts | 11 +++++++++++ 5 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 .changeset/env-model-provider-capabilities.md diff --git a/.changeset/env-model-provider-capabilities.md b/.changeset/env-model-provider-capabilities.md new file mode 100644 index 00000000..845e4800 --- /dev/null +++ b/.changeset/env-model-provider-capabilities.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Avoid defaulting OpenAI and Anthropic environment models to Kimi-only capabilities. diff --git a/docs/en/configuration/env-vars.md b/docs/en/configuration/env-vars.md index e1476820..323f37f3 100644 --- a/docs/en/configuration/env-vars.md +++ b/docs/en/configuration/env-vars.md @@ -105,7 +105,7 @@ Complete variable list: | `KIMI_MODEL_PROVIDER_TYPE` | No | Provider type: `kimi`, `anthropic`, `openai` | `kimi` | | `KIMI_MODEL_BASE_URL` | No | API base URL | Each type has its own default | | `KIMI_MODEL_MAX_CONTEXT_SIZE` | No | Maximum context length (tokens) | `262144` (256 K) | -| `KIMI_MODEL_CAPABILITIES` | No | Comma-separated capability tags, unioned with auto-detected capabilities | `image_in,thinking` | +| `KIMI_MODEL_CAPABILITIES` | No | Comma-separated capability tags, unioned with auto-detected capabilities | `kimi` → `image_in,thinking`; `openai` / `anthropic` → unset | | `KIMI_MODEL_DISPLAY_NAME` | No | Name shown in `/model` | Falls back to `KIMI_MODEL_NAME` | | `KIMI_MODEL_MAX_OUTPUT_SIZE` | No | Per-request output cap (`anthropic` only) | Model default | | `KIMI_MODEL_REASONING_KEY` | No | Reasoning field name override (`openai` only) | Auto-detected | diff --git a/docs/zh/configuration/env-vars.md b/docs/zh/configuration/env-vars.md index f668d761..0f16b512 100644 --- a/docs/zh/configuration/env-vars.md +++ b/docs/zh/configuration/env-vars.md @@ -105,7 +105,7 @@ kimi | `KIMI_MODEL_PROVIDER_TYPE` | 否 | 供应商类型:`kimi`、`anthropic`、`openai` | `kimi` | | `KIMI_MODEL_BASE_URL` | 否 | API 基础 URL | 各类型有各自默认值 | | `KIMI_MODEL_MAX_CONTEXT_SIZE` | 否 | 最大上下文长度(token 数) | `262144`(256K) | -| `KIMI_MODEL_CAPABILITIES` | 否 | 逗号分隔的能力标签,与自动探测的能力取并集 | `image_in,thinking` | +| `KIMI_MODEL_CAPABILITIES` | 否 | 逗号分隔的能力标签,与自动探测的能力取并集 | `kimi` → `image_in,thinking`;`openai` / `anthropic` → 未设置 | | `KIMI_MODEL_DISPLAY_NAME` | 否 | 在 `/model` 中显示的名称 | 回退到 `KIMI_MODEL_NAME` | | `KIMI_MODEL_MAX_OUTPUT_SIZE` | 否 | 单次输出上限(仅 `anthropic`) | 模型默认值 | | `KIMI_MODEL_REASONING_KEY` | 否 | 推理字段名覆盖(仅 `openai`) | 自动探测 | diff --git a/packages/agent-core/src/config/env-model.ts b/packages/agent-core/src/config/env-model.ts index 1d0956b7..33614323 100644 --- a/packages/agent-core/src/config/env-model.ts +++ b/packages/agent-core/src/config/env-model.ts @@ -24,8 +24,10 @@ const DEFAULT_BASE_URL: Partial> = { /** Default context window (256K) used when KIMI_MODEL_MAX_CONTEXT_SIZE is unset. */ const DEFAULT_MAX_CONTEXT_SIZE = 262144; -/** Default capabilities when KIMI_MODEL_CAPABILITIES is unset (kimi models support both). */ -const DEFAULT_CAPABILITIES = ['image_in', 'thinking']; +/** Default capabilities when KIMI_MODEL_CAPABILITIES is unset. */ +const DEFAULT_CAPABILITIES_BY_TYPE: Partial> = { + kimi: ['image_in', 'thinking'], +}; type Env = Readonly>; @@ -119,7 +121,9 @@ export function applyEnvModelConfig(config: KimiConfig, env: Env = process.env): maxOutputRaw !== undefined ? parsePositiveInt(maxOutputRaw, 'KIMI_MODEL_MAX_OUTPUT_SIZE') : undefined; - const capabilities = parseCapabilities(env['KIMI_MODEL_CAPABILITIES']) ?? DEFAULT_CAPABILITIES; + const capabilities = + parseCapabilities(env['KIMI_MODEL_CAPABILITIES']) ?? + DEFAULT_CAPABILITIES_BY_TYPE[type]?.slice(); const displayName = trimmed(env['KIMI_MODEL_DISPLAY_NAME']); const reasoningKey = trimmed(env['KIMI_MODEL_REASONING_KEY']); const adaptiveThinking = parseBooleanVar( diff --git a/packages/agent-core/test/config/env-model.test.ts b/packages/agent-core/test/config/env-model.test.ts index 3c17f0d4..15d85fcb 100644 --- a/packages/agent-core/test/config/env-model.test.ts +++ b/packages/agent-core/test/config/env-model.test.ts @@ -88,6 +88,17 @@ describe('applyEnvModelConfig', () => { expect(anthropic?.baseUrl).toBeUndefined(); }); + it('does not apply Kimi default capabilities to non-Kimi providers', () => { + expect( + apply({ ...MIN, KIMI_MODEL_PROVIDER_TYPE: 'openai' }) + .models?.[ENV_MODEL_ALIAS_KEY]?.capabilities, + ).toBeUndefined(); + expect( + apply({ ...MIN, KIMI_MODEL_PROVIDER_TYPE: 'anthropic' }) + .models?.[ENV_MODEL_ALIAS_KEY]?.capabilities, + ).toBeUndefined(); + }); + it('rejects unsupported provider types', () => { expectConfigInvalid(() => apply({ ...MIN, KIMI_MODEL_PROVIDER_TYPE: 'google-genai' }), From 73e7a882129ccaedbbdd4a9fdbb9862d043ab867 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E5=AD=90=E6=98=82?= Date: Wed, 3 Jun 2026 01:48:30 +0800 Subject: [PATCH 2/5] fix(kosong): recognize MiMo Anthropic model capabilities --- .changeset/mimo-anthropic-capabilities.md | 5 ++ .../src/providers/capability-registry.ts | 38 ++++++++++++++ .../kosong/test/capability-providers.test.ts | 51 +++++++++++++++++++ 3 files changed, 94 insertions(+) create mode 100644 .changeset/mimo-anthropic-capabilities.md diff --git a/.changeset/mimo-anthropic-capabilities.md b/.changeset/mimo-anthropic-capabilities.md new file mode 100644 index 00000000..4c3426a6 --- /dev/null +++ b/.changeset/mimo-anthropic-capabilities.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Recognize MiMo Anthropic-compatible chat model capabilities. diff --git a/packages/kosong/src/providers/capability-registry.ts b/packages/kosong/src/providers/capability-registry.ts index 17ec9d6b..1c86e2e5 100644 --- a/packages/kosong/src/providers/capability-registry.ts +++ b/packages/kosong/src/providers/capability-registry.ts @@ -45,6 +45,14 @@ const GEMINI_CATALOGUED_PREFIXES = [ 'gemini-2.5-flash', ] as const; +const MIMO_THINKING_TOOL_MODELS = [ + 'mimo-v2.5-pro', + 'mimo-v2-pro', + 'mimo-v2-flash', +] as const; + +const MIMO_THINKING_VISION_TOOL_MODELS = ['mimo-v2.5', 'mimo-v2-omni'] as const; + const OPENAI_REASONING_CAPABILITY: ModelCapability = Object.freeze({ image_in: false, video_in: false, @@ -90,6 +98,24 @@ const ANTHROPIC_THINKING_VISION_TOOL_CAPABILITY: ModelCapability = Object.freeze max_context_tokens: 0, }); +const MIMO_THINKING_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: false, + video_in: false, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 0, +}); + +const MIMO_THINKING_VISION_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: true, + video_in: false, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 0, +}); + const GEMINI_MULTIMODAL_TOOL_CAPABILITY: ModelCapability = Object.freeze({ image_in: true, video_in: true, @@ -135,6 +161,14 @@ const OPENAI_RESPONSES_CAPABILITY_CATALOG: readonly CapabilityCatalogEntry[] = [ ]; const ANTHROPIC_CAPABILITY_CATALOG: readonly CapabilityCatalogEntry[] = [ + { + matches: (name) => hasExactModel(name, MIMO_THINKING_TOOL_MODELS), + capability: MIMO_THINKING_TOOL_CAPABILITY, + }, + { + matches: (name) => hasExactModel(name, MIMO_THINKING_VISION_TOOL_MODELS), + capability: MIMO_THINKING_VISION_TOOL_CAPABILITY, + }, { matches: (name) => hasPrefix(name, CLAUDE_3_PREFIXES), capability: ANTHROPIC_VISION_TOOL_CAPABILITY, @@ -153,6 +187,10 @@ function hasPrefix(modelName: string, prefixes: readonly string[]): boolean { return prefixes.some((prefix) => modelName.startsWith(prefix)); } +function hasExactModel(modelName: string, models: readonly string[]): boolean { + return models.some((model) => modelName === model); +} + function isOpenAIReasoningModel(modelName: string): boolean { return /^o\d/.test(modelName); } diff --git a/packages/kosong/test/capability-providers.test.ts b/packages/kosong/test/capability-providers.test.ts index 55f26d97..26e6b6d7 100644 --- a/packages/kosong/test/capability-providers.test.ts +++ b/packages/kosong/test/capability-providers.test.ts @@ -114,6 +114,57 @@ describe('AnthropicChatProvider.getCapability', () => { expect(cap.tool_use).toBe(true); }); + it('mimo-v2.5-pro → thinking + tool_use, image_in=false', () => { + const cap = make('mimo-v2.5-pro').getCapability(); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + expect(cap.image_in).toBe(false); + }); + + it('mimo-v2-pro → thinking + tool_use, image_in=false', () => { + const cap = make('mimo-v2-pro').getCapability(); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + expect(cap.image_in).toBe(false); + }); + + it('mimo-v2.5 → image_in + thinking + tool_use', () => { + const cap = make('mimo-v2.5').getCapability(); + expect(cap.image_in).toBe(true); + expect(cap.video_in).toBe(false); + expect(cap.audio_in).toBe(false); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + }); + + it('mimo-v2-omni → image_in + thinking + tool_use', () => { + const cap = make('mimo-v2-omni').getCapability(); + expect(cap.image_in).toBe(true); + expect(cap.video_in).toBe(false); + expect(cap.audio_in).toBe(false); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + }); + + it('mimo-v2-flash → thinking + tool_use, image_in=false', () => { + const cap = make('mimo-v2-flash').getCapability(); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + expect(cap.image_in).toBe(false); + }); + + it('MiMo speech models are not inferred as Anthropic chat models', () => { + for (const m of [ + 'mimo-v2.5-asr', + 'mimo-v2.5-tts', + 'mimo-v2.5-tts-voiceclone', + 'mimo-v2.5-tts-voicedesign', + 'mimo-v2-tts', + ]) { + expect(make(m).getCapability()).toEqual(UNKNOWN_CAPABILITY); + } + }); + it('no Anthropic model supports audio_in', () => { // Sanity: Anthropic has no audio-input models today. If one ships later // and this fails, update the table — but make it a conscious decision. From f87e75499497881dd3aa978b1eeb10d867460e39 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E5=AD=90=E6=98=82?= Date: Wed, 3 Jun 2026 01:55:54 +0800 Subject: [PATCH 3/5] fix(kosong): recognize MiniMax Anthropic model capabilities --- .changeset/minimax-anthropic-capabilities.md | 5 +++ .../src/providers/capability-registry.ts | 38 +++++++++++++++++++ .../kosong/test/capability-providers.test.ts | 30 ++++++++++++++- 3 files changed, 72 insertions(+), 1 deletion(-) create mode 100644 .changeset/minimax-anthropic-capabilities.md diff --git a/.changeset/minimax-anthropic-capabilities.md b/.changeset/minimax-anthropic-capabilities.md new file mode 100644 index 00000000..8ea20a14 --- /dev/null +++ b/.changeset/minimax-anthropic-capabilities.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Recognize MiniMax Anthropic-compatible chat model capabilities. diff --git a/packages/kosong/src/providers/capability-registry.ts b/packages/kosong/src/providers/capability-registry.ts index 1c86e2e5..7212eecd 100644 --- a/packages/kosong/src/providers/capability-registry.ts +++ b/packages/kosong/src/providers/capability-registry.ts @@ -53,6 +53,18 @@ const MIMO_THINKING_TOOL_MODELS = [ const MIMO_THINKING_VISION_TOOL_MODELS = ['mimo-v2.5', 'mimo-v2-omni'] as const; +const MINIMAX_THINKING_VISION_TOOL_MODELS = ['minimax-m3'] as const; + +const MINIMAX_TEXT_TOOL_MODELS = [ + 'minimax-m2.7', + 'minimax-m2.7-highspeed', + 'minimax-m2.5', + 'minimax-m2.5-highspeed', + 'minimax-m2.1', + 'minimax-m2.1-highspeed', + 'minimax-m2', +] as const; + const OPENAI_REASONING_CAPABILITY: ModelCapability = Object.freeze({ image_in: false, video_in: false, @@ -116,6 +128,24 @@ const MIMO_THINKING_VISION_TOOL_CAPABILITY: ModelCapability = Object.freeze({ max_context_tokens: 0, }); +const MINIMAX_THINKING_VISION_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: true, + video_in: false, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 0, +}); + +const MINIMAX_TEXT_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: false, + video_in: false, + audio_in: false, + thinking: false, + tool_use: true, + max_context_tokens: 0, +}); + const GEMINI_MULTIMODAL_TOOL_CAPABILITY: ModelCapability = Object.freeze({ image_in: true, video_in: true, @@ -169,6 +199,14 @@ const ANTHROPIC_CAPABILITY_CATALOG: readonly CapabilityCatalogEntry[] = [ matches: (name) => hasExactModel(name, MIMO_THINKING_VISION_TOOL_MODELS), capability: MIMO_THINKING_VISION_TOOL_CAPABILITY, }, + { + matches: (name) => hasExactModel(name, MINIMAX_THINKING_VISION_TOOL_MODELS), + capability: MINIMAX_THINKING_VISION_TOOL_CAPABILITY, + }, + { + matches: (name) => hasExactModel(name, MINIMAX_TEXT_TOOL_MODELS), + capability: MINIMAX_TEXT_TOOL_CAPABILITY, + }, { matches: (name) => hasPrefix(name, CLAUDE_3_PREFIXES), capability: ANTHROPIC_VISION_TOOL_CAPABILITY, diff --git a/packages/kosong/test/capability-providers.test.ts b/packages/kosong/test/capability-providers.test.ts index 26e6b6d7..482d8e4f 100644 --- a/packages/kosong/test/capability-providers.test.ts +++ b/packages/kosong/test/capability-providers.test.ts @@ -165,10 +165,38 @@ describe('AnthropicChatProvider.getCapability', () => { } }); + it('MiniMax-M3 → image_in + thinking + tool_use, video_in=false', () => { + const cap = make('MiniMax-M3').getCapability(); + expect(cap.image_in).toBe(true); + expect(cap.video_in).toBe(false); + expect(cap.audio_in).toBe(false); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + }); + + it('MiniMax M2.x Anthropic models → tool_use only', () => { + for (const m of [ + 'MiniMax-M2.7', + 'MiniMax-M2.7-highspeed', + 'MiniMax-M2.5', + 'MiniMax-M2.5-highspeed', + 'MiniMax-M2.1', + 'MiniMax-M2.1-highspeed', + 'MiniMax-M2', + ]) { + const cap = make(m).getCapability(); + expect(cap.tool_use).toBe(true); + expect(cap.image_in).toBe(false); + expect(cap.video_in).toBe(false); + expect(cap.audio_in).toBe(false); + expect(cap.thinking).toBe(false); + } + }); + it('no Anthropic model supports audio_in', () => { // Sanity: Anthropic has no audio-input models today. If one ships later // and this fails, update the table — but make it a conscious decision. - for (const m of ['claude-3-5-sonnet', 'claude-3-haiku', 'claude-opus-4']) { + for (const m of ['claude-3-5-sonnet', 'claude-3-haiku', 'claude-opus-4', 'MiniMax-M3']) { expect(make(m).getCapability().audio_in).toBe(false); } }); From ff895ae653580df369b48970bdc3cb1feb58107a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E5=AD=90=E6=98=82?= Date: Wed, 3 Jun 2026 02:15:52 +0800 Subject: [PATCH 4/5] fix(kosong): recognize DeepSeek Anthropic model capabilities --- .changeset/deepseek-anthropic-capabilities.md | 5 +++++ .../src/providers/capability-registry.ts | 15 +++++++++++++++ .../kosong/test/capability-providers.test.ts | 19 ++++++++++++++++++- 3 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 .changeset/deepseek-anthropic-capabilities.md diff --git a/.changeset/deepseek-anthropic-capabilities.md b/.changeset/deepseek-anthropic-capabilities.md new file mode 100644 index 00000000..91312efe --- /dev/null +++ b/.changeset/deepseek-anthropic-capabilities.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Recognize DeepSeek Anthropic-compatible chat model capabilities. diff --git a/packages/kosong/src/providers/capability-registry.ts b/packages/kosong/src/providers/capability-registry.ts index 7212eecd..c452d2d1 100644 --- a/packages/kosong/src/providers/capability-registry.ts +++ b/packages/kosong/src/providers/capability-registry.ts @@ -65,6 +65,8 @@ const MINIMAX_TEXT_TOOL_MODELS = [ 'minimax-m2', ] as const; +const DEEPSEEK_THINKING_TOOL_MODELS = ['deepseek-v4-pro', 'deepseek-v4-flash'] as const; + const OPENAI_REASONING_CAPABILITY: ModelCapability = Object.freeze({ image_in: false, video_in: false, @@ -146,6 +148,15 @@ const MINIMAX_TEXT_TOOL_CAPABILITY: ModelCapability = Object.freeze({ max_context_tokens: 0, }); +const DEEPSEEK_THINKING_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: false, + video_in: false, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 0, +}); + const GEMINI_MULTIMODAL_TOOL_CAPABILITY: ModelCapability = Object.freeze({ image_in: true, video_in: true, @@ -207,6 +218,10 @@ const ANTHROPIC_CAPABILITY_CATALOG: readonly CapabilityCatalogEntry[] = [ matches: (name) => hasExactModel(name, MINIMAX_TEXT_TOOL_MODELS), capability: MINIMAX_TEXT_TOOL_CAPABILITY, }, + { + matches: (name) => hasExactModel(name, DEEPSEEK_THINKING_TOOL_MODELS), + capability: DEEPSEEK_THINKING_TOOL_CAPABILITY, + }, { matches: (name) => hasPrefix(name, CLAUDE_3_PREFIXES), capability: ANTHROPIC_VISION_TOOL_CAPABILITY, diff --git a/packages/kosong/test/capability-providers.test.ts b/packages/kosong/test/capability-providers.test.ts index 482d8e4f..aceb35b5 100644 --- a/packages/kosong/test/capability-providers.test.ts +++ b/packages/kosong/test/capability-providers.test.ts @@ -193,10 +193,27 @@ describe('AnthropicChatProvider.getCapability', () => { } }); + it('DeepSeek v4 Anthropic models → thinking + tool_use, no media', () => { + for (const m of ['deepseek-v4-pro', 'deepseek-v4-flash']) { + const cap = make(m).getCapability(); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + expect(cap.image_in).toBe(false); + expect(cap.video_in).toBe(false); + expect(cap.audio_in).toBe(false); + } + }); + it('no Anthropic model supports audio_in', () => { // Sanity: Anthropic has no audio-input models today. If one ships later // and this fails, update the table — but make it a conscious decision. - for (const m of ['claude-3-5-sonnet', 'claude-3-haiku', 'claude-opus-4', 'MiniMax-M3']) { + for (const m of [ + 'claude-3-5-sonnet', + 'claude-3-haiku', + 'claude-opus-4', + 'MiniMax-M3', + 'deepseek-v4-pro', + ]) { expect(make(m).getCapability().audio_in).toBe(false); } }); From 2cc09f13a8de72a317222db9f014fbd5fc5c072e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A6=E5=AD=90=E6=98=82?= Date: Wed, 3 Jun 2026 02:32:38 +0800 Subject: [PATCH 5/5] fix(kosong): recognize Kimi Open Platform model capabilities --- .changeset/kimi-model-capabilities.md | 5 ++ packages/kosong/src/providers/kimi.ts | 46 +++++++++++++++++ .../kosong/test/capability-providers.test.ts | 51 +++++++++++++++++-- 3 files changed, 99 insertions(+), 3 deletions(-) create mode 100644 .changeset/kimi-model-capabilities.md diff --git a/.changeset/kimi-model-capabilities.md b/.changeset/kimi-model-capabilities.md new file mode 100644 index 00000000..045c3424 --- /dev/null +++ b/.changeset/kimi-model-capabilities.md @@ -0,0 +1,5 @@ +--- +"@moonshot-ai/kimi-code": patch +--- + +Recognize current Kimi Open Platform model capabilities. diff --git a/packages/kosong/src/providers/kimi.ts b/packages/kosong/src/providers/kimi.ts index ef53eca7..0913391a 100644 --- a/packages/kosong/src/providers/kimi.ts +++ b/packages/kosong/src/providers/kimi.ts @@ -40,6 +40,43 @@ import { sanitizeToolCallId, type ToolCallIdPolicy, } from './tool-call-id'; + +const KIMI_K2_6_CAPABILITY: ModelCapability = Object.freeze({ + image_in: true, + video_in: true, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 0, +}); + +const KIMI_K2_5_CAPABILITY: ModelCapability = Object.freeze({ + image_in: true, + video_in: false, + audio_in: false, + thinking: true, + tool_use: true, + max_context_tokens: 0, +}); + +const KIMI_VISION_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: true, + video_in: false, + audio_in: false, + thinking: false, + tool_use: true, + max_context_tokens: 0, +}); + +const KIMI_TEXT_TOOL_CAPABILITY: ModelCapability = Object.freeze({ + image_in: false, + video_in: false, + audio_in: false, + thinking: false, + tool_use: true, + max_context_tokens: 0, +}); + export interface KimiOptions { apiKey?: string | undefined; baseUrl?: string | undefined; @@ -499,6 +536,15 @@ export class KimiChatProvider implements ChatProvider { } getCapability(_model?: string): ModelCapability { + const model = (_model ?? this._model).toLowerCase(); + if (model === 'kimi-k2.6') return KIMI_K2_6_CAPABILITY; + if (model === 'kimi-k2.5') return KIMI_K2_5_CAPABILITY; + if (/^moonshot-v1-(?:8k|32k|128k)-vision-preview$/.test(model)) { + return KIMI_VISION_TOOL_CAPABILITY; + } + if (/^moonshot-v1-(?:8k|32k|128k)$/.test(model)) { + return KIMI_TEXT_TOOL_CAPABILITY; + } return UNKNOWN_CAPABILITY; } diff --git a/packages/kosong/test/capability-providers.test.ts b/packages/kosong/test/capability-providers.test.ts index aceb35b5..a688ba26 100644 --- a/packages/kosong/test/capability-providers.test.ts +++ b/packages/kosong/test/capability-providers.test.ts @@ -24,12 +24,57 @@ describe('KimiChatProvider.getCapability', () => { return new KimiChatProvider({ model, apiKey: 'test-key' }); } - it('does not infer capabilities from Kimi model names', () => { + it('kimi-k2.6 → image_in + video_in + thinking + tool_use', () => { + const cap = make('kimi-k2.6').getCapability(); + expect(cap.image_in).toBe(true); + expect(cap.video_in).toBe(true); + expect(cap.audio_in).toBe(false); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + }); + + it('kimi-k2.5 → image_in + thinking + tool_use, video_in=false', () => { + const cap = make('kimi-k2.5').getCapability(); + expect(cap.image_in).toBe(true); + expect(cap.video_in).toBe(false); + expect(cap.audio_in).toBe(false); + expect(cap.thinking).toBe(true); + expect(cap.tool_use).toBe(true); + }); + + it('moonshot-v1 vision models → image_in + tool_use', () => { + for (const model of [ + 'moonshot-v1-8k-vision-preview', + 'moonshot-v1-32k-vision-preview', + 'moonshot-v1-128k-vision-preview', + ]) { + const cap = make(model).getCapability(); + expect(cap.image_in).toBe(true); + expect(cap.video_in).toBe(false); + expect(cap.thinking).toBe(false); + expect(cap.tool_use).toBe(true); + } + }); + + it('moonshot-v1 text models → tool_use only', () => { + for (const model of ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k']) { + const cap = make(model).getCapability(); + expect(cap.image_in).toBe(false); + expect(cap.video_in).toBe(false); + expect(cap.thinking).toBe(false); + expect(cap.tool_use).toBe(true); + } + }); + + it('does not infer capabilities from managed or retired Kimi model names', () => { for (const model of [ 'kimi-for-coding', 'kimi-code', + 'kimi-k2-0905-preview', + 'kimi-k2-0711-preview', 'kimi-k2-turbo-preview', - 'kimi-k2.5', + 'kimi-k2-thinking', + 'kimi-k2-thinking-turbo', 'kimi-thinking-preview', ]) { expect(make(model).getCapability()).toEqual(UNKNOWN_CAPABILITY); @@ -38,7 +83,7 @@ describe('KimiChatProvider.getCapability', () => { it('explicit model arg overrides this.modelName', () => { const provider = make('kimi-k2-turbo-preview'); - expect(provider.getCapability('kimi-for-coding')).toEqual(UNKNOWN_CAPABILITY); + expect(provider.getCapability('kimi-k2.6').video_in).toBe(true); }); it('unknown Kimi model → UNKNOWN_CAPABILITY (no throw)', () => {