mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-22 23:22:09 +03:00
⭐5 — Provider x-search de primeira classe (SuperGrok/xAI x_search) em POST /v1/search e MCP omniroute_x_search. Fallback de credenciais xai-oauth→xao→xai; distinto de web search e do X Developer MCP. Reconciliado com o release tip (que já incluía #10981 "skip catalog-default SearXNG" deste mesmo lote): merge trouxe 5 conflitos reais de contagem gerada (llm.txt/README.md/AGENTS.md/PROVIDER_REFERENCE.md/SVGs/46 mirrors i18n, todos verificados como bump puro 347→348, sem perda de conteúdo do HEAD) + 1 conflito real de mergeable=CONFLICTING. Durante a validação, os 3 testes novos de SearXNG expuseram um bug real de interação com #10981: `isUnconfiguredLoopbackSearchProvider()` checava o baseUrl ESTÁTICO do catálogo em vez do baseUrl efetivo (após override de `provider_options.baseUrl` ou `providerSpecificData.baseUrl` da conexão), então QUALQUER request a searxng-search — mesmo com override customizado — era rejeitado como se fosse o default não-configurado. Corrigido em `open-sse/handlers/search.ts` (resolve o baseUrl efetivo via `resolveSearchBaseUrl()` antes do skip-check, tanto para o provider primário quanto o alternate). Um teste do próprio #10988 que assumia o comportamento pré-#10981 (default localhost:8888 sempre atendido) foi atualizado para refletir o comportamento já mesclado e intencional (503 quando não configurado). Validação completa: typecheck limpo, 70/70 testes unit (search-route/search-registry/x-search-provider/searxng-loopback-default), 24/24 vitest MCP, 14/14 integration (search-providers-catalog), lint limpo nos arquivos tocados, docs-counts-sync OK (2 drifts soft pré-existentes, não relacionados), gates estáticos (file-size/complexity/cognitive/dead-code/changelog) todos OK.
248 lines
8.8 KiB
TypeScript
248 lines
8.8 KiB
TypeScript
// Tests for the international Kimi web executor (www.kimi.com Connect-RPC API).
|
|
//
|
|
// Previously this provider targeted kimi.moonshot.cn; that domain now redirects
|
|
// every non-CN visitor to www.kimi.com, which uses a Connect-RPC streaming API.
|
|
// These tests pin the parser behavior of the Connect envelope framing and the
|
|
// JSON event-delta extractor.
|
|
|
|
import { describe, it } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const mod = await import("../../open-sse/executors/kimi-web.ts");
|
|
const { getModelsByProviderId } = await import("../../open-sse/config/providerModels.ts");
|
|
|
|
describe("KimiWebExecutor", () => {
|
|
it("can be instantiated", () => {
|
|
const executor = new mod.KimiWebExecutor();
|
|
assert.ok(executor);
|
|
});
|
|
|
|
it("execute returns a 400 error when no access token is provided", async () => {
|
|
const executor = new mod.KimiWebExecutor();
|
|
const result = await executor.execute({
|
|
model: "k2d6",
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
stream: false,
|
|
credentials: { apiKey: "" },
|
|
signal: null,
|
|
} as never);
|
|
assert.equal(result.response.status, 400);
|
|
const body = (await result.response.json()) as { error: { code: string } };
|
|
assert.match(body.error.code, /HTTP_400|400/);
|
|
});
|
|
|
|
it("execute targets www.kimi.com (not kimi.moonshot.cn)", async () => {
|
|
const executor = new mod.KimiWebExecutor();
|
|
let capturedUrl = "";
|
|
const originalFetch = globalThis.fetch;
|
|
try {
|
|
globalThis.fetch = (async (url: Parameters<typeof fetch>[0]) => {
|
|
capturedUrl = String(url);
|
|
return new Response(new ReadableStream({ start: (c) => c.close() }), {
|
|
status: 200,
|
|
headers: { "content-type": "application/connect+json" },
|
|
});
|
|
}) as typeof fetch;
|
|
await executor.execute({
|
|
model: "k2d6",
|
|
body: { messages: [{ role: "user", content: "hi" }] },
|
|
stream: false,
|
|
credentials: { apiKey: "opaque-kimi-access-token" },
|
|
signal: null,
|
|
} as never);
|
|
assert.ok(capturedUrl.startsWith("https://www.kimi.ai/"), `got ${capturedUrl}`);
|
|
assert.ok(!capturedUrl.includes("moonshot.cn"));
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
it("builds the current snake_case ChatRequest without replaying cookies", async () => {
|
|
const executor = new mod.KimiWebExecutor();
|
|
const endStream = mod.frameConnectMessage("{}");
|
|
endStream[0] = 2;
|
|
let capturedInit: RequestInit | undefined;
|
|
const originalFetch = globalThis.fetch;
|
|
try {
|
|
globalThis.fetch = (async (_url: Parameters<typeof fetch>[0], init?: RequestInit) => {
|
|
capturedInit = init;
|
|
return new Response(endStream, {
|
|
status: 200,
|
|
headers: { "content-type": "application/connect+json" },
|
|
});
|
|
}) as typeof fetch;
|
|
const result = await executor.execute({
|
|
model: "k3",
|
|
body: {
|
|
model: "k3",
|
|
messages: [
|
|
{ role: "system", content: "Be terse." },
|
|
{ role: "user", content: "hi" },
|
|
],
|
|
tools: null,
|
|
functions: null,
|
|
},
|
|
stream: false,
|
|
credentials: { apiKey: "access_token=opaque-token" },
|
|
signal: null,
|
|
} as never);
|
|
|
|
assert.equal(result.response.status, 200);
|
|
const headers = capturedInit?.headers as Record<string, string>;
|
|
assert.equal(headers.Authorization, "Bearer opaque-token");
|
|
assert.equal(headers.Cookie, undefined);
|
|
|
|
const framed = capturedInit?.body as Uint8Array;
|
|
const decoded = mod.decodeConnectFrame(framed, 0);
|
|
const request = decoded.frame?.message as {
|
|
chat_id: string;
|
|
kimiplus_id: string;
|
|
scenario: string;
|
|
model?: unknown;
|
|
tools: unknown[];
|
|
message: { blocks: Array<{ text: { content: string } }> };
|
|
options: {
|
|
system_prompt: string;
|
|
thinking: boolean;
|
|
enable_plugin: boolean;
|
|
reasoning_effort: string;
|
|
context_length: string;
|
|
};
|
|
};
|
|
assert.equal(request.chat_id, "");
|
|
assert.equal(request.kimiplus_id, undefined);
|
|
assert.equal(request.scenario, "SCENARIO_K2D5");
|
|
assert.equal(request.model, undefined);
|
|
assert.deepEqual(request.tools, []);
|
|
assert.equal(request.message.blocks[0].text.content, "hi");
|
|
assert.equal(request.options.system_prompt, "Be terse.");
|
|
assert.equal(request.options.thinking, true);
|
|
assert.equal(request.options.enable_plugin, false);
|
|
assert.equal(request.options.reasoning_effort, "REASONING_EFFORT_NONE");
|
|
assert.equal(request.options.context_length, undefined);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
|
|
it("maps k2d6 effort exactly and rejects unsupported levels", async () => {
|
|
const executor = new mod.KimiWebExecutor();
|
|
const endStream = mod.frameConnectMessage("{}");
|
|
endStream[0] = 2;
|
|
const originalFetch = globalThis.fetch;
|
|
try {
|
|
globalThis.fetch = (async () => new Response(endStream, { status: 200 })) as typeof fetch;
|
|
const accepted = await executor.execute({
|
|
model: "k2d6",
|
|
body: {
|
|
model: "k2d6",
|
|
messages: [{ role: "user", content: "hi" }],
|
|
reasoning_effort: "low",
|
|
},
|
|
stream: false,
|
|
credentials: { apiKey: "opaque-token" },
|
|
signal: null,
|
|
} as never);
|
|
assert.equal(accepted.transformedBody.options.reasoning_effort, "REASONING_EFFORT_LOW");
|
|
|
|
const rejected = await executor.execute({
|
|
model: "k2d6",
|
|
body: {
|
|
model: "k2d6",
|
|
messages: [{ role: "user", content: "hi" }],
|
|
reasoning_effort: "high",
|
|
},
|
|
stream: false,
|
|
credentials: { apiKey: "opaque-token" },
|
|
signal: null,
|
|
} as never);
|
|
assert.equal(rejected.response.status, 400);
|
|
} finally {
|
|
globalThis.fetch = originalFetch;
|
|
}
|
|
});
|
|
});
|
|
|
|
describe("resolveModelConfig", () => {
|
|
const { resolveModelConfig } = mod;
|
|
|
|
it("maps k3 to the K2D5 route (same as K2.6, not premium OK Computer)", () => {
|
|
const cfg = resolveModelConfig("k3");
|
|
assert.ok(cfg);
|
|
assert.equal(cfg.scenario, "SCENARIO_K2D5");
|
|
assert.equal(cfg.kimiPlusId, undefined);
|
|
assert.deepEqual(cfg.supportedReasoningEfforts, [
|
|
"REASONING_EFFORT_NONE",
|
|
"REASONING_EFFORT_LOW",
|
|
]);
|
|
assert.equal(cfg.defaultReasoningEffort, "REASONING_EFFORT_NONE");
|
|
assert.deepEqual(cfg.supportedContextLengths, []);
|
|
assert.equal(cfg.defaultContextLength, undefined);
|
|
});
|
|
|
|
it("maps k2d6 to the K2D5 route and its exact effort enum", () => {
|
|
const cfg = resolveModelConfig("k2d6");
|
|
assert.ok(cfg);
|
|
assert.equal(cfg.scenario, "SCENARIO_K2D5");
|
|
assert.deepEqual(cfg.supportedReasoningEfforts, [
|
|
"REASONING_EFFORT_NONE",
|
|
"REASONING_EFFORT_LOW",
|
|
]);
|
|
assert.equal(cfg.defaultReasoningEffort, "REASONING_EFFORT_NONE");
|
|
});
|
|
|
|
it("does not silently route an unknown or unsupported agent model", () => {
|
|
assert.equal(resolveModelConfig("k2d6-thinking"), null);
|
|
assert.equal(resolveModelConfig("k3-agent-ultra"), null);
|
|
});
|
|
});
|
|
|
|
describe("kimi-web catalog", () => {
|
|
it("lists only currently supported non-agent web models", () => {
|
|
const models = getModelsByProviderId("kimi-web");
|
|
assert.deepEqual(
|
|
models.map((model) => ({ id: model.id, name: model.name })),
|
|
[
|
|
{ id: "k3", name: "K3" },
|
|
{ id: "k2d6", name: "K2.6" },
|
|
]
|
|
);
|
|
assert.ok(models.every((model) => model.supportsReasoning));
|
|
assert.ok(!models.some((model) => model.id.includes("agent")));
|
|
assert.ok(
|
|
!models.some((model) => ["kimi-default", "kimi-k2.6", "kimi-128k"].includes(model.id))
|
|
);
|
|
});
|
|
});
|
|
|
|
describe("extractKimiAccessToken", () => {
|
|
const { extractKimiAccessToken } = mod;
|
|
|
|
it("returns empty string for empty input", () => {
|
|
assert.equal(extractKimiAccessToken(""), "");
|
|
assert.equal(extractKimiAccessToken(" "), "");
|
|
});
|
|
|
|
it("accepts the current opaque localStorage access token", () => {
|
|
assert.equal(extractKimiAccessToken("opaque-token"), "opaque-token");
|
|
});
|
|
|
|
it("keeps legacy kimi-auth cookie input compatible", () => {
|
|
const jwt = "eyJhbGciOiJIUzUxMiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ4In0.signature";
|
|
const pasted = `_ga=GA1.1.x; theme=dark; kimi-auth=${jwt}; _gcl_au=1.1.x; lang=en-US`;
|
|
assert.equal(extractKimiAccessToken(pasted), jwt);
|
|
});
|
|
|
|
it("extracts access_token from storage-like input", () => {
|
|
assert.equal(extractKimiAccessToken("access_token=current-token"), "current-token");
|
|
});
|
|
|
|
it("strips a leading Authorization: Bearer label", () => {
|
|
assert.equal(extractKimiAccessToken("Authorization: Bearer current-token"), "current-token");
|
|
});
|
|
|
|
it("returns empty when no Kimi token is present", () => {
|
|
assert.equal(extractKimiAccessToken("foo=bar; baz=qux"), "");
|
|
});
|
|
});
|