From 53cd3daed3cb89115e5408b816813a67dccf2b4c Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 10 May 2026 06:17:52 +0100 Subject: [PATCH] fix(kimi): use stable coding model id --- CHANGELOG.md | 1 + docs/concepts/model-providers.md | 8 ++--- docs/gateway/config-tools.md | 4 +-- docs/providers/moonshot.md | 10 +++---- extensions/kimi-coding/api.ts | 2 ++ extensions/kimi-coding/index.test.ts | 17 +++++++++++ extensions/kimi-coding/index.ts | 6 +++- extensions/kimi-coding/onboard.test.ts | 4 +-- .../kimi-coding/provider-catalog.test.ts | 14 +++++++-- extensions/kimi-coding/provider-catalog.ts | 29 +++++++++++++------ 10 files changed, 70 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 879abd8890c..3d9b7af8a2c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,6 +65,7 @@ Docs: https://docs.openclaw.ai - Agents/Anthropic: report 1M session context for Claude Opus/Sonnet 4 models even when local model config still advertises 200k, matching model discovery and preventing premature status/UI overflow. Fixes #66766. - Models/OpenRouter: hide missing-auth direct provider rows in `/model status` when they are only duplicated by a nested OpenRouter model id such as `openrouter/google/...`, while preserving explicitly configured direct providers. Fixes #62317. - Models: preserve an explicitly selected provider/model such as `opencode-go/deepseek-v4-pro` when another provider owns the same bare model alias. Fixes #79325. +- Kimi Code: use Kimi's stable `kimi-for-coding` API model id in bundled catalog, onboarding, and docs while normalizing legacy `kimi-code` and `k2p5` refs. Fixes #79965. - Volcengine/Kimi: strip provider-unsupported tool schema length and item constraint keywords for direct and coding-plan models so hosted Kimi runs do not reject message tools with `minLength`. Fixes #38817. - DeepSeek: backfill V4 `reasoning_content` replay fields for unowned OpenAI-compatible proxy providers, preventing follow-up request failures outside the bundled DeepSeek and OpenRouter routes. Fixes #79608. diff --git a/docs/concepts/model-providers.md b/docs/concepts/model-providers.md index c01bdbbb58a..c79fc16a063 100644 --- a/docs/concepts/model-providers.md +++ b/docs/concepts/model-providers.md @@ -301,7 +301,7 @@ See [/providers/kilocode](/providers/kilocode) for setup details. | Groq | `groq` | `GROQ_API_KEY` | - | | Hugging Face Inference | `huggingface` | `HUGGINGFACE_HUB_TOKEN` or `HF_TOKEN` | `huggingface/deepseek-ai/DeepSeek-R1` | | Kilo Gateway | `kilocode` | `KILOCODE_API_KEY` | `kilocode/kilo/auto` | -| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-code` | +| Kimi Coding | `kimi` | `KIMI_API_KEY` or `KIMICODE_API_KEY` | `kimi/kimi-for-coding` | | MiniMax | `minimax` / `minimax-portal` | `MINIMAX_API_KEY` / `MINIMAX_OAUTH_TOKEN` | `minimax/MiniMax-M2.7` | | Mistral | `mistral` | `MISTRAL_API_KEY` | `mistral/mistral-large-latest` | | Moonshot | `moonshot` | `MOONSHOT_API_KEY` | `moonshot/kimi-k2.6` | @@ -394,18 +394,18 @@ Kimi Coding uses Moonshot AI's Anthropic-compatible endpoint: - Provider: `kimi` - Auth: `KIMI_API_KEY` -- Example model: `kimi/kimi-code` +- Example model: `kimi/kimi-for-coding` ```json5 { env: { KIMI_API_KEY: "sk-..." }, agents: { - defaults: { model: { primary: "kimi/kimi-code" } }, + defaults: { model: { primary: "kimi/kimi-for-coding" } }, }, } ``` -Legacy `kimi/k2p5` remains accepted as a compatibility model id. +Legacy `kimi/kimi-code` and `kimi/k2p5` remain accepted as compatibility model ids and normalize to Kimi's stable API model id. ### Volcano Engine (Doubao) diff --git a/docs/gateway/config-tools.md b/docs/gateway/config-tools.md index 439ef2acacb..460e2d96f31 100644 --- a/docs/gateway/config-tools.md +++ b/docs/gateway/config-tools.md @@ -538,8 +538,8 @@ Interactive custom-provider onboarding infers image input for common vision mode env: { KIMI_API_KEY: "sk-..." }, agents: { defaults: { - model: { primary: "kimi/kimi-code" }, - models: { "kimi/kimi-code": { alias: "Kimi Code" } }, + model: { primary: "kimi/kimi-for-coding" }, + models: { "kimi/kimi-for-coding": { alias: "Kimi Code" } }, }, }, } diff --git a/docs/providers/moonshot.md b/docs/providers/moonshot.md index d947e35bac5..16286a0f00f 100644 --- a/docs/providers/moonshot.md +++ b/docs/providers/moonshot.md @@ -9,7 +9,7 @@ title: "Moonshot AI" Moonshot provides the Kimi API with OpenAI-compatible endpoints. Configure the provider and set the default model to `moonshot/kimi-k2.6`, or use -Kimi Coding with `kimi/kimi-code`. +Kimi Coding with `kimi/kimi-for-coding`. Moonshot and Kimi Coding are **separate providers**. Keys are not interchangeable, endpoints differ, and model refs differ (`moonshot/...` vs `kimi/...`). @@ -185,7 +185,7 @@ Choose your provider and follow the setup steps. **Best for:** code-focused tasks via the Kimi Coding endpoint. - Kimi Coding uses a different API key and provider prefix (`kimi/...`) than Moonshot (`moonshot/...`). Legacy model ref `kimi/k2p5` remains accepted as a compatibility id. + Kimi Coding uses a different API key and provider prefix (`kimi/...`) than Moonshot (`moonshot/...`). The stable API model ref is `kimi/kimi-for-coding`; legacy refs `kimi/kimi-code` and `kimi/k2p5` remain accepted and normalize to that API model id. @@ -199,7 +199,7 @@ Choose your provider and follow the setup steps. { agents: { defaults: { - model: { primary: "kimi/kimi-code" }, + model: { primary: "kimi/kimi-for-coding" }, }, }, } @@ -219,9 +219,9 @@ Choose your provider and follow the setup steps. env: { KIMI_API_KEY: "sk-..." }, agents: { defaults: { - model: { primary: "kimi/kimi-code" }, + model: { primary: "kimi/kimi-for-coding" }, models: { - "kimi/kimi-code": { alias: "Kimi" }, + "kimi/kimi-for-coding": { alias: "Kimi" }, }, }, }, diff --git a/extensions/kimi-coding/api.ts b/extensions/kimi-coding/api.ts index 42b5368a85f..95617429cd0 100644 --- a/extensions/kimi-coding/api.ts +++ b/extensions/kimi-coding/api.ts @@ -2,5 +2,7 @@ export { buildKimiCodingProvider, KIMI_CODING_BASE_URL, KIMI_CODING_DEFAULT_MODEL_ID, + KIMI_CODING_LEGACY_MODEL_IDS, + normalizeKimiCodingModelId, } from "./provider-catalog.js"; export { KIMI_CODING_MODEL_REF, KIMI_MODEL_REF } from "./onboard.js"; diff --git a/extensions/kimi-coding/index.test.ts b/extensions/kimi-coding/index.test.ts index 70899521bb9..8cd9ffb0bbb 100644 --- a/extensions/kimi-coding/index.test.ts +++ b/extensions/kimi-coding/index.test.ts @@ -3,6 +3,23 @@ import { describe, expect, it } from "vitest"; import plugin from "./index.js"; describe("kimi provider plugin", () => { + it("normalizes legacy Kimi Code ids to the stable API model id", async () => { + const provider = await registerSingleProviderPlugin(plugin); + + expect( + provider.normalizeResolvedModel?.({ + provider: "kimi", + modelId: "kimi-code", + model: { + id: "kimi-code", + name: "Kimi Code", + provider: "kimi", + api: "anthropic-messages", + }, + } as never), + ).toMatchObject({ id: "kimi-for-coding" }); + }); + it("uses binary thinking with thinking off by default", async () => { const provider = await registerSingleProviderPlugin(plugin); diff --git a/extensions/kimi-coding/index.ts b/extensions/kimi-coding/index.ts index 2b956c0e47c..0113344e876 100644 --- a/extensions/kimi-coding/index.ts +++ b/extensions/kimi-coding/index.ts @@ -4,7 +4,7 @@ import { normalizeProviderId } from "openclaw/plugin-sdk/provider-model-shared"; import type { SecretInput } from "openclaw/plugin-sdk/secret-input"; import { isRecord, normalizeOptionalString } from "openclaw/plugin-sdk/text-runtime"; import { applyKimiCodeConfig, KIMI_CODING_MODEL_REF } from "./onboard.js"; -import { buildKimiCodingProvider } from "./provider-catalog.js"; +import { buildKimiCodingProvider, normalizeKimiCodingModelId } from "./provider-catalog.js"; import { KIMI_REPLAY_POLICY } from "./replay-policy.js"; import { wrapKimiProviderStream } from "./stream.js"; @@ -96,6 +96,10 @@ export default definePluginEntry({ }, }, buildReplayPolicy: () => KIMI_REPLAY_POLICY, + normalizeResolvedModel: ({ model }) => { + const normalizedId = normalizeKimiCodingModelId(model.id); + return normalizedId === model.id ? undefined : { ...model, id: normalizedId }; + }, resolveThinkingProfile: () => ({ levels: [ { id: "off", label: "off" }, diff --git a/extensions/kimi-coding/onboard.test.ts b/extensions/kimi-coding/onboard.test.ts index 3da27d2808f..fb2ce6e8363 100644 --- a/extensions/kimi-coding/onboard.test.ts +++ b/extensions/kimi-coding/onboard.test.ts @@ -9,7 +9,7 @@ import { describe("kimi coding onboard", () => { it("keeps the historical Kimi model ref alias pointed at the coding default", () => { - expect(KIMI_MODEL_REF).toBe("kimi/kimi-code"); + expect(KIMI_MODEL_REF).toBe("kimi/kimi-for-coding"); expect(KIMI_CODING_MODEL_REF).toBe(KIMI_MODEL_REF); }); @@ -21,7 +21,7 @@ describe("kimi coding onboard", () => { api: "anthropic-messages", baseUrl: "https://api.kimi.com/coding/", }); - expect(provider?.models?.map((model) => model.id)).toEqual(["kimi-code"]); + expect(provider?.models?.map((model) => model.id)).toEqual(["kimi-for-coding"]); expect(cfg.agents?.defaults?.models?.[KIMI_MODEL_REF]?.alias).toBe("Kimi"); }); diff --git a/extensions/kimi-coding/provider-catalog.test.ts b/extensions/kimi-coding/provider-catalog.test.ts index 398711ac01f..c9d7469cf13 100644 --- a/extensions/kimi-coding/provider-catalog.test.ts +++ b/extensions/kimi-coding/provider-catalog.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { buildKimiCodingProvider } from "./provider-catalog.js"; +import { buildKimiCodingProvider, normalizeKimiCodingModelId } from "./provider-catalog.js"; describe("kimi provider catalog", () => { it("builds the bundled Kimi coding defaults", () => { @@ -8,6 +8,16 @@ describe("kimi provider catalog", () => { expect(provider.api).toBe("anthropic-messages"); expect(provider.baseUrl).toBe("https://api.kimi.com/coding/"); expect(provider.headers).toEqual({ "User-Agent": "claude-code/0.1.0" }); - expect(provider.models.map((model) => model.id)).toEqual(["kimi-code", "k2p5"]); + expect(provider.models.map((model) => model.id)).toEqual([ + "kimi-for-coding", + "kimi-code", + "k2p5", + ]); + }); + + it("normalizes legacy Kimi coding model ids to the stable API model id", () => { + expect(normalizeKimiCodingModelId("kimi-code")).toBe("kimi-for-coding"); + expect(normalizeKimiCodingModelId("k2p5")).toBe("kimi-for-coding"); + expect(normalizeKimiCodingModelId("kimi-for-coding")).toBe("kimi-for-coding"); }); }); diff --git a/extensions/kimi-coding/provider-catalog.ts b/extensions/kimi-coding/provider-catalog.ts index 4553879d7f5..54af0a6650c 100644 --- a/extensions/kimi-coding/provider-catalog.ts +++ b/extensions/kimi-coding/provider-catalog.ts @@ -1,9 +1,12 @@ -import type { ModelProviderConfig } from "openclaw/plugin-sdk/provider-model-shared"; +import type { + ModelDefinitionConfig, + ModelProviderConfig, +} from "openclaw/plugin-sdk/provider-model-shared"; const KIMI_BASE_URL = "https://api.kimi.com/coding/"; const KIMI_CODING_USER_AGENT = "claude-code/0.1.0"; -const KIMI_DEFAULT_MODEL_ID = "kimi-code"; -const KIMI_LEGACY_MODEL_ID = "k2p5"; +const KIMI_DEFAULT_MODEL_ID = "kimi-for-coding"; +const KIMI_LEGACY_MODEL_IDS = ["kimi-code", "k2p5"] as const; const KIMI_CODING_DEFAULT_CONTEXT_WINDOW = 262144; const KIMI_CODING_DEFAULT_MAX_TOKENS = 32768; const KIMI_CODING_DEFAULT_COST = { @@ -12,6 +15,7 @@ const KIMI_CODING_DEFAULT_COST = { cacheRead: 0, cacheWrite: 0, }; +const KIMI_CODING_INPUT = ["text", "image"] satisfies NonNullable; export function buildKimiCodingProvider(): ModelProviderConfig { return { @@ -25,23 +29,30 @@ export function buildKimiCodingProvider(): ModelProviderConfig { id: KIMI_DEFAULT_MODEL_ID, name: "Kimi Code", reasoning: true, - input: ["text", "image"], + input: [...KIMI_CODING_INPUT], cost: KIMI_CODING_DEFAULT_COST, contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW, maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS, }, - { - id: KIMI_LEGACY_MODEL_ID, - name: "Kimi Code (legacy model id)", + ...KIMI_LEGACY_MODEL_IDS.map((id) => ({ + id, + name: `Kimi Code (legacy ${id})`, reasoning: true, - input: ["text", "image"], + input: [...KIMI_CODING_INPUT], cost: KIMI_CODING_DEFAULT_COST, contextWindow: KIMI_CODING_DEFAULT_CONTEXT_WINDOW, maxTokens: KIMI_CODING_DEFAULT_MAX_TOKENS, - }, + })), ], }; } +export function normalizeKimiCodingModelId(modelId: string): string { + return KIMI_LEGACY_MODEL_IDS.includes(modelId as (typeof KIMI_LEGACY_MODEL_IDS)[number]) + ? KIMI_DEFAULT_MODEL_ID + : modelId; +} + export const KIMI_CODING_BASE_URL = KIMI_BASE_URL; export const KIMI_CODING_DEFAULT_MODEL_ID = KIMI_DEFAULT_MODEL_ID; +export const KIMI_CODING_LEGACY_MODEL_IDS = KIMI_LEGACY_MODEL_IDS;