mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 13:52:09 +03:00
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.
This commit is contained in:
@@ -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 },
|
||||
],
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -58,7 +58,9 @@ const MODEL_ALIASES: Record<string, string> = {
|
||||
"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",
|
||||
|
||||
@@ -77,6 +77,35 @@ export const PROVIDER_MODELS_CONFIG: Record<string, ProviderModelsConfigEntry> =
|
||||
.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",
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user