From 0369bdb8f4f3ec3b9c67342c9bdd14f92fe87dd3 Mon Sep 17 00:00:00 2001 From: Armin Ronacher Date: Mon, 15 Jun 2026 17:08:59 +0200 Subject: [PATCH] fix(ai): add Moonshot CN Kimi K2.7 metadata closes #5760 --- packages/ai/CHANGELOG.md | 4 ++ packages/ai/scripts/generate-models.ts | 34 +++++++++++-- packages/ai/src/models.generated.ts | 20 ++++++++ .../ai/src/providers/openai-completions.ts | 6 ++- .../openai-completions-tool-choice.test.ts | 48 +++++++++++++++++++ packages/ai/test/supports-xhigh.test.ts | 9 ++++ packages/coding-agent/CHANGELOG.md | 4 ++ 7 files changed, 120 insertions(+), 5 deletions(-) diff --git a/packages/ai/CHANGELOG.md b/packages/ai/CHANGELOG.md index ff08f5b7a..243b729f7 100644 --- a/packages/ai/CHANGELOG.md +++ b/packages/ai/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed Moonshot AI China model metadata to include Kimi K2.7 Code, and omitted unsupported thinking-off payloads for Kimi K2.7 Code models ([#5760](https://github.com/earendil-works/pi/issues/5760)). + ## [0.79.4] - 2026-06-15 ### Fixed diff --git a/packages/ai/scripts/generate-models.ts b/packages/ai/scripts/generate-models.ts index dfb8f0617..07d0559d1 100644 --- a/packages/ai/scripts/generate-models.ts +++ b/packages/ai/scripts/generate-models.ts @@ -68,6 +68,8 @@ const KIMI_STATIC_HEADERS = { "User-Agent": "KimiCLI/1.5", } as const; +const MOONSHOT_CN_MIRRORED_MODEL_IDS = new Set(["kimi-k2.7-code", "kimi-k2.7-code-highspeed"]); + const TOGETHER_BASE_URL = "https://api.together.ai/v1"; const TOGETHER_BASE_COMPAT: OpenAICompletionsCompat = { supportsStore: false, @@ -347,6 +349,15 @@ function applyThinkingLevelMetadata(model: Model): void { if (model.provider === "openai-codex" && supportsOpenAiXhigh(model.id)) { mergeThinkingLevelMap(model, { minimal: "low" }); } + if ( + (model.provider === "moonshotai" || model.provider === "moonshotai-cn") && + (model.id === "kimi-k2.7-code" || model.id === "kimi-k2.7-code-highspeed") + ) { + // Kimi K2.7 Code is always-thinking. Official docs say + // `thinking: { type: "disabled" }` is rejected, and callers can omit + // the thinking parameter to use the enabled default. + mergeThinkingLevelMap(model, { off: null }); + } if (model.provider === "openrouter" && model.id.startsWith("inception/mercury-2")) { // Mercury 2 in instant mode (reasoning_effort: "none") disables tool calling. // Mark "off" unsupported so the openai-completions provider omits the reasoning param @@ -1262,12 +1273,27 @@ async function loadModelsDevData(): Promise[]> { supportsStrictMode: false, thinkingFormat: "deepseek", }; + const getMoonshotProviderModels = (key: "moonshotai" | "moonshotai-cn"): Record => { + const providerModels = data[key]?.models as Record | undefined; + return providerModels ? { ...providerModels } : {}; + }; + const moonshotModels = { + moonshotai: getMoonshotProviderModels("moonshotai"), + "moonshotai-cn": getMoonshotProviderModels("moonshotai-cn"), + }; + + // models.dev can lag the CN catalog while the global Moonshot catalog already + // has the model. Mirror selected current model IDs into moonshotai-cn until + // upstream CN metadata catches up. + for (const modelId of MOONSHOT_CN_MIRRORED_MODEL_IDS) { + const model = moonshotModels.moonshotai[modelId]; + if (model && !moonshotModels["moonshotai-cn"][modelId]) { + moonshotModels["moonshotai-cn"][modelId] = model; + } + } for (const { key, provider, baseUrl } of moonshotVariants) { - if (!data[key]?.models) continue; - - for (const [modelId, model] of Object.entries(data[key].models)) { - const m = model as ModelsDevModel; + for (const [modelId, m] of Object.entries(moonshotModels[key])) { if (m.tool_call !== true) continue; models.push({ diff --git a/packages/ai/src/models.generated.ts b/packages/ai/src/models.generated.ts index b1d877fa0..cf18edc2e 100644 --- a/packages/ai/src/models.generated.ts +++ b/packages/ai/src/models.generated.ts @@ -6425,6 +6425,7 @@ export const MODELS = { baseUrl: "https://api.moonshot.ai/v1", compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"deepseek"}, reasoning: true, + thinkingLevelMap: {"off":null}, input: ["text", "image"], cost: { input: 0.95, @@ -6563,6 +6564,25 @@ export const MODELS = { contextWindow: 262144, maxTokens: 262144, } satisfies Model<"openai-completions">, + "kimi-k2.7-code": { + id: "kimi-k2.7-code", + name: "Kimi K2.7 Code", + api: "openai-completions", + provider: "moonshotai-cn", + baseUrl: "https://api.moonshot.cn/v1", + compat: {"supportsStore":false,"supportsDeveloperRole":false,"supportsReasoningEffort":false,"maxTokensField":"max_tokens","supportsStrictMode":false,"thinkingFormat":"deepseek"}, + reasoning: true, + thinkingLevelMap: {"off":null}, + input: ["text", "image"], + cost: { + input: 0.95, + output: 4, + cacheRead: 0.19, + cacheWrite: 0, + }, + contextWindow: 262144, + maxTokens: 262144, + } satisfies Model<"openai-completions">, }, "nvidia": { "meta/llama-3.1-70b-instruct": { diff --git a/packages/ai/src/providers/openai-completions.ts b/packages/ai/src/providers/openai-completions.ts index 0f87c8973..140726edc 100644 --- a/packages/ai/src/providers/openai-completions.ts +++ b/packages/ai/src/providers/openai-completions.ts @@ -564,7 +564,11 @@ function buildParams( preserve_thinking: true, }; } else if (compat.thinkingFormat === "deepseek" && model.reasoning) { - (params as any).thinking = { type: options?.reasoningEffort ? "enabled" : "disabled" }; + if (options?.reasoningEffort) { + (params as any).thinking = { type: "enabled" }; + } else if (model.thinkingLevelMap?.off !== null) { + (params as any).thinking = { type: "disabled" }; + } if (options?.reasoningEffort && compat.supportsReasoningEffort) { (params as any).reasoning_effort = model.thinkingLevelMap?.[options.reasoningEffort] ?? options.reasoningEffort; diff --git a/packages/ai/test/openai-completions-tool-choice.test.ts b/packages/ai/test/openai-completions-tool-choice.test.ts index 28c91795c..9cf844969 100644 --- a/packages/ai/test/openai-completions-tool-choice.test.ts +++ b/packages/ai/test/openai-completions-tool-choice.test.ts @@ -1118,6 +1118,54 @@ describe("openai-completions tool_choice", () => { expect(params.reasoning_effort).toBeUndefined(); }); + it("omits disabled thinking for Moonshot Kimi K2.7 Code models", async () => { + const cases = [getModel("moonshotai", "kimi-k2.7-code"), getModel("moonshotai-cn", "kimi-k2.7-code")]; + + for (const model of cases) { + expect(model).toBeDefined(); + let payload: unknown; + + await streamSimple( + model!, + { + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], + }, + { + apiKey: "test", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string }; + expect(params.thinking).toBeUndefined(); + expect(params.reasoning_effort).toBeUndefined(); + } + }); + + it("keeps disabled thinking for Moonshot Kimi K2.6 when thinking is off", async () => { + const model = getModel("moonshotai-cn", "kimi-k2.6")!; + let payload: unknown; + + await streamSimple( + model, + { + messages: [{ role: "user", content: "Hi", timestamp: Date.now() }], + }, + { + apiKey: "test", + onPayload: (params: unknown) => { + payload = params; + }, + }, + ).result(); + + const params = (payload ?? mockState.lastParams) as { thinking?: unknown; reasoning_effort?: string }; + expect(params.thinking).toEqual({ type: "disabled" }); + expect(params.reasoning_effort).toBeUndefined(); + }); + it("sends max_tokens for OpenCode completions models", async () => { const cases = [getModel("opencode-go", "kimi-k2.6")!, getModel("opencode", "grok-build-0.1")!] as const; diff --git a/packages/ai/test/supports-xhigh.test.ts b/packages/ai/test/supports-xhigh.test.ts index ec6dc87b5..9f5363cbd 100644 --- a/packages/ai/test/supports-xhigh.test.ts +++ b/packages/ai/test/supports-xhigh.test.ts @@ -69,6 +69,15 @@ describe("getSupportedThinkingLevels", () => { expect(getSupportedThinkingLevels(model!)).toEqual(["off", "high"]); }); + it("excludes thinking off for Moonshot Kimi K2.7 Code models", () => { + const cases = [getModel("moonshotai", "kimi-k2.7-code"), getModel("moonshotai-cn", "kimi-k2.7-code")]; + + for (const model of cases) { + expect(model).toBeDefined(); + expect(getSupportedThinkingLevels(model!)).toEqual(["minimal", "low", "medium", "high"]); + } + }); + it("includes only high for OpenCode Grok Build", () => { const model = getModel("opencode", "grok-build-0.1"); expect(model).toBeDefined(); diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 621b0fe1a..05e391361 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Fixed inherited Moonshot AI China model metadata to include Kimi K2.7 Code, and omitted unsupported thinking-off payloads for Kimi K2.7 Code models ([#5760](https://github.com/earendil-works/pi/issues/5760)). + ## [0.79.4] - 2026-06-15 ### New Features