From 678517f56c322bdf2e55c8e380d9004ad8a80e65 Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Tue, 25 Aug 2026 19:29:33 +0900 Subject: [PATCH 01/14] release: v2.33.0-preview.20260825 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7b3d03972eb..ba2b3ad83c6 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.32.1-preview.20260825", + "version": "2.33.0-preview.20260825", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 809a06ba00340c905dfac4ab588616e638c2fbfd Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Thu, 27 Aug 2026 21:33:19 +0900 Subject: [PATCH 02/14] release: v2.34.0-preview.20260827 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fd0601a9fcd..f13e70a612f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.34.0", + "version": "2.34.0-preview.20260827", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From dc1feaf7fd8f27c9cc416a1bcbd7ecc247962f2e Mon Sep 17 00:00:00 2001 From: bitkyc08-arch Date: Sat, 29 Aug 2026 00:29:18 +0900 Subject: [PATCH 03/14] release: v2.36.0-preview.20260829 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 57f06ad22b3..dc4e2ad765d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.35.0", + "version": "2.36.0-preview.20260829", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 3224168846f4e7b784beffb576f7ecce42308511 Mon Sep 17 00:00:00 2001 From: JUN Date: Wed, 2 Sep 2026 18:43:29 +0900 Subject: [PATCH 04/14] fix(release): pass the bump job's permissions through the reusable-workflow call (#3262) Both v2.40.0 release dispatches (33615174183 preview, 33615177849 main) died at startup_failure: a workflow_call cannot grant its callee more than the calling job holds, and dev-version-bump.yml's job declares contents+pull- requests write. #3129 wired the call but never dispatched a release, so this is its first live run. The caller job now declares exactly the callee's two permissions; no other job in release.yml gains anything. Co-authored-by: jun (cherry picked from commit 7ce0ba51834740d7b4d5ec4793f6572d84624409) --- .github/workflows/release.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 458bb67e0a5..261aece1d18 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -67,6 +67,14 @@ jobs: bump-dev-version: needs: publish if: ${{ inputs.dry-run != true }} + # A reusable-workflow CALL cannot grant the callee more than the calling job holds, + # and GitHub refuses the whole run at startup when the called workflow's own job + # declares permissions the caller did not pass down ("startup_failure", runs + # 33615174183 / 33615177849 — the first dispatches since #3129 wired this call). + # The callee's job declares exactly these two; nothing else in this file gains them. + permissions: + contents: write + pull-requests: write uses: ./.github/workflows/dev-version-bump.yml with: released-version: v${{ inputs.version }} From 954b99d7bf3395f23ee186d402c43d32824de227 Mon Sep 17 00:00:00 2001 From: lidge-jun Date: Tue, 8 Sep 2026 17:44:26 +0900 Subject: [PATCH 05/14] release: set preview channel version 2.48.0-preview.20260908 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e2d2f3fe8ea..95794a7f46f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.47.0-preview.20260908", + "version": "2.48.0-preview.20260908", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From d24ff57bc4dd53afbbb1d2c972266e6d89ea5c24 Mon Sep 17 00:00:00 2001 From: lidge-jun Date: Tue, 8 Sep 2026 17:44:26 +0900 Subject: [PATCH 06/14] release: set main channel version 2.48.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d94d23cab8..6547da65528 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.47.0", + "version": "2.48.0", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 02044b294b66de3d5799a5ff84e17c3230c8246c Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 14 Sep 2026 19:28:06 +0900 Subject: [PATCH 07/14] chore(release): promote 2.55.0-preview.20260914 to preview Promotes the dev product snapshot 62f02223a0 to the preview train. The 2.55.0 line carries the #4546 cost-guard work: one send budget per logical request with a shared final-recovery reserve, zero-is-zero refusals with a typed error rather than a synthetic 502, compact and the Kiro inner retries admitted against that budget, a finite send ceiling per root workflow with an interactive reserve a fan-out cannot take, and a healthy detour promoted on transient-hold expiry instead of released cold. The previous preview tip 2.54.0-preview.20260914 is already tagged and published and is outranked by v2.54.0, so it could not be re-released; this is a new candidate rather than a re-cut. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9e9f74ae75a..fdcec0ec5d3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.55.0", + "version": "2.55.0-preview.20260914", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 58c15b819a1f9bc5611e0387ce5e62b7de82bcf6 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 14 Sep 2026 19:47:46 +0900 Subject: [PATCH 08/14] chore(release): promote the verified 2.55.0 product tree to main Same product tree as preview 7bdd1b29b5 / 2.55.0-preview.20260914, which published successfully with its registry smoke green. Only package.json version differs. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fdcec0ec5d3..9e9f74ae75a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@bitkyc08/opencodex", - "version": "2.55.0-preview.20260914", + "version": "2.55.0", "description": "Universal provider proxy for OpenAI Codex & Claude Code — use any LLM with Codex CLI/App/SDK and Claude Code", "type": "module", "main": "./bin/package-main.mjs", From 92ec6b3c90f864912ac83c5508f94e07602c1a8e Mon Sep 17 00:00:00 2001 From: panyuanyuan Date: Mon, 21 Sep 2026 10:42:46 +0800 Subject: [PATCH 09/14] fix(kimi): update Kimi coding registry for K2.8 (adjustable thinking, 1M context, current alias default) - kimi-for-coding is the stable subscription alias Moonshot re-points at each coding release; it now routes to K2.8 Preview. Live GET /coding/v1/models lists only kimi-for-coding[-highspeed], k3, k3-256k; the k2.x ids are retired from the subscription endpoint. - K2.8 accepts the same adjustable low/high/max thinking ladder as k3 (verified live: 350K-token request accepted at max effort; upstream rejects beyond 1,048,576 with 'model token limit: 1048576'). - Bump kimi-for-coding context window to the verified 1M ceiling and advertise text+image input. - Default kimi / kimi-code presets to kimi-for-coding instead of the retired kimi-k2.7-code. - Update provider-registry parity test to match the new verified shape. --- src/providers/registry/entries-core.ts | 6 ++++-- src/providers/registry/entries-extended.ts | 5 +++-- src/providers/registry/model-seeds.ts | 21 +++++++++++++------ .../provider-registry-parity.test.ts | 14 ++++++++++--- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/providers/registry/entries-core.ts b/src/providers/registry/entries-core.ts index f16f9f15687..abef9fb938c 100644 --- a/src/providers/registry/entries-core.ts +++ b/src/providers/registry/entries-core.ts @@ -445,7 +445,10 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ jawcodeBundle: "moonshot", note: "Log in with your Kimi account", models: KIMI_CODING_MODELS, - defaultModel: "kimi-k2.7-code", + // 260921: kimi-k2.7-code was retired from the subscription endpoint (live /models lists + // only kimi-for-coding[-highspeed], k3, k3-256k). The kimi-for-coding alias is the + // stable ID and currently routes to K2.8 Preview. + defaultModel: "kimi-for-coding", modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS, modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES, // K3 accepts low/high/max; Codex aliases are normalized by the model-scoped wire map. @@ -1258,4 +1261,3 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ note: "Serverless Inference subscription API. Live discovery exposes only kimi-k2-instruct because Vultr documents it as the sole tool-calling model.", }, ]; - diff --git a/src/providers/registry/entries-extended.ts b/src/providers/registry/entries-extended.ts index a3ffe393c67..7611cdeac9b 100644 --- a/src/providers/registry/entries-extended.ts +++ b/src/providers/registry/entries-extended.ts @@ -1008,7 +1008,9 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [ }, { id: "kimi-code", label: "Kimi (coding)", baseUrl: "https://api.kimi.com/coding/v1", adapter: "openai-chat", authKind: "key", - dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-k2.7-code", + // 260921: kimi-k2.7-code was retired from the coding endpoint; the kimi-for-coding alias + // is the stable ID and currently routes to K2.8 Preview (same as the OAuth preset). + dashboardUrl: "https://platform.moonshot.cn/console/api-keys", defaultModel: "kimi-for-coding", modelSuffixBracketStrip: true, // API-key form of the same Kimi Code Plan transport; keep cache affinity identical to OAuth. promptCacheKey: true, @@ -1362,4 +1364,3 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [ note: "Official CodeBuddy Code CLI (Tencent Cloud), China/internal environment. Uses the documented CODEBUDDY_API_KEY + headless CLI surface; never reads desktop sessions or private console endpoints. Region-isolated from codebuddy (Global); credentials are never exchanged across regions. v1 disables CLI tools (--tools \"\"): text/reasoning only for now. Requires `npm i -g @tencent-ai/codebuddy-code`. AUP/routing authorization flagged for maintainer security review.", }, ]; - diff --git a/src/providers/registry/model-seeds.ts b/src/providers/registry/model-seeds.ts index a1d9661395a..eb9db04199c 100644 --- a/src/providers/registry/model-seeds.ts +++ b/src/providers/registry/model-seeds.ts @@ -642,11 +642,20 @@ export const ALIBABA_TOKEN_PLAN_PRESERVE_REASONING = [ export const KIMI_K3_STANDARD_CONTEXT_WINDOW = 262_144; export const KIMI_K3_1M_CONTEXT_WINDOW = 1_048_576; export const KIMI_CODING_K3_MODELS = ["k3", "k3[1m]"]; +// 260921 Kimi K2.8: `kimi-for-coding` is the stable subscription alias Moonshot re-points +// at each coding release. Live GET /coding/v1/models lists only kimi-for-coding[-highspeed], +// k3, k3-256k — the k2.x ids are retired from the subscription endpoint. Since K2.8 Preview +// the alias serves an adjustable low/high/max thinking ladder (same wire map as k3) and a +// 1M context ceiling. Verified live 260921: 350K-token request accepted; upstream rejects +// with "model token limit: 1048576" beyond that. +// Evidence: https://www.kimi.com/code/docs/en/kimi-code/models.html +export const KIMI_CODING_K28_MODELS = ["kimi-for-coding"]; +export const KIMI_CODING_ADJUSTABLE_THINKING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_CODING_K28_MODELS]; export const KIMI_LEGACY_API_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"]; export const KIMI_API_MODELS = ["kimi-k3", ...KIMI_LEGACY_API_MODELS]; export const KIMI_CODING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_LEGACY_API_MODELS, "kimi-for-coding"]; export const KIMI_THINKING_MODELS = KIMI_CODING_MODELS; -export const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_MODELS.filter(id => !KIMI_CODING_K3_MODELS.includes(id)); +export const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_MODELS.filter(id => !KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id)); export const KIMI_API_NO_REASONING_MODELS = KIMI_API_MODELS.filter(id => id !== "kimi-k3"); export const KIMI_CODING_K3_REASONING_EFFORTS = ["low", "high", "max"]; export const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record = { @@ -658,13 +667,13 @@ export const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record = { max: "max", }; export const KIMI_CODING_REASONING_EFFORTS = Object.fromEntries( - KIMI_CODING_MODELS.map(id => [id, KIMI_CODING_K3_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]), + KIMI_CODING_MODELS.map(id => [id, KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]), ); export const KIMI_CODING_DEFAULT_REASONING_EFFORTS = Object.fromEntries( - KIMI_CODING_K3_MODELS.map(id => [id, "max"]), + KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, "max"]), ); export const KIMI_CODING_REASONING_EFFORT_MAPS = Object.fromEntries( - KIMI_CODING_K3_MODELS.map(id => [id, KIMI_CODING_K3_REASONING_EFFORT_MAP]), + KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, KIMI_CODING_K3_REASONING_EFFORT_MAP]), ); export const KIMI_API_REASONING_EFFORTS = Object.fromEntries( KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? ["max"] : []]), @@ -758,10 +767,10 @@ export const NVIDIA_NIM_NO_VISION_MODELS = [ "poolside/laguna-xs-2.1", "z-ai/glm-5.3", "z-ai/glm-5.2", ]; export const KIMI_CODING_MODEL_CONTEXT_WINDOWS: Record = Object.fromEntries( - KIMI_CODING_MODELS.map(id => [id, id === "k3[1m]" ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]), + KIMI_CODING_MODELS.map(id => [id, (id === "k3[1m]" || KIMI_CODING_K28_MODELS.includes(id)) ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]), ); export const KIMI_CODING_MODEL_INPUT_MODALITIES = Object.fromEntries( - KIMI_CODING_K3_MODELS.map(id => [id, ["text", "image"]]), + KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, ["text", "image"]]), ); export const NEURALWATT_REASONING_HISTORY_MODELS = [ "glm-5.3", "glm-5.3-short", "glm-5.3-flash", diff --git a/tests/providers/provider-registry-parity.test.ts b/tests/providers/provider-registry-parity.test.ts index 094eb8f1cb1..f7a0138d61b 100644 --- a/tests/providers/provider-registry-parity.test.ts +++ b/tests/providers/provider-registry-parity.test.ts @@ -967,12 +967,19 @@ describe("provider registry parity", () => { const entry = PROVIDER_REGISTRY.find(provider => provider.id === providerId); expect(entry?.models).toEqual(codingModels); for (const modelId of codingModels) { - expect(entry?.modelContextWindows?.[modelId]).toBe(modelId === "k3[1m]" ? 1_048_576 : 262_144); + // 260921: kimi-for-coding (K2.8 Preview) shares the verified 1M ceiling with k3[1m]; + // all other ids stay at the 256K standard window. + expect(entry?.modelContextWindows?.[modelId]).toBe(modelId === "k3[1m]" || modelId === "kimi-for-coding" ? 1_048_576 : 262_144); } for (const field of parityLists) { expect(entry?.[field]).toContain("kimi-k2.7-code"); - expect(entry?.[field]).toContain("kimi-for-coding"); + // kimi-for-coding left noReasoningModels when K2.8 added the adjustable ladder; + // every other parity list still carries it. + if (field !== "noReasoningModels") expect(entry?.[field]).toContain("kimi-for-coding"); } + expect(entry?.noReasoningModels).not.toContain("kimi-for-coding"); + expect(entry?.modelReasoningEfforts?.["kimi-for-coding"]).toEqual(["low", "high", "max"]); + expect(entry?.modelDefaultReasoningEfforts?.["kimi-for-coding"]).toBe("max"); expect(entry?.modelSuffixBracketStrip).toBe(true); expect(entry?.promptCacheKey).toBe(true); // Key-pool 429 rotation rebuilds the provider from the persisted config (not the routed @@ -1004,7 +1011,8 @@ describe("provider registry parity", () => { expect(entry?.noPenaltyModels).toContain("k3"); expect(entry?.preserveReasoningContentModels).toContain("k3"); expect(entry?.preserveReasoningContentModels).toContain("k3[1m]"); - expect(entry?.modelReasoningEfforts?.["kimi-for-coding"]).toEqual([]); + // 260921: K2.8 gave kimi-for-coding the same adjustable low/high/max ladder as k3. + expect(entry?.modelReasoningEfforts?.["kimi-for-coding"]).toEqual(["low", "high", "max"]); } const kimi = PROVIDER_REGISTRY.find(provider => provider.id === "kimi")!; From f208fb5eabe2445ef7e1e27fe3d7f810f8c6d34b Mon Sep 17 00:00:00 2001 From: panyuanyuan Date: Mon, 21 Sep 2026 11:27:32 +0800 Subject: [PATCH 10/14] fix(kimi): retire k2.x ids from the coding picker and migrate saved configs to kimi-for-coding Address review on #5403: - MODEL_RENAMES gains kimi/kimi-code entries mapping the retired default kimi-k2.7-code to the live kimi-for-coding alias, so saved configs keep working after Moonshot removed the k2.x ids from the subscription endpoint. - The kimi/kimi-code presets seed only ids the endpoint still serves (live /coding/v1/models: kimi-for-coding, k3). Every preset metadata list is live-id only: seeding a retired id there re-armed the rename migration on every boot (#5066 shape), because the residue guard cannot skip a list that holds the retired id without the live alias. - KIMI_CODING_MODELS is replaced by KIMI_CODING_LIVE_MODELS built from KIMI_CODING_K3_MODELS + KIMI_CODING_K28_MODELS, so a future alias added to the K28 constant flows into the picker and every parallel record. - Parity tests now assert defaultModel is kimi-for-coding for both presets (a registry rollback to the retired default would otherwise pass silently). Verified: bun test on model-rename-migration, provider-registry-parity and codex-catalog (432 pass), full tests/providers sweep (only pre-existing proxy-environment timeouts fail, identical on the clean base), tsc clean. --- src/providers/model-rename-migration.ts | 18 +++++ src/providers/registry/entries-core.ts | 7 +- src/providers/registry/entries-extended.ts | 6 +- src/providers/registry/model-seeds.ts | 22 +++--- .../providers/model-rename-migration.test.ts | 69 +++++++++++++++++++ .../provider-registry-parity.test.ts | 28 ++++---- 6 files changed, 125 insertions(+), 25 deletions(-) diff --git a/src/providers/model-rename-migration.ts b/src/providers/model-rename-migration.ts index 0606fe3dcd9..631890e370c 100644 --- a/src/providers/model-rename-migration.ts +++ b/src/providers/model-rename-migration.ts @@ -65,6 +65,24 @@ export const MODEL_RENAMES: readonly ModelRename[] = [ to: "qwen3.8-max", reason: "Alibaba shipped Qwen3.8-Max as stable and documents the preview endpoint as liable to be taken offline once preview concludes", }, + // Kimi coding renames. Moonshot retired the k2.x ids from the subscription/coding + // endpoint when K2.8 Preview shipped (live /coding/v1/models lists only + // kimi-for-coding[-highspeed], k3, k3-256k); kimi-for-coding is the stable alias the + // endpoint still serves and currently routes to K2.8 Preview. The registry picker no + // longer seeds the retired ids, so a saved defaultModel naming one is a dead selection + // rather than a merely outdated one. + { + provider: "kimi", + from: "kimi-k2.7-code", + to: "kimi-for-coding", + reason: "Moonshot retired the k2.x coding ids from the subscription endpoint when K2.8 Preview shipped; kimi-for-coding is the stable alias the endpoint still serves", + }, + { + provider: "kimi-code", + from: "kimi-k2.7-code", + to: "kimi-for-coding", + reason: "Moonshot retired the k2.x coding ids from the coding endpoint when K2.8 Preview shipped; kimi-for-coding is the stable alias the endpoint still serves", + }, // Antigravity Flash generations. Google takes the previous Flash model off Cloud Code // Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a // dead selection rather than a merely outdated one. Routing already redirects these ids diff --git a/src/providers/registry/entries-core.ts b/src/providers/registry/entries-core.ts index abef9fb938c..3a956081ddc 100644 --- a/src/providers/registry/entries-core.ts +++ b/src/providers/registry/entries-core.ts @@ -55,7 +55,7 @@ import { deepseekThinkingEffortsFor, deepseekReasoningMapFor, KIMI_K3_STANDARD_CONTEXT_WINDOW, - KIMI_CODING_MODELS, + KIMI_CODING_LIVE_MODELS, KIMI_THINKING_MODELS, KIMI_CODING_NO_REASONING_MODELS, KIMI_CODING_K3_REASONING_EFFORTS, @@ -444,7 +444,10 @@ export const PROVIDER_REGISTRY_CORE: readonly ProviderRegistryEntry[] = [ oauthId: "kimi", jawcodeBundle: "moonshot", note: "Log in with your Kimi account", - models: KIMI_CODING_MODELS, + // 260921: the retired k2.x ids stay out of the picker — live /coding/v1/models lists + // only kimi-for-coding[-highspeed], k3, k3-256k. Saved rows still naming kimi-k2.7-code + // are repaired by MODEL_RENAMES in model-rename-migration.ts. + models: KIMI_CODING_LIVE_MODELS, // 260921: kimi-k2.7-code was retired from the subscription endpoint (live /models lists // only kimi-for-coding[-highspeed], k3, k3-256k). The kimi-for-coding alias is the // stable ID and currently routes to K2.8 Preview. diff --git a/src/providers/registry/entries-extended.ts b/src/providers/registry/entries-extended.ts index 234b33c7a4c..3dc0ff10b46 100644 --- a/src/providers/registry/entries-extended.ts +++ b/src/providers/registry/entries-extended.ts @@ -72,10 +72,10 @@ import { VOLCENGINE_PLAN_TEXT_ONLY_MODELS, ALIBABA_INTL_TOKEN_PLAN_INPUT_MODALITIES, KIMI_API_MODELS, - KIMI_CODING_MODELS, KIMI_THINKING_MODELS, KIMI_CODING_NO_REASONING_MODELS, KIMI_API_NO_REASONING_MODELS, + KIMI_CODING_LIVE_MODELS, KIMI_CODING_REASONING_EFFORTS, KIMI_CODING_DEFAULT_REASONING_EFFORTS, KIMI_CODING_REASONING_EFFORT_MAPS, @@ -1021,7 +1021,9 @@ export const PROVIDER_REGISTRY_EXTENDED: readonly ProviderRegistryEntry[] = [ promptCacheKey: true, // Keep Responses tool-result adjacency aligned with the OAuth preset (#4726). requiresAdjacentResponsesToolResults: true, - models: KIMI_CODING_MODELS, + // 260921: same live-id picker as the OAuth preset — the retired k2.x ids are repaired + // in saved configs by MODEL_RENAMES, not offered on fresh installs. + models: KIMI_CODING_LIVE_MODELS, modelContextWindows: KIMI_CODING_MODEL_CONTEXT_WINDOWS, modelInputModalities: KIMI_CODING_MODEL_INPUT_MODALITIES, noReasoningModels: KIMI_CODING_NO_REASONING_MODELS, diff --git a/src/providers/registry/model-seeds.ts b/src/providers/registry/model-seeds.ts index 61f5f77a6c1..1d6884aabfb 100644 --- a/src/providers/registry/model-seeds.ts +++ b/src/providers/registry/model-seeds.ts @@ -650,12 +650,18 @@ export const KIMI_CODING_K3_MODELS = ["k3", "k3[1m]"]; // with "model token limit: 1048576" beyond that. // Evidence: https://www.kimi.com/code/docs/en/kimi-code/models.html export const KIMI_CODING_K28_MODELS = ["kimi-for-coding"]; -export const KIMI_CODING_ADJUSTABLE_THINKING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_CODING_K28_MODELS]; +export const KIMI_CODING_LIVE_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_CODING_K28_MODELS]; +export const KIMI_CODING_ADJUSTABLE_THINKING_MODELS = [...KIMI_CODING_LIVE_MODELS]; export const KIMI_LEGACY_API_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"]; export const KIMI_API_MODELS = ["kimi-k3", ...KIMI_LEGACY_API_MODELS]; -export const KIMI_CODING_MODELS = [...KIMI_CODING_K3_MODELS, ...KIMI_LEGACY_API_MODELS, "kimi-for-coding"]; -export const KIMI_THINKING_MODELS = KIMI_CODING_MODELS; -export const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_MODELS.filter(id => !KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id)); +// Every kimi coding preset record - picker, context windows, locked-parameter lists - +// derives from the live ids only. seeding a retired id in a metadata list would re-arm +// the model-rename migration on every boot (#5066): the list holds the retired id but +// not the live alias, so the residue guard cannot skip it. The retired ids survive only +// in KIMI_LEGACY_API_MODELS (moonshot platform API records); model-rename-migration +// repairs saved rows still naming them. +export const KIMI_THINKING_MODELS = KIMI_CODING_LIVE_MODELS; +export const KIMI_CODING_NO_REASONING_MODELS = KIMI_CODING_LIVE_MODELS.filter(id => !KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id)); export const KIMI_API_NO_REASONING_MODELS = KIMI_API_MODELS.filter(id => id !== "kimi-k3"); export const KIMI_CODING_K3_REASONING_EFFORTS = ["low", "high", "max"]; export const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record = { @@ -667,7 +673,7 @@ export const KIMI_CODING_K3_REASONING_EFFORT_MAP: Record = { max: "max", }; export const KIMI_CODING_REASONING_EFFORTS = Object.fromEntries( - KIMI_CODING_MODELS.map(id => [id, KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]), + KIMI_CODING_LIVE_MODELS.map(id => [id, KIMI_CODING_ADJUSTABLE_THINKING_MODELS.includes(id) ? KIMI_CODING_K3_REASONING_EFFORTS : []]), ); export const KIMI_CODING_DEFAULT_REASONING_EFFORTS = Object.fromEntries( KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, "max"]), @@ -678,8 +684,8 @@ export const KIMI_CODING_REASONING_EFFORT_MAPS = Object.fromEntries( export const KIMI_API_REASONING_EFFORTS = Object.fromEntries( KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? ["max"] : []]), ); -export const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_MODELS; -export const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-for-coding"]; +export const KIMI_LOCKED_PARAMETER_MODELS = KIMI_CODING_LIVE_MODELS; +export const KIMI_AUTO_TOOL_CHOICE_ONLY_MODELS = ["kimi-for-coding"]; export const KIMI_API_MODEL_CONTEXT_WINDOWS: Record = Object.fromEntries( KIMI_API_MODELS.map(id => [id, id === "kimi-k3" ? KIMI_K3_1M_CONTEXT_WINDOW : 262_144]), ); @@ -767,7 +773,7 @@ export const NVIDIA_NIM_NO_VISION_MODELS = [ "poolside/laguna-xs-2.1", "z-ai/glm-5.3", "z-ai/glm-5.2", ]; export const KIMI_CODING_MODEL_CONTEXT_WINDOWS: Record = Object.fromEntries( - KIMI_CODING_MODELS.map(id => [id, (id === "k3[1m]" || KIMI_CODING_K28_MODELS.includes(id)) ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]), + KIMI_CODING_LIVE_MODELS.map(id => [id, (id === "k3[1m]" || KIMI_CODING_K28_MODELS.includes(id)) ? KIMI_K3_1M_CONTEXT_WINDOW : KIMI_K3_STANDARD_CONTEXT_WINDOW]), ); export const KIMI_CODING_MODEL_INPUT_MODALITIES = Object.fromEntries( KIMI_CODING_ADJUSTABLE_THINKING_MODELS.map(id => [id, ["text", "image"]]), diff --git a/tests/providers/model-rename-migration.test.ts b/tests/providers/model-rename-migration.test.ts index be52a3da6ab..69197ee149b 100644 --- a/tests/providers/model-rename-migration.test.ts +++ b/tests/providers/model-rename-migration.test.ts @@ -142,6 +142,75 @@ describe("registry model rename migration (#1610)", () => { expect(entry?.models).not.toContain(rename.from); } }); + + test("repairs a saved kimi row still defaulting to the retired k2.7 id", () => { + // The shape a config saved under the pre-K2.8 registry carries: the picker list, + // the context-window record and the default all name kimi-k2.7-code, and the old + // registry already seeded kimi-for-coding rows next to them. + const stale = { + providers: { + kimi: { + adapter: "openai-chat", + baseUrl: "https://api.kimi.com/coding/v1", + authMode: "oauth", + defaultModel: "kimi-k2.7-code", + models: ["k3", "k3[1m]", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-for-coding"], + modelContextWindows: { "kimi-k2.7-code": 262_144, "kimi-for-coding": 262_144 }, + }, + }, + } as unknown as OcxConfig; + + const { config, changed } = projectModelRenames(stale, MODEL_RENAMES); + const prov = config.providers.kimi!; + expect(changed).toBe(true); + expect(prov.defaultModel).toBe("kimi-for-coding"); + // The picker entry is renamed in place (it keeps its slot, per renameInList) and + // collapses into the row the old registry already seeded under the live alias. + expect(prov.models).toEqual(["k3", "k3[1m]", "kimi-for-coding", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"]); + // The current registry seed no longer publishes the retired id anywhere (the kimi + // preset records are live-id only, so the residue guard has nothing to skip), and the + // record already carries a kimi-for-coding row from the old registry - the rename + // drops the retired key and keeps the newer row untouched. + expect(prov.modelContextWindows?.["kimi-k2.7-code"]).toBeUndefined(); + expect(prov.modelContextWindows?.["kimi-for-coding"]).toBe(262_144); + }); + + test("repairs the kimi-code key preset row the same way", () => { + const stale = { + providers: { + "kimi-code": { + adapter: "openai-chat", + baseUrl: "https://api.kimi.com/coding/v1", + authMode: "key", + apiKey: "sk-test", + defaultModel: "kimi-k2.7-code", + models: ["k3", "k3[1m]", "kimi-k2.7-code", "kimi-k2.6", "kimi-k2.5", "kimi-for-coding"], + }, + }, + } as unknown as OcxConfig; + + const { config, changed } = projectModelRenames(stale, MODEL_RENAMES); + const prov = config.providers["kimi-code"]!; + expect(changed).toBe(true); + expect(prov.defaultModel).toBe("kimi-for-coding"); + expect(prov.models).toEqual(["k3", "k3[1m]", "kimi-for-coding", "kimi-k2.6", "kimi-k2.5"]); + }); + + test("leaves a kimi row repointed at a different gateway alone", () => { + const custom = { + providers: { + kimi: { + adapter: "openai-chat", + baseUrl: "https://my-proxy.internal/v1", + authMode: "oauth", + defaultModel: "kimi-k2.7-code", + models: ["kimi-k2.7-code"], + }, + }, + } as unknown as OcxConfig; + const { changed } = projectModelRenames(custom, MODEL_RENAMES); + expect(changed).toBe(false); + }); }); describe("model rename startup persistence", () => { diff --git a/tests/providers/provider-registry-parity.test.ts b/tests/providers/provider-registry-parity.test.ts index 7266bcf4276..cf4d5b3bd78 100644 --- a/tests/providers/provider-registry-parity.test.ts +++ b/tests/providers/provider-registry-parity.test.ts @@ -945,15 +945,12 @@ describe("provider registry parity", () => { }); test("Kimi coding aliases preserve model context and capability parity", () => { - const codingModels = [ - "k3", - "k3[1m]", - "kimi-k2.7-code", - "kimi-k2.7-code-highspeed", - "kimi-k2.6", - "kimi-k2.5", - "kimi-for-coding", - ]; + // 260921: the picker AND every preset metadata list seed only ids the subscription + // endpoint still serves (live /coding/v1/models: kimi-for-coding[-highspeed], k3, + // k3-256k). Seeding a retired id in a metadata list would re-arm the model-rename + // migration on every boot (#5066); saved rows still naming one are repaired by + // MODEL_RENAMES instead. + const codingModels = ["k3", "k3[1m]", "kimi-for-coding"]; const parityLists = [ "noReasoningModels", "noTemperatureModels", @@ -966,18 +963,23 @@ describe("provider registry parity", () => { for (const providerId of ["kimi", "kimi-code"]) { const entry = PROVIDER_REGISTRY.find(provider => provider.id === providerId); expect(entry?.models).toEqual(codingModels); + // The whole point of the refresh: both presets default to the live alias. A + // registry rollback to the retired default would silently pass without this. + expect(entry?.defaultModel).toBe("kimi-for-coding"); + expect(entry?.models).not.toContain("kimi-k2.7-code"); for (const modelId of codingModels) { // 260921: kimi-for-coding (K2.8 Preview) shares the verified 1M ceiling with k3[1m]; // all other ids stay at the 256K standard window. expect(entry?.modelContextWindows?.[modelId]).toBe(modelId === "k3[1m]" || modelId === "kimi-for-coding" ? 1_048_576 : 262_144); } for (const field of parityLists) { - expect(entry?.[field]).toContain("kimi-k2.7-code"); - // kimi-for-coding left noReasoningModels when K2.8 added the adjustable ladder; - // every other parity list still carries it. + // Every preset list is live-id only: kimi-for-coding must be there, the retired + // k2.x ids must not (a stale k2.7 row would leak the dead id back into the picker). if (field !== "noReasoningModels") expect(entry?.[field]).toContain("kimi-for-coding"); + expect(entry?.[field] ?? []).not.toContain("kimi-k2.7-code"); } - expect(entry?.noReasoningModels).not.toContain("kimi-for-coding"); + // kimi-for-coding left noReasoningModels when K2.8 added the adjustable ladder. + expect(entry?.noReasoningModels ?? []).not.toContain("kimi-for-coding"); expect(entry?.modelReasoningEfforts?.["kimi-for-coding"]).toEqual(["low", "high", "max"]); expect(entry?.modelDefaultReasoningEfforts?.["kimi-for-coding"]).toBe("max"); expect(entry?.modelSuffixBracketStrip).toBe(true); From 84fa9721ddf598dc631afa3f4b340bffe274473b Mon Sep 17 00:00:00 2001 From: panyuanyuan Date: Mon, 21 Sep 2026 11:46:39 +0800 Subject: [PATCH 11/14] docs(kimi): document the K2.8 coding refresh in the providers guide Address the CodeRabbit finding on #5403: the kimi row in the canonical English providers guide (and the zh-cn translation) now documents the kimi-for-coding default, the 1M context window, the adjustable low/high/max ladder (default max), image input, and the automatic kimi-k2.7-code migration on upgrade. Verified with the required validation: cd docs-site && bun install --frozen-lockfile && bun run build (497 pages, exit 0). --- docs-site/src/content/docs/guides/providers.md | 2 +- docs-site/src/content/docs/zh-cn/guides/providers.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index b013cecfd2e..d8b76799850 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -198,7 +198,7 @@ ocx logout | --- | --- | --- | --- | | `xai` | `openai-chat` | `https://cli-chat-proxy.grok.com/v1` | OAuth uses the separate Grok CLI subscription gateway. The API-key override uses `https://api.x.ai/v1` and may inject Priority Processing. Live-first Grok catalog; `grok-4.5` is the fallback default. | | `anthropic` | `anthropic` | `https://api.anthropic.com` | Claude models; live model list fetched from `/v1/models`. | -| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi K2.7/K2.6/K2.5 coding models. | +| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan coding models. Defaults to the stable `kimi-for-coding` alias (currently K2.8 Preview): 1M-token context window, adjustable `low`/`high`/`max` thinking (default `max`), text + image input. The retired `kimi-k2.7-code` default is migrated to the alias on upgrade. | | `nous` | `openai-chat` | `https://inference-api.nousresearch.com/v1` | Nous Research subscription gateway (same backend Hermes Agent uses). Device-grant login against `portal.nousresearch.com`; the access token is the per-request inference JWT. Mixed paid + `:free` model catalog (`tencent/hy3:free`, `stepfun/step-3.7-flash:free`, ...) discovered live from the signed-in account. Refresh tokens are single-use and rotated on every refresh. | | `kiro` | `kiro` | `https://runtime.us-east-1.kiro.dev` | Initial login imports the installed, signed-in `kiro-cli` session (on Unix, install with `curl -fsSL https://cli.kiro.dev/install` | `bash`; on Windows PowerShell, use `irm 'https://cli.kiro.dev/install.ps1'` | `iex`; then run `kiro-cli login`). **Add account** logs `kiro-cli` out, starts a fresh browser login that switches the account used by `kiro-cli`, and stores account-scoped profile metadata. Existing OpenCodex accounts are preserved, and cancellation or failure restores the previous `kiro-cli` session. | | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Google OAuth over the Cloud Code Assist wire. Live discovery uses CCA's authenticated `v1internal:fetchAvailableModels` endpoint and publishes the agent models available to the signed-in account; the maintained catalog remains the fallback. | diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index f4796581124..f8801873d8e 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -101,7 +101,7 @@ ocx logout | --- | --- | --- | --- | | `xai` | `openai-chat` | `https://cli-chat-proxy.grok.com/v1` | OAuth 使用独立的 Grok CLI 订阅网关。API 密钥覆盖模式使用 `https://api.x.ai/v1`,并可能注入 Priority Processing。优先使用实时 Grok 目录;回退默认模型为 `grok-4.5`。 | | `anthropic` | `anthropic` | `https://api.anthropic.com` | Claude 模型;实时模型列表从 `/v1/models` 获取。 | -| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi K2.7/K2.6/K2.5 编程模型。 | +| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan 编程模型。默认使用稳定的 `kimi-for-coding` 别名(当前指向 K2.8 Preview):100 万 token 上下文、可调 `low`/`high`/`max` 思考档(默认 `max`)、支持文本 + 图片输入。已下架的 `kimi-k2.7-code` 默认模型会在升级时自动迁移到该别名。 | | `nous` | `openai-chat` | `https://inference-api.nousresearch.com/v1` | Nous Research 订阅网关(与 Hermes Agent 使用同一后端)。通过设备授权登录 `portal.nousresearch.com`;access 令牌是每个请求的 inference JWT。付费 + `:free` 模型混合目录(`tencent/hy3:free`、`stepfun/step-3.7-flash:free` 等)会从已登录账户实时发现。Refresh 令牌是单次使用,每次刷新都会轮换。 | | `kiro` | `kiro` | `https://runtime.us-east-1.kiro.dev` | 首次登录会导入已安装并已登录的 Kiro CLI 会话(Unix 使用 `curl -fsSL https://cli.kiro.dev/install` | `bash`;Windows PowerShell 使用 `irm 'https://cli.kiro.dev/install.ps1'` | `iex`;然后运行 `kiro-cli login`)。**添加账户**会先退出 `kiro-cli`,再启动新的浏览器登录,从而切换 `kiro-cli` 自身使用的账户,并保存账户范围的配置文件元数据。现有 OpenCodex 账户会保留;如果取消或失败,则恢复之前的 `kiro-cli` 会话。 | | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | 通过 Cloud Code Assist 协议使用 Google OAuth。实时发现调用已认证的 CCA `v1internal:fetchAvailableModels` 端点,并仅发布当前登录账户可用的 agent 模型;维护中的目录仍作为回退。 | From 608d7a22bc5fcd482806cd49d0ee114b3419a7e8 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 21 Sep 2026 13:22:57 +0900 Subject: [PATCH 12/14] fix(kimi): repair saved K2 coding metadata --- .../src/content/docs/guides/providers.md | 2 +- .../content/docs/zh-cn/guides/providers.md | 2 +- src/providers/model-rename-migration.ts | 100 +++++++++++++++--- .../providers/model-rename-migration.test.ts | 50 +++++++-- 4 files changed, 129 insertions(+), 25 deletions(-) diff --git a/docs-site/src/content/docs/guides/providers.md b/docs-site/src/content/docs/guides/providers.md index d8b76799850..e8cba6c928d 100644 --- a/docs-site/src/content/docs/guides/providers.md +++ b/docs-site/src/content/docs/guides/providers.md @@ -198,7 +198,7 @@ ocx logout | --- | --- | --- | --- | | `xai` | `openai-chat` | `https://cli-chat-proxy.grok.com/v1` | OAuth uses the separate Grok CLI subscription gateway. The API-key override uses `https://api.x.ai/v1` and may inject Priority Processing. Live-first Grok catalog; `grok-4.5` is the fallback default. | | `anthropic` | `anthropic` | `https://api.anthropic.com` | Claude models; live model list fetched from `/v1/models`. | -| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan coding models. Defaults to the stable `kimi-for-coding` alias (currently K2.8 Preview): 1M-token context window, adjustable `low`/`high`/`max` thinking (default `max`), text + image input. The retired `kimi-k2.7-code` default is migrated to the alias on upgrade. | +| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan coding models. Defaults to the stable `kimi-for-coding` alias (currently K2.8 Preview): 1M-token context window, adjustable `low`/`high`/`max` thinking (default `max`), text + image input. Retired `kimi-k2.x` selections are migrated to the alias on upgrade. | | `nous` | `openai-chat` | `https://inference-api.nousresearch.com/v1` | Nous Research subscription gateway (same backend Hermes Agent uses). Device-grant login against `portal.nousresearch.com`; the access token is the per-request inference JWT. Mixed paid + `:free` model catalog (`tencent/hy3:free`, `stepfun/step-3.7-flash:free`, ...) discovered live from the signed-in account. Refresh tokens are single-use and rotated on every refresh. | | `kiro` | `kiro` | `https://runtime.us-east-1.kiro.dev` | Initial login imports the installed, signed-in `kiro-cli` session (on Unix, install with `curl -fsSL https://cli.kiro.dev/install` | `bash`; on Windows PowerShell, use `irm 'https://cli.kiro.dev/install.ps1'` | `iex`; then run `kiro-cli login`). **Add account** logs `kiro-cli` out, starts a fresh browser login that switches the account used by `kiro-cli`, and stores account-scoped profile metadata. Existing OpenCodex accounts are preserved, and cancellation or failure restores the previous `kiro-cli` session. | | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | Google OAuth over the Cloud Code Assist wire. Live discovery uses CCA's authenticated `v1internal:fetchAvailableModels` endpoint and publishes the agent models available to the signed-in account; the maintained catalog remains the fallback. | diff --git a/docs-site/src/content/docs/zh-cn/guides/providers.md b/docs-site/src/content/docs/zh-cn/guides/providers.md index f8801873d8e..d799f306705 100644 --- a/docs-site/src/content/docs/zh-cn/guides/providers.md +++ b/docs-site/src/content/docs/zh-cn/guides/providers.md @@ -101,7 +101,7 @@ ocx logout | --- | --- | --- | --- | | `xai` | `openai-chat` | `https://cli-chat-proxy.grok.com/v1` | OAuth 使用独立的 Grok CLI 订阅网关。API 密钥覆盖模式使用 `https://api.x.ai/v1`,并可能注入 Priority Processing。优先使用实时 Grok 目录;回退默认模型为 `grok-4.5`。 | | `anthropic` | `anthropic` | `https://api.anthropic.com` | Claude 模型;实时模型列表从 `/v1/models` 获取。 | -| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan 编程模型。默认使用稳定的 `kimi-for-coding` 别名(当前指向 K2.8 Preview):100 万 token 上下文、可调 `low`/`high`/`max` 思考档(默认 `max`)、支持文本 + 图片输入。已下架的 `kimi-k2.7-code` 默认模型会在升级时自动迁移到该别名。 | +| `kimi` | `openai-chat` | `https://api.kimi.com/coding/v1` | Kimi Code Plan 编程模型。默认使用稳定的 `kimi-for-coding` 别名(当前指向 K2.8 Preview):100 万 token 上下文、可调 `low`/`high`/`max` 思考档(默认 `max`)、支持文本 + 图片输入。已下架的 `kimi-k2.x` 选择会在升级时自动迁移到该别名。 | | `nous` | `openai-chat` | `https://inference-api.nousresearch.com/v1` | Nous Research 订阅网关(与 Hermes Agent 使用同一后端)。通过设备授权登录 `portal.nousresearch.com`;access 令牌是每个请求的 inference JWT。付费 + `:free` 模型混合目录(`tencent/hy3:free`、`stepfun/step-3.7-flash:free` 等)会从已登录账户实时发现。Refresh 令牌是单次使用,每次刷新都会轮换。 | | `kiro` | `kiro` | `https://runtime.us-east-1.kiro.dev` | 首次登录会导入已安装并已登录的 Kiro CLI 会话(Unix 使用 `curl -fsSL https://cli.kiro.dev/install` | `bash`;Windows PowerShell 使用 `irm 'https://cli.kiro.dev/install.ps1'` | `iex`;然后运行 `kiro-cli login`)。**添加账户**会先退出 `kiro-cli`,再启动新的浏览器登录,从而切换 `kiro-cli` 自身使用的账户,并保存账户范围的配置文件元数据。现有 OpenCodex 账户会保留;如果取消或失败,则恢复之前的 `kiro-cli` 会话。 | | `google-antigravity` | `google` | `https://daily-cloudcode-pa.googleapis.com` | 通过 Cloud Code Assist 协议使用 Google OAuth。实时发现调用已认证的 CCA `v1internal:fetchAvailableModels` 端点,并仅发布当前登录账户可用的 agent 模型;维护中的目录仍作为回退。 | diff --git a/src/providers/model-rename-migration.ts b/src/providers/model-rename-migration.ts index 631890e370c..5d9a8ff6a18 100644 --- a/src/providers/model-rename-migration.ts +++ b/src/providers/model-rename-migration.ts @@ -45,6 +45,52 @@ export interface ModelRename { * but lose the reasoning picker entirely. */ dropReasoningEffortMap?: boolean; + /** + * Remove both the retired id and its replacement from `noReasoningModels`. + * + * Use this only when the rename also marks a capability change: carrying the old + * no-reasoning classification onto a newly adjustable alias would keep the picker + * disabled after the model id itself was repaired. + */ + dropNoReasoningModels?: boolean; + /** Refresh exact registry defaults already saved under the replacement id. */ + targetSeedRefresh?: { + contextWindow?: { from: number; to: number }; + reasoning?: { + fromEfforts: readonly string[]; + toEfforts: readonly string[]; + defaultEffort: string; + effortMap: Readonly>; + }; + }; +} + +const KIMI_K28_ALIAS = "kimi-for-coding"; +const KIMI_RETIRED_CODING_IDS = [ + "kimi-k2.7-code", + "kimi-k2.7-code-highspeed", + "kimi-k2.6", + "kimi-k2.5", +] as const; +const KIMI_K28_TARGET_SEED_REFRESH: NonNullable = { + contextWindow: { from: 262_144, to: 1_048_576 }, + reasoning: { + fromEfforts: [], + toEfforts: ["low", "high", "max"], + defaultEffort: "max", + effortMap: { none: "none", low: "low", medium: "high", high: "high", xhigh: "max", max: "max" }, + }, +}; + +function kimiCodingRenames(provider: "kimi" | "kimi-code", endpoint: string): ModelRename[] { + return KIMI_RETIRED_CODING_IDS.map(from => ({ + provider, + from, + to: KIMI_K28_ALIAS, + reason: `Moonshot retired the k2.x coding ids from the ${endpoint}; kimi-for-coding is the stable alias the endpoint still serves`, + dropNoReasoningModels: true, + targetSeedRefresh: KIMI_K28_TARGET_SEED_REFRESH, + })); } /** @@ -71,18 +117,8 @@ export const MODEL_RENAMES: readonly ModelRename[] = [ // endpoint still serves and currently routes to K2.8 Preview. The registry picker no // longer seeds the retired ids, so a saved defaultModel naming one is a dead selection // rather than a merely outdated one. - { - provider: "kimi", - from: "kimi-k2.7-code", - to: "kimi-for-coding", - reason: "Moonshot retired the k2.x coding ids from the subscription endpoint when K2.8 Preview shipped; kimi-for-coding is the stable alias the endpoint still serves", - }, - { - provider: "kimi-code", - from: "kimi-k2.7-code", - to: "kimi-for-coding", - reason: "Moonshot retired the k2.x coding ids from the coding endpoint when K2.8 Preview shipped; kimi-for-coding is the stable alias the endpoint still serves", - }, + ...kimiCodingRenames("kimi", "subscription endpoint after K2.8 Preview shipped"), + ...kimiCodingRenames("kimi-code", "coding endpoint after K2.8 Preview shipped"), // Antigravity Flash generations. Google takes the previous Flash model off Cloud Code // Assist almost immediately when the next ships, so a saved 3.6 (or older 3.5) id is a // dead selection rather than a merely outdated one. Routing already redirects these ids @@ -151,6 +187,11 @@ function renameInList(value: unknown, from: string, to: string): string[] | null return next; } +function dropRenamedIdsFromList(value: unknown, from: string, to: string): string[] | null { + if (!Array.isArray(value) || !value.includes(from)) return null; + return value.filter(entry => typeof entry === "string" && entry !== from && entry !== to); +} + function renameInRecord(value: unknown, from: string, to: string): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) return null; const record = value as Record; @@ -178,6 +219,36 @@ function dropFromRecord(value: unknown, from: string): Record | return next; } +function sameStringArray(value: unknown, expected: readonly string[]): boolean { + return Array.isArray(value) + && value.length === expected.length + && value.every((entry, index) => entry === expected[index]); +} + +/** Refresh only exact defaults emitted by the previous registry; preserve user overrides. */ +function refreshTargetSeed(row: Record, rename: ModelRename): boolean { + const refresh = rename.targetSeedRefresh; + if (!refresh) return false; + let changed = false; + + const windows = row.modelContextWindows as Record | undefined; + if (windows && refresh.contextWindow && windows[rename.to] === refresh.contextWindow.from) { + windows[rename.to] = refresh.contextWindow.to; + changed = true; + } + + const reasoning = refresh.reasoning; + const efforts = row.modelReasoningEfforts as Record | undefined; + if (!reasoning || !efforts || !sameStringArray(efforts[rename.to], reasoning.fromEfforts)) return changed; + efforts[rename.to] = [...reasoning.toEfforts]; + + const defaults = (row.modelDefaultReasoningEfforts ??= {}) as Record; + if (!(rename.to in defaults)) defaults[rename.to] = reasoning.defaultEffort; + const maps = (row.modelReasoningEffortMap ??= {}) as Record; + if (!(rename.to in maps)) maps[rename.to] = { ...reasoning.effortMap }; + return true; +} + /** * `provider/model` rows in the top-level `disabledModels` list. * @@ -299,7 +370,9 @@ export function projectModelRenames( let touched = false; for (const field of MODEL_ID_LISTS) { if (isRegistryResidue(seed, field, row[field], rename)) continue; - const next = renameInList(row[field], rename.from, rename.to); + const next = rename.dropNoReasoningModels && field === "noReasoningModels" + ? dropRenamedIdsFromList(row[field], rename.from, rename.to) + : renameInList(row[field], rename.from, rename.to); if (!next) continue; row[field] = next; touched = true; @@ -318,6 +391,7 @@ export function projectModelRenames( touched = true; } if (renameDisabledModels(config, rename)) touched = true; + if (touched && refreshTargetSeed(row, rename)) touched = true; if (touched) { changed = true; diff --git a/tests/providers/model-rename-migration.test.ts b/tests/providers/model-rename-migration.test.ts index 69197ee149b..516219831f3 100644 --- a/tests/providers/model-rename-migration.test.ts +++ b/tests/providers/model-rename-migration.test.ts @@ -156,6 +156,10 @@ describe("registry model rename migration (#1610)", () => { defaultModel: "kimi-k2.7-code", models: ["k3", "k3[1m]", "kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-for-coding"], modelContextWindows: { "kimi-k2.7-code": 262_144, "kimi-for-coding": 262_144 }, + noReasoningModels: ["kimi-k2.7-code", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5", "kimi-for-coding"], + modelReasoningEfforts: { "kimi-k2.7-code": [], "kimi-for-coding": [] }, + modelDefaultReasoningEfforts: { k3: "max" }, + modelReasoningEffortMap: { k3: { high: "high" } }, }, }, } as unknown as OcxConfig; @@ -164,15 +168,14 @@ describe("registry model rename migration (#1610)", () => { const prov = config.providers.kimi!; expect(changed).toBe(true); expect(prov.defaultModel).toBe("kimi-for-coding"); - // The picker entry is renamed in place (it keeps its slot, per renameInList) and - // collapses into the row the old registry already seeded under the live alias. - expect(prov.models).toEqual(["k3", "k3[1m]", "kimi-for-coding", "kimi-k2.7-code-highspeed", "kimi-k2.6", "kimi-k2.5"]); - // The current registry seed no longer publishes the retired id anywhere (the kimi - // preset records are live-id only, so the residue guard has nothing to skip), and the - // record already carries a kimi-for-coding row from the old registry - the rename - // drops the retired key and keeps the newer row untouched. - expect(prov.modelContextWindows?.["kimi-k2.7-code"]).toBeUndefined(); - expect(prov.modelContextWindows?.["kimi-for-coding"]).toBe(262_144); + expect(prov.models).toEqual(["k3", "k3[1m]", "kimi-for-coding"]); + expect(prov.modelContextWindows).toEqual({ "kimi-for-coding": 1_048_576 }); + expect(prov.noReasoningModels).toEqual([]); + expect(prov.modelReasoningEfforts).toEqual({ "kimi-for-coding": ["low", "high", "max"] }); + expect(prov.modelDefaultReasoningEfforts).toEqual({ k3: "max", "kimi-for-coding": "max" }); + expect(prov.modelReasoningEffortMap?.["kimi-for-coding"]).toEqual({ + none: "none", low: "low", medium: "high", high: "high", xhigh: "max", max: "max", + }); }); test("repairs the kimi-code key preset row the same way", () => { @@ -193,7 +196,34 @@ describe("registry model rename migration (#1610)", () => { const prov = config.providers["kimi-code"]!; expect(changed).toBe(true); expect(prov.defaultModel).toBe("kimi-for-coding"); - expect(prov.models).toEqual(["k3", "k3[1m]", "kimi-for-coding", "kimi-k2.6", "kimi-k2.5"]); + expect(prov.models).toEqual(["k3", "k3[1m]", "kimi-for-coding"]); + }); + + test("preserves explicit kimi-for-coding metadata while retiring old ids", () => { + const stale = { + providers: { + kimi: { + adapter: "openai-chat", + baseUrl: "https://api.kimi.com/coding/v1", + authMode: "oauth", + defaultModel: "kimi-k2.6", + models: ["kimi-k2.6", "kimi-for-coding"], + modelContextWindows: { "kimi-k2.6": 262_144, "kimi-for-coding": 131_072 }, + modelReasoningEfforts: { "kimi-k2.6": [], "kimi-for-coding": ["low"] }, + modelDefaultReasoningEfforts: { "kimi-for-coding": "low" }, + modelReasoningEffortMap: { "kimi-for-coding": { medium: "low" } }, + }, + }, + } as unknown as OcxConfig; + + const { config } = projectModelRenames(stale, MODEL_RENAMES); + const prov = config.providers.kimi!; + expect(prov.defaultModel).toBe("kimi-for-coding"); + expect(prov.models).toEqual(["kimi-for-coding"]); + expect(prov.modelContextWindows?.["kimi-for-coding"]).toBe(131_072); + expect(prov.modelReasoningEfforts?.["kimi-for-coding"]).toEqual(["low"]); + expect(prov.modelDefaultReasoningEfforts?.["kimi-for-coding"]).toBe("low"); + expect(prov.modelReasoningEffortMap?.["kimi-for-coding"]).toEqual({ medium: "low" }); }); test("leaves a kimi row repointed at a different gateway alone", () => { From 1bce6f6b5493b6b752bd0a5dd3b8bcef1bef824e Mon Sep 17 00:00:00 2001 From: panyuanyuan Date: Mon, 21 Sep 2026 12:35:48 +0800 Subject: [PATCH 13/14] fix(kimi): drop the stale no-reasoning classification even when only the replacement id is present Address the CodeRabbit finding on the maintainer's 608d7a22b: a saved row can carry kimi-for-coding in noReasoningModels while every retired id is already gone from the row (the pre-K2.8 registry seeded the alias there). The early return in dropRenamedIdsFromList required the retired id, so the stale classification survived and kept the reasoning picker disabled for the live alias. Proceed when the list contains either id and filter both. Verified: model-rename-migration + provider-registry-parity 101 pass, tsc clean; new regression test covers the replacement-id-only row. --- src/providers/model-rename-migration.ts | 6 ++++- .../providers/model-rename-migration.test.ts | 25 +++++++++++++++++++ 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/providers/model-rename-migration.ts b/src/providers/model-rename-migration.ts index 5d9a8ff6a18..91db1f0b06b 100644 --- a/src/providers/model-rename-migration.ts +++ b/src/providers/model-rename-migration.ts @@ -188,7 +188,11 @@ function renameInList(value: unknown, from: string, to: string): string[] | null } function dropRenamedIdsFromList(value: unknown, from: string, to: string): string[] | null { - if (!Array.isArray(value) || !value.includes(from)) return null; + // The replacement id alone is enough to proceed: the pre-rename registry can have + // seeded `to` here while every retired id is already gone from the row. Leaving that + // stale classification in place would keep the picker disabled for a newly + // adjustable alias, so both ids are filtered whenever either one is present. + if (!Array.isArray(value) || (!value.includes(from) && !value.includes(to))) return null; return value.filter(entry => typeof entry === "string" && entry !== from && entry !== to); } diff --git a/tests/providers/model-rename-migration.test.ts b/tests/providers/model-rename-migration.test.ts index 516219831f3..c18f32ce8ba 100644 --- a/tests/providers/model-rename-migration.test.ts +++ b/tests/providers/model-rename-migration.test.ts @@ -226,6 +226,31 @@ describe("registry model rename migration (#1610)", () => { expect(prov.modelReasoningEffortMap?.["kimi-for-coding"]).toEqual({ medium: "low" }); }); + test("clears a stale no-reasoning classification saved under the live alias alone", () => { + // A config saved by the pre-K2.8 registry can carry kimi-for-coding in + // noReasoningModels even after every retired id is gone from the row - the old + // registry seeded the alias there. With no `from` left to rename, the stale + // classification would survive and keep the picker disabled; the drop must + // therefore trigger on the replacement id alone. + const stale = { + providers: { + kimi: { + adapter: "openai-chat", + baseUrl: "https://api.kimi.com/coding/v1", + authMode: "oauth", + defaultModel: "kimi-for-coding", + models: ["k3", "k3[1m]", "kimi-for-coding"], + noReasoningModels: ["kimi-for-coding"], + }, + }, + } as unknown as OcxConfig; + + const { config, changed } = projectModelRenames(stale, MODEL_RENAMES); + const prov = config.providers.kimi!; + expect(changed).toBe(true); + expect(prov.noReasoningModels).toEqual([]); + }); + test("leaves a kimi row repointed at a different gateway alone", () => { const custom = { providers: { From 4a3044efee4977c05f333360aebb48b71c437157 Mon Sep 17 00:00:00 2001 From: JUN Date: Mon, 21 Sep 2026 14:13:53 +0900 Subject: [PATCH 14/14] fix(kimi): preserve explicit reasoning overrides --- src/providers/model-rename-migration.ts | 28 ++++++++++++++----- .../providers/model-rename-migration.test.ts | 22 +++++++++++++++ 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/src/providers/model-rename-migration.ts b/src/providers/model-rename-migration.ts index 91db1f0b06b..0c66e803de3 100644 --- a/src/providers/model-rename-migration.ts +++ b/src/providers/model-rename-migration.ts @@ -187,12 +187,15 @@ function renameInList(value: unknown, from: string, to: string): string[] | null return next; } -function dropRenamedIdsFromList(value: unknown, from: string, to: string): string[] | null { - // The replacement id alone is enough to proceed: the pre-rename registry can have - // seeded `to` here while every retired id is already gone from the row. Leaving that - // stale classification in place would keep the picker disabled for a newly - // adjustable alias, so both ids are filtered whenever either one is present. - if (!Array.isArray(value) || (!value.includes(from) && !value.includes(to))) return null; +function dropRenamedIdsFromList( + value: unknown, + from: string, + to: string, + dropStaleTarget: boolean, +): string[] | null { + if (!Array.isArray(value)) return null; + const hasRetired = value.includes(from); + if (!hasRetired && !(dropStaleTarget && value.includes(to))) return null; return value.filter(entry => typeof entry === "string" && entry !== from && entry !== to); } @@ -229,6 +232,12 @@ function sameStringArray(value: unknown, expected: readonly string[]): boolean { && value.every((entry, index) => entry === expected[index]); } +function targetReasoningMatchesStaleSeed(row: Record, rename: ModelRename): boolean { + const reasoning = rename.targetSeedRefresh?.reasoning; + const efforts = row.modelReasoningEfforts as Record | undefined; + return !!reasoning && !!efforts && sameStringArray(efforts[rename.to], reasoning.fromEfforts); +} + /** Refresh only exact defaults emitted by the previous registry; preserve user overrides. */ function refreshTargetSeed(row: Record, rename: ModelRename): boolean { const refresh = rename.targetSeedRefresh; @@ -375,7 +384,12 @@ export function projectModelRenames( for (const field of MODEL_ID_LISTS) { if (isRegistryResidue(seed, field, row[field], rename)) continue; const next = rename.dropNoReasoningModels && field === "noReasoningModels" - ? dropRenamedIdsFromList(row[field], rename.from, rename.to) + ? dropRenamedIdsFromList( + row[field], + rename.from, + rename.to, + targetReasoningMatchesStaleSeed(row, rename), + ) : renameInList(row[field], rename.from, rename.to); if (!next) continue; row[field] = next; diff --git a/tests/providers/model-rename-migration.test.ts b/tests/providers/model-rename-migration.test.ts index c18f32ce8ba..63f3ff71b69 100644 --- a/tests/providers/model-rename-migration.test.ts +++ b/tests/providers/model-rename-migration.test.ts @@ -241,6 +241,7 @@ describe("registry model rename migration (#1610)", () => { defaultModel: "kimi-for-coding", models: ["k3", "k3[1m]", "kimi-for-coding"], noReasoningModels: ["kimi-for-coding"], + modelReasoningEfforts: { "kimi-for-coding": [] }, }, }, } as unknown as OcxConfig; @@ -249,6 +250,27 @@ describe("registry model rename migration (#1610)", () => { const prov = config.providers.kimi!; expect(changed).toBe(true); expect(prov.noReasoningModels).toEqual([]); + expect(prov.modelReasoningEfforts?.["kimi-for-coding"]).toEqual(["low", "high", "max"]); + }); + + test("preserves an explicit no-reasoning override on the live alias", () => { + const configured = { + providers: { + kimi: { + adapter: "openai-chat", + baseUrl: "https://api.kimi.com/coding/v1", + authMode: "oauth", + defaultModel: "kimi-for-coding", + models: ["k3", "k3[1m]", "kimi-for-coding"], + noReasoningModels: ["kimi-for-coding"], + modelReasoningEfforts: { "kimi-for-coding": ["low"] }, + }, + }, + } as unknown as OcxConfig; + + const { config, changed } = projectModelRenames(configured, MODEL_RENAMES); + expect(changed).toBe(false); + expect(config.providers.kimi?.noReasoningModels).toEqual(["kimi-for-coding"]); }); test("leaves a kimi row repointed at a different gateway alone", () => {