From 8d0ed4f936f9bca7f710e47b79ebb7c137d21ced Mon Sep 17 00:00:00 2001 From: janeza2 <49841619+janeza2@users.noreply.github.com> Date: Fri, 3 Jul 2026 18:35:56 +0700 Subject: [PATCH] fix(kimi-web, qwen-web): align model catalog with live /models + map scenario per model (#5915) * fix(kimi-web): align catalog with live models Update the kimi-web catalog and request scenario selection to match www.kimi.com's live GetAvailableModels response. * fix(qwen-web): stop aliasing qwen3-coder-plus Keep qwen3-coder-plus as its own model because it is present in the live Qwen web models catalog. --- .../providers/registry/kimi/web/index.ts | 12 +++++-- open-sse/executors/kimi-web.ts | 36 +++++++++++++------ open-sse/executors/qwen-web.ts | 4 ++- .../models/discovery/providerModelsConfig.ts | 29 +++++++++++++++ tests/unit/executor-kimi-web.test.ts | 26 ++++++++++++-- tests/unit/web-cookie-providers-new.test.ts | 6 ++-- 6 files changed, 94 insertions(+), 19 deletions(-) diff --git a/open-sse/config/providers/registry/kimi/web/index.ts b/open-sse/config/providers/registry/kimi/web/index.ts index c86d3ce426..1bcd615615 100644 --- a/open-sse/config/providers/registry/kimi/web/index.ts +++ b/open-sse/config/providers/registry/kimi/web/index.ts @@ -14,8 +14,14 @@ export const kimi_webProvider: RegistryEntry = { authType: "apikey", authHeader: "cookie", models: [ - { id: "kimi-default", name: "Kimi Default" }, - { id: "kimi-k2.6", name: "Kimi K2.6 (Thinking)" }, - { id: "kimi-128k", name: "Kimi 128K (Long Context)" }, + // Model ids are the `key` field from www.kimi.com's + // `/apiv2/kimi.gateway.config.v1.ConfigService/GetAvailableModels` response. + // Agent / Agent-Swarm variants (`k2d6-agent`, `k2d6-agent-ultra`) are + // intentionally NOT exposed — they need a different scenario + // (`SCENARIO_OK_COMPUTER`) plus `kimiPlusId` / `agentMode` fields, which + // the executor does not yet shape. Use `kimi-coding` (api.kimi.com) for + // agentic flows. + { id: "k2d6", name: "K2.6 Instant" }, + { id: "k2d6-thinking", name: "K2.6 Thinking", supportsReasoning: true }, ], }; diff --git a/open-sse/executors/kimi-web.ts b/open-sse/executors/kimi-web.ts index f45e9d1072..cf4ff4bdbe 100644 --- a/open-sse/executors/kimi-web.ts +++ b/open-sse/executors/kimi-web.ts @@ -36,7 +36,24 @@ const CHAT_URL = `${BASE_URL}/apiv2/kimi.gateway.chat.v1.ChatService/Chat`; const USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/149.0.0.0 Safari/537.36"; -const DEFAULT_SCENARIO = "SCENARIO_K2D5"; +/** + * Map a Kimi model id (the `key` field from `GetAvailableModels`) to the + * request shape the upstream expects. Today only the chat-tier `k2d6` family + * is supported — the agent variants (`k2d6-agent`, `k2d6-agent-ultra`) need + * a different scenario (`SCENARIO_OK_COMPUTER`) plus `kimiPlusId` / + * `agentMode` fields that this executor does not shape; users who need + * agentic Kimi should use the `kimi-coding` (api.kimi.com) provider. + */ +export interface KimiModelConfig { + scenario: string; + thinking: boolean; +} + +export function resolveModelConfig(modelId: string): KimiModelConfig { + if (modelId === "k2d6-thinking") return { scenario: "SCENARIO_K2D5", thinking: true }; + // `k2d6` (Instant) and any unknown id fall back to the default chat scenario. + return { scenario: "SCENARIO_K2D5", thinking: false }; +} /** Wrap a JSON message in the 5-byte Connect streaming envelope (flags + length). */ export function frameConnectMessage(json: string): Uint8Array { @@ -206,14 +223,14 @@ export class KimiWebExecutor extends BaseExecutor { return headers; } - private buildRequestBody(prompt: string, wantThinking: boolean): string { + private buildRequestBody(prompt: string, wantThinking: boolean, scenario: string): string { return JSON.stringify({ - scenario: DEFAULT_SCENARIO, + scenario, tools: [{ type: "TOOL_TYPE_SEARCH", search: {} }, { type: "TOOL_TYPE_CRON_JOB" }], message: { role: "user", blocks: [{ message_id: "", text: { content: prompt } }], - scenario: DEFAULT_SCENARIO, + scenario, }, options: { thinking: wantThinking, enable_plugin: true }, }); @@ -236,14 +253,13 @@ export class KimiWebExecutor extends BaseExecutor { const messages = (bodyObj.messages as Array<{ role: string; content: unknown }>) || []; const modelId = (bodyObj.model as string) || "kimi-default"; - // Decide thinking intent. A user sending `reasoning_effort: "none"` is - // explicit — honour it even when the model id suggests a thinking variant. - // Otherwise thinking models (kimi-k2.6 etc.) default to thinking on. - const modelWantsThinking = /k2\.6|k2-6|think/i.test(modelId); - const wantThinking = bodyObj.reasoning_effort === "none" ? false : modelWantsThinking; + // Resolve scenario + default thinking flag from the model id (catalog truth), + // then honour an explicit `reasoning_effort: "none"` override from the caller. + const modelConfig = resolveModelConfig(modelId); + const wantThinking = bodyObj.reasoning_effort === "none" ? false : modelConfig.thinking; const prompt = foldMessages(messages); - const reqBody = this.buildRequestBody(prompt, wantThinking); + const reqBody = this.buildRequestBody(prompt, wantThinking, modelConfig.scenario); const reqHeaders = this.buildKimiHeaders(jwt); // Connect framing wraps the JSON body in a 5-byte envelope. Without it the diff --git a/open-sse/executors/qwen-web.ts b/open-sse/executors/qwen-web.ts index a61072d283..485eb93758 100644 --- a/open-sse/executors/qwen-web.ts +++ b/open-sse/executors/qwen-web.ts @@ -58,7 +58,9 @@ const MODEL_ALIASES: Record = { "qwen3-plus": "qwen3.7-plus", "qwen3-max": "qwen3.7-max", "qwen3-flash": "qwen3.6-plus", - "qwen3-coder-plus": "qwen3.7-max", + // Note: `qwen3-coder-plus` is a real upstream model id (Qwen3-Coder) and + // must NOT be aliased — the previous `"qwen3-coder-plus": "qwen3.7-max"` + // entry silently rewrote valid coder requests to the wrong model. "qwen3-coder-flash": "qwen3.6-plus", qwen: "qwen3.7-max", qwen3: "qwen3.7-max", diff --git a/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts b/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts index 4cffe5772b..b7bb1940a8 100644 --- a/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts +++ b/src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts @@ -77,6 +77,35 @@ export const PROVIDER_MODELS_CONFIG: Record = .filter((m: any) => m.id); }, }, + // #5858 follow-up: kimi-web (cookie provider) on the international domain. + // `GetAvailableModels` returns the model list as a plain JSON envelope + // (no Connect framing on either request or response — only the chat + // completion endpoint uses the 5-byte envelope). Auth: Bearer JWT extracted + // from the `kimi-auth` cookie the user pasted. Agent variants + // (`k2d6-agent*`) need a different scenario + agent fields this executor + // doesn't shape, so they're filtered out. + "kimi-web": { + url: "https://www.kimi.com/apiv2/kimi.gateway.config.v1.ConfigService/GetAvailableModels", + method: "GET", + headers: { accept: "application/json, text/plain, */*", "Content-Type": "application/json" }, + authHeader: "Authorization", + authPrefix: "Bearer ", + parseResponse: (data) => { + const list = (data?.availableModels || []) as Array<{ + key?: string; + displayName?: string; + thinking?: boolean; + }>; + return list + .filter((m) => typeof m.key === "string" && !m.key?.includes("agent")) + .map((m) => ({ + id: m.key as string, + name: m.displayName || (m.key as string), + supportsReasoning: !!m.thinking, + owned_by: "kimi", + })); + }, + }, antigravity: { url: getAntigravityModelsDiscoveryUrls()[0], method: "POST", diff --git a/tests/unit/executor-kimi-web.test.ts b/tests/unit/executor-kimi-web.test.ts index 05f6e52f34..bff69e30d6 100644 --- a/tests/unit/executor-kimi-web.test.ts +++ b/tests/unit/executor-kimi-web.test.ts @@ -19,7 +19,7 @@ describe("KimiWebExecutor", () => { it("execute returns a 400 error when no JWT is provided", async () => { const executor = new mod.KimiWebExecutor(); const result = await executor.execute({ - model: "kimi-default", + model: "k2d6", body: { messages: [{ role: "user", content: "hi" }] }, stream: false, credentials: { apiKey: "" }, @@ -43,7 +43,7 @@ describe("KimiWebExecutor", () => { }); }) as typeof fetch; await executor.execute({ - model: "kimi-default", + model: "k2d6", body: { messages: [{ role: "user", content: "hi" }] }, stream: false, credentials: { apiKey: "kimi-auth=fake.jwt.token" }, @@ -57,6 +57,28 @@ describe("KimiWebExecutor", () => { }); }); +describe("resolveModelConfig", () => { + const { resolveModelConfig } = mod; + + it("maps k2d6-thinking to the K2D5 scenario with thinking enabled", () => { + const cfg = resolveModelConfig("k2d6-thinking"); + assert.equal(cfg.scenario, "SCENARIO_K2D5"); + assert.equal(cfg.thinking, true); + }); + + it("maps k2d6 (Instant) to the K2D5 scenario without thinking", () => { + const cfg = resolveModelConfig("k2d6"); + assert.equal(cfg.scenario, "SCENARIO_K2D5"); + assert.equal(cfg.thinking, false); + }); + + it("falls back to K2D5 + no thinking for an unknown model id", () => { + const cfg = resolveModelConfig("k2d6-agent"); + assert.equal(cfg.scenario, "SCENARIO_K2D5"); + assert.equal(cfg.thinking, false); + }); +}); + describe("extractKimiJwt", () => { const { extractKimiJwt } = mod; diff --git a/tests/unit/web-cookie-providers-new.test.ts b/tests/unit/web-cookie-providers-new.test.ts index 2cc59bcd23..4390fc33ca 100644 --- a/tests/unit/web-cookie-providers-new.test.ts +++ b/tests/unit/web-cookie-providers-new.test.ts @@ -675,7 +675,7 @@ test("Kimi Web: targets www.kimi.com (international)", async () => { const executor = new KimiWebExecutor(); const result = await executor.execute({ ...noopExecuteInput, - model: "kimi-default", + model: "k2d6", credentials: { apiKey: "kimi-auth=eyJ.eyJzdWI.signature" }, }); assert.ok(result.response instanceof Response); @@ -695,7 +695,7 @@ test("Kimi Web: missing JWT returns a 400 before fetching", async () => { const executor = new KimiWebExecutor(); const result = await executor.execute({ ...noopExecuteInput, - model: "kimi-default", + model: "k2d6", credentials: { apiKey: "" }, }); assert.equal(result.response.status, 400); @@ -707,7 +707,7 @@ test("Kimi Web: error response returns error result", async () => { const executor = new KimiWebExecutor(); const result = await executor.execute({ ...noopExecuteInput, - model: "kimi-default", + model: "k2d6", credentials: { apiKey: "kimi-auth=eyJ.eyJzdWI.signature" }, }); assert.ok(result.response instanceof Response);