mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-27 01:22:10 +03:00
perf(executors): lazy-load the executor registry — defer class imports + construction to first use (#11220) (#11421)
Validated in a combined 3-PR batch worktree off release/v3.8.51 tip (a sibling PR from the same author, #11495, was held out — a typecheck error in zai-web.ts only reproduced with this PR + #11495 boarded together, and cleared without #11495; isolated this PR alone confirmed clean on its own too, so the interaction belonged to #11495's side — see its comment). - Golden lock: executor-map-golden.test.ts — passes byte-identical (same keys, classes, provider identities, dispatch guards) - Focused tests part of batch's 94/94 node:test run - typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity — all OK - Full-repo lint: 228 pre-existing dashboard react-hooks/* findings, unrelated to this diff Thanks for the measured, careful methodology here — the golden-lock contract plus the isolated DATA_DIR benchmarking make this an easy PR to trust despite the wide surface (72 files).
This commit is contained in:
@@ -84,7 +84,7 @@ test("adobe-firefly is registered in VIDEO_PROVIDERS with adobe-firefly-video fo
|
||||
});
|
||||
|
||||
test("getExecutor(adobe-firefly) rejects chat completions", async () => {
|
||||
const executor = getExecutor("adobe-firefly");
|
||||
const executor = await getExecutor("adobe-firefly");
|
||||
assert.ok(executor);
|
||||
const result = await executor.execute({
|
||||
model: "adobe-firefly/nano-banana-pro",
|
||||
@@ -95,9 +95,10 @@ test("getExecutor(adobe-firefly) rejects chat completions", async () => {
|
||||
stream: false,
|
||||
credentials: { apiKey: "tok" },
|
||||
});
|
||||
assert.ok(result.response, "executor must return a Response wrapper");
|
||||
assert.equal(result.response.status, 400);
|
||||
const bodyText = await result.response.text();
|
||||
const response = result instanceof Response ? result : result.response;
|
||||
assert.ok(response, "executor must return a Response wrapper");
|
||||
assert.equal(response.status, 400);
|
||||
const bodyText = await response.text();
|
||||
assert.match(bodyText, /images\/generations|videos\/generations|media-generation/i);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,7 +5,8 @@ import {
|
||||
applyAzureParamRules,
|
||||
AZURE_COMPLETION_TOKEN_DEPLOYMENT,
|
||||
} from "../../open-sse/executors/azureParamRules.ts";
|
||||
import { getExecutor, AzureAiExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { getExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { AzureAiExecutor } from "../../open-sse/executors/azure-ai.ts";
|
||||
|
||||
/**
|
||||
* Regression guards for two Azure 400s observed against a live Azure AI Foundry
|
||||
@@ -87,8 +88,8 @@ test("the regex does not match unrelated names by accident", () => {
|
||||
assert.equal(AZURE_COMPLETION_TOKEN_DEPLOYMENT.test("Kimi-K2.7-Code"), false);
|
||||
});
|
||||
|
||||
test("azure-ai resolves to AzureAiExecutor, not the bare DefaultExecutor", () => {
|
||||
const executor = getExecutor("azure-ai");
|
||||
test("azure-ai resolves to AzureAiExecutor, not the bare DefaultExecutor", async () => {
|
||||
const executor = await getExecutor("azure-ai");
|
||||
assert.ok(
|
||||
executor instanceof AzureAiExecutor,
|
||||
"azure-ai must have its own executor so it inherits the Azure param rules"
|
||||
|
||||
@@ -59,11 +59,11 @@ function mockFetchCapture(status = 200, text = "Hello from Blackbox") {
|
||||
};
|
||||
}
|
||||
|
||||
test("BlackboxWebExecutor is registered in executor index", () => {
|
||||
test("BlackboxWebExecutor is registered in executor index", async () => {
|
||||
assert.ok(hasSpecializedExecutor("blackbox-web"));
|
||||
assert.ok(hasSpecializedExecutor("bb-web"));
|
||||
const executor = getExecutor("blackbox-web");
|
||||
const alias = getExecutor("bb-web");
|
||||
const executor = await getExecutor("blackbox-web");
|
||||
const alias = await getExecutor("bb-web");
|
||||
assert.ok(executor instanceof BlackboxWebExecutor);
|
||||
assert.ok(alias instanceof BlackboxWebExecutor);
|
||||
});
|
||||
|
||||
@@ -41,7 +41,7 @@ after(() => {
|
||||
test("no config (disabled by default) returns the provider's own executor", async () => {
|
||||
clearUpstreamProxyConfigCache("openai");
|
||||
const exec = await resolveExecutorWithProxy("openai");
|
||||
assert.equal(exec, getExecutor("openai"));
|
||||
assert.equal(exec, await getExecutor("openai"));
|
||||
});
|
||||
|
||||
test("mode 'native' returns the provider's own executor", async () => {
|
||||
@@ -52,7 +52,7 @@ test("mode 'native' returns the provider's own executor", async () => {
|
||||
});
|
||||
clearUpstreamProxyConfigCache("openai");
|
||||
const exec = await resolveExecutorWithProxy("openai");
|
||||
assert.equal(exec, getExecutor("openai"));
|
||||
assert.equal(exec, await getExecutor("openai"));
|
||||
});
|
||||
|
||||
test("mode 'cliproxyapi' returns the CLIProxyAPI passthrough executor", async () => {
|
||||
@@ -63,7 +63,7 @@ test("mode 'cliproxyapi' returns the CLIProxyAPI passthrough executor", async ()
|
||||
});
|
||||
clearUpstreamProxyConfigCache("anthropic");
|
||||
const exec = await resolveExecutorWithProxy("anthropic");
|
||||
assert.equal(exec, getExecutor("cliproxyapi"));
|
||||
assert.equal(exec, await getExecutor("cliproxyapi"));
|
||||
});
|
||||
|
||||
test("mode 'fallback' returns a distinct wrapper owning its own execute()", async () => {
|
||||
@@ -74,8 +74,8 @@ test("mode 'fallback' returns a distinct wrapper owning its own execute()", asyn
|
||||
});
|
||||
clearUpstreamProxyConfigCache("openai");
|
||||
const exec = await resolveExecutorWithProxy("openai");
|
||||
assert.notEqual(exec, getExecutor("openai"));
|
||||
assert.notEqual(exec, getExecutor("cliproxyapi"));
|
||||
assert.notEqual(exec, await getExecutor("openai"));
|
||||
assert.notEqual(exec, await getExecutor("cliproxyapi"));
|
||||
assert.equal(typeof exec.execute, "function");
|
||||
});
|
||||
|
||||
@@ -94,7 +94,7 @@ test("connection override 'claude-native' selects CLIProxyAPI even when provider
|
||||
const exec = await resolveExecutorWithProxy("openai", undefined, {
|
||||
cliproxyapiMode: "claude-native",
|
||||
});
|
||||
assert.equal(exec, getExecutor("cliproxyapi"));
|
||||
assert.equal(exec, await getExecutor("cliproxyapi"));
|
||||
});
|
||||
|
||||
test("connection override 'claude-native' selects CLIProxyAPI even with no provider config (default)", async () => {
|
||||
@@ -102,7 +102,7 @@ test("connection override 'claude-native' selects CLIProxyAPI even with no provi
|
||||
const exec = await resolveExecutorWithProxy("anthropic", undefined, {
|
||||
cliproxyapiMode: "claude-native",
|
||||
});
|
||||
assert.equal(exec, getExecutor("cliproxyapi"));
|
||||
assert.equal(exec, await getExecutor("cliproxyapi"));
|
||||
});
|
||||
|
||||
test("no connection override + provider mode native → native executor (unchanged)", async () => {
|
||||
@@ -115,13 +115,13 @@ test("no connection override + provider mode native → native executor (unchang
|
||||
const exec = await resolveExecutorWithProxy("openai", undefined, {
|
||||
someOtherField: "x",
|
||||
});
|
||||
assert.equal(exec, getExecutor("openai"));
|
||||
assert.equal(exec, await getExecutor("openai"));
|
||||
});
|
||||
|
||||
test("connection override absent (undefined providerSpecificData) preserves default behaviour", async () => {
|
||||
clearUpstreamProxyConfigCache("openai");
|
||||
const exec = await resolveExecutorWithProxy("openai");
|
||||
assert.equal(exec, getExecutor("openai"));
|
||||
assert.equal(exec, await getExecutor("openai"));
|
||||
});
|
||||
|
||||
test("connection override wins over provider mode 'fallback'", async () => {
|
||||
@@ -135,5 +135,5 @@ test("connection override wins over provider mode 'fallback'", async () => {
|
||||
cliproxyapiMode: "claude-native",
|
||||
});
|
||||
// Connection override short-circuits to the passthrough executor, not the fallback wrapper.
|
||||
assert.equal(exec, getExecutor("cliproxyapi"));
|
||||
assert.equal(exec, await getExecutor("cliproxyapi"));
|
||||
});
|
||||
|
||||
@@ -456,7 +456,7 @@ test("chatCore times out upstream execution before provider response headers", a
|
||||
// (fresh-DB default leaves it off → the waitFor below would never resolve;
|
||||
// failed deterministically on CI and on an isolated run, incl. at v3.8.18).
|
||||
await settingsDb.updateSettings({ call_log_pipeline_enabled: true });
|
||||
const executor = getExecutor("openai");
|
||||
const executor = await getExecutor("openai");
|
||||
const originalGetTimeoutMs = executor.getTimeoutMs?.bind(executor);
|
||||
executor.getTimeoutMs = () => 200;
|
||||
|
||||
|
||||
@@ -370,16 +370,16 @@ function reset() {
|
||||
|
||||
// ─── Registration ───────────────────────────────────────────────────────────
|
||||
|
||||
test("ChatGptWebExecutor is registered in executor index", () => {
|
||||
test("ChatGptWebExecutor is registered in executor index", async () => {
|
||||
assert.ok(hasSpecializedExecutor("chatgpt-web"));
|
||||
assert.ok(hasSpecializedExecutor("cgpt-web"));
|
||||
const executor = getExecutor("chatgpt-web");
|
||||
const executor = await getExecutor("chatgpt-web");
|
||||
assert.ok(executor instanceof ChatGptWebExecutor);
|
||||
});
|
||||
|
||||
test("ChatGptWebExecutor alias resolves to same type", () => {
|
||||
const a = getExecutor("chatgpt-web");
|
||||
const b = getExecutor("cgpt-web");
|
||||
test("ChatGptWebExecutor alias resolves to same type", async () => {
|
||||
const a = await getExecutor("chatgpt-web");
|
||||
const b = await getExecutor("cgpt-web");
|
||||
assert.ok(a instanceof ChatGptWebExecutor);
|
||||
assert.ok(b instanceof ChatGptWebExecutor);
|
||||
});
|
||||
|
||||
@@ -63,14 +63,14 @@ describe("ChipotleExecutor", () => {
|
||||
|
||||
it("is registered in executor index", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const exec = getExecutor("chipotle");
|
||||
const exec = await getExecutor("chipotle");
|
||||
assert.ok(exec, "chipotle executor should be registered");
|
||||
assert.ok(exec instanceof ChipotleExecutor);
|
||||
});
|
||||
|
||||
it("pepper alias works", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const exec = getExecutor("pepper");
|
||||
const exec = await getExecutor("pepper");
|
||||
assert.ok(exec, "pepper alias should be registered");
|
||||
assert.ok(exec instanceof ChipotleExecutor);
|
||||
});
|
||||
|
||||
@@ -22,14 +22,14 @@ test("B: ClaudeWebExecutor alias cw-web is registered", () => {
|
||||
assert.ok(hasSpecializedExecutor("cw-web"));
|
||||
});
|
||||
|
||||
test("C: ClaudeWebExecutor can be retrieved from executor registry", () => {
|
||||
const executor = getExecutor("claude-web");
|
||||
test("C: ClaudeWebExecutor can be retrieved from executor registry", async () => {
|
||||
const executor = await getExecutor("claude-web");
|
||||
assert.ok(executor instanceof ClaudeWebExecutor);
|
||||
});
|
||||
|
||||
test("D: ClaudeWebExecutor cw-web alias resolves to same type", () => {
|
||||
const a = getExecutor("claude-web");
|
||||
const b = getExecutor("cw-web");
|
||||
test("D: ClaudeWebExecutor cw-web alias resolves to same type", async () => {
|
||||
const a = await getExecutor("claude-web");
|
||||
const b = await getExecutor("cw-web");
|
||||
assert.ok(a instanceof ClaudeWebExecutor);
|
||||
assert.ok(b instanceof ClaudeWebExecutor);
|
||||
});
|
||||
|
||||
@@ -167,9 +167,9 @@ test("cloudflare-playground registry entry has no-auth shape and curated models"
|
||||
assert.equal(llama?.supportsReasoning, undefined);
|
||||
});
|
||||
|
||||
test("executor resolves for both the id and the cfp alias", () => {
|
||||
const byId = getExecutor("cloudflare-playground");
|
||||
const byAlias = getExecutor("cfp");
|
||||
test("executor resolves for both the id and the cfp alias", async () => {
|
||||
const byId = await getExecutor("cloudflare-playground");
|
||||
const byAlias = await getExecutor("cfp");
|
||||
assert.ok(byId instanceof CloudflarePlaygroundExecutor);
|
||||
assert.ok(byAlias instanceof CloudflarePlaygroundExecutor);
|
||||
});
|
||||
|
||||
@@ -185,10 +185,10 @@ test("codebuddy-cn vision flag is set on the visual models", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("getExecutor returns the CodeBuddyCnExecutor for 'codebuddy-cn' and the 'cbcn' alias", () => {
|
||||
const e = getExecutor("codebuddy-cn");
|
||||
test("getExecutor returns the CodeBuddyCnExecutor for 'codebuddy-cn' and the 'cbcn' alias", async () => {
|
||||
const e = await getExecutor("codebuddy-cn");
|
||||
assert.ok(e instanceof CodeBuddyCnExecutor, "executor must be CodeBuddyCnExecutor");
|
||||
const aliasExec = getExecutor("cbcn");
|
||||
const aliasExec = await getExecutor("cbcn");
|
||||
assert.ok(aliasExec instanceof CodeBuddyCnExecutor, "alias 'cbcn' must resolve to same executor");
|
||||
});
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const { refreshAndUpdateCredentials } = await import("../../src/lib/usage/providerLimits.ts");
|
||||
|
||||
test("codex: quota-sync must NOT proactively rotate the refresh_token (Auth0 family-revocation cascade guard)", async () => {
|
||||
const exec = getExecutor("codex");
|
||||
const exec = await getExecutor("codex");
|
||||
const origNeeds = exec.needsRefresh;
|
||||
const origRefresh = exec.refreshCredentials;
|
||||
let refreshCalls = 0;
|
||||
@@ -68,7 +68,7 @@ test("codex: quota-sync must NOT proactively rotate the refresh_token (Auth0 fam
|
||||
});
|
||||
|
||||
test("non-rotating OAuth provider is still refreshed proactively from quota-sync (gate is not over-broad)", async () => {
|
||||
const exec = getExecutor("cursor");
|
||||
const exec = await getExecutor("cursor");
|
||||
const origNeeds = exec.needsRefresh;
|
||||
const origRefresh = exec.refreshCredentials;
|
||||
let refreshCalls = 0;
|
||||
|
||||
@@ -91,15 +91,15 @@ test("Command Code provider catalog has pinned models and alias lookup", () => {
|
||||
assert.equal(getRegistryEntry("cmd"), entry);
|
||||
});
|
||||
|
||||
test("getExecutor returns the specialized Command Code executor", () => {
|
||||
test("getExecutor returns the specialized Command Code executor", async () => {
|
||||
assert.equal(hasSpecializedExecutor("command-code"), true);
|
||||
assert.ok(getExecutor("command-code") instanceof CommandCodeExecutor);
|
||||
assert.ok(getExecutor("cmd") instanceof CommandCodeExecutor);
|
||||
assert.ok((await getExecutor("command-code")) instanceof CommandCodeExecutor);
|
||||
assert.ok((await getExecutor("cmd")) instanceof CommandCodeExecutor);
|
||||
});
|
||||
|
||||
test("Command Code executor posts a flat OpenAI body + standard headers to /provider/v1/chat/completions (#10265)", async () => {
|
||||
const calls = captureFetch({});
|
||||
const executor = getExecutor("command-code");
|
||||
const executor = await getExecutor("command-code");
|
||||
const { response, url, headers } = await executor.execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
@@ -143,7 +143,7 @@ test("Command Code executor posts a flat OpenAI body + standard headers to /prov
|
||||
|
||||
test("Command Code executor passes reasoning/thinking fields through at the top level of the OpenAI body", async () => {
|
||||
const calls = captureFetch({});
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -166,7 +166,7 @@ test("Command Code executor passes reasoning/thinking fields through at the top
|
||||
|
||||
test("Command Code executor honors body.model rewrite from payload rules", async () => {
|
||||
const calls = captureFetch({});
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek-v4-pro-max",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -187,7 +187,7 @@ test("Command Code executor maps unsupported minimal reasoning_effort to low (up
|
||||
const calls = captureFetch({});
|
||||
// `minimal` (a Muse Spark catalog tier) must be downgraded to `low` before
|
||||
// the wire body is built, on BOTH the combo and single-model paths.
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "poolside/laguna-s-2.1-free",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -232,7 +232,7 @@ test("Command Code executor passes the upstream OpenAI SSE stream through untouc
|
||||
});
|
||||
};
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
const { response } = (await getExecutor("command-code")).execute({
|
||||
model: "gpt-5.4",
|
||||
stream: true,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -266,7 +266,7 @@ test("Command Code executor passes the upstream OpenAI JSON through untouched (n
|
||||
});
|
||||
};
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
const { response } = (await getExecutor("command-code")).execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -279,7 +279,7 @@ test("Command Code executor passes the upstream OpenAI JSON through untouched (n
|
||||
|
||||
test("Command Code executor surfaces upstream errors", async () => {
|
||||
globalThis.fetch = async () => new Response("bad key", { status: 401, statusText: "Unauthorized" });
|
||||
const upstreamFailure = await getExecutor("command-code").execute({
|
||||
const upstreamFailure = (await getExecutor("command-code")).execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -291,7 +291,7 @@ test("Command Code executor surfaces upstream errors", async () => {
|
||||
|
||||
test("Command Code executor omits max_tokens when the client does not supply one", async () => {
|
||||
const calls = captureFetch({});
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "zai-org/GLM-5.1",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -305,7 +305,7 @@ test("Command Code executor omits max_tokens when the client does not supply one
|
||||
test("Command Code executor clamps an oversized client-supplied max_tokens to the endpoint ceiling", async () => {
|
||||
const calls = captureFetch({});
|
||||
// A client asking for more than the 200000 endpoint ceiling is clamped down.
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -316,7 +316,7 @@ test("Command Code executor clamps an oversized client-supplied max_tokens to th
|
||||
|
||||
test("Command Code executor honors a smaller client-provided max_tokens", async () => {
|
||||
const calls = captureFetch({});
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "zai-org/GLM-5.1",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -350,7 +350,7 @@ test("Command Code stream preserves the upstream OpenAI usage chunk (passthrough
|
||||
globalThis.fetch = async () =>
|
||||
new Response(sse, { status: 200, headers: { "Content-Type": "text/event-stream" } });
|
||||
|
||||
const { response } = await getExecutor("command-code").execute({
|
||||
const { response } = (await getExecutor("command-code")).execute({
|
||||
model: "gpt-5.4-mini",
|
||||
stream: true,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
|
||||
@@ -40,7 +40,7 @@ async function captureBody(body: Record<string, unknown>): Promise<FetchCall> {
|
||||
calls.push({ url: String(url), init, body: JSON.parse(String(init.body)) });
|
||||
return commandCodeStream([{ type: "text-delta", text: "ok" }, { type: "finish" }]);
|
||||
};
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -73,4 +73,4 @@ test("Command Code omits max_tokens when the client sends 0 (#5166)", async () =
|
||||
test("Command Code still honors a positive client max_tokens after the #5166 fix", async () => {
|
||||
const call = await captureBody({ max_tokens: 2048 });
|
||||
assert.equal(call.body.max_tokens, 2048);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,9 +15,7 @@ import fs from "node:fs";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(
|
||||
path.join(os.tmpdir(), "omniroute-cmd-code-user-array-5166-")
|
||||
);
|
||||
const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-cmd-code-user-array-5166-"));
|
||||
process.env.DATA_DIR = TEST_DATA_DIR;
|
||||
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
@@ -58,7 +56,7 @@ function captureFetch(response: Response) {
|
||||
|
||||
test("#5166 user message with multi-part array content passes through as an OpenAI array", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -86,7 +84,7 @@ test("#5166 user message with multi-part array content passes through as an Open
|
||||
|
||||
test("#5166 user message with single text-part array passes through", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -102,7 +100,7 @@ test("#5166 user message with single text-part array passes through", async () =
|
||||
|
||||
test("#5166 user message with plain string content passes through unchanged", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -114,7 +112,7 @@ test("#5166 user message with plain string content passes through unchanged", as
|
||||
|
||||
test("#5166 user message with mixed parts (text + image_url) keeps all parts", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -135,4 +133,4 @@ test("#5166 user message with mixed parts (text + image_url) keeps all parts", a
|
||||
assert.equal(parts.length, 2, "text + image both preserved");
|
||||
assert.equal(parts[0].text, "Describe this:");
|
||||
assert.equal(parts[1].type, "image_url");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -66,7 +66,7 @@ function wireModel(calls: FetchCall[]): string {
|
||||
|
||||
test("#10809: command-code/mimo-v2.5 wire model is normalized to xiaomi/mimo-v2.5", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "command-code/mimo-v2.5",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -77,7 +77,7 @@ test("#10809: command-code/mimo-v2.5 wire model is normalized to xiaomi/mimo-v2.
|
||||
|
||||
test("#10809: cmd/mimo-v2.5 (alias prefix) is also normalized", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "cmd/mimo-v2.5",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -88,7 +88,7 @@ test("#10809: cmd/mimo-v2.5 (alias prefix) is also normalized", async () => {
|
||||
|
||||
test("#10809: already vendor-prefixed wire ids pass through unchanged", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "command-code/deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -104,7 +104,7 @@ test("#10809: already vendor-prefixed wire ids pass through unchanged", async ()
|
||||
|
||||
test("image_url parts pass through unchanged (text + image preserved)", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "MiniMaxAI/MiniMax-M3",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -125,15 +125,12 @@ test("image_url parts pass through unchanged (text + image preserved)", async ()
|
||||
assert.equal(content.length, 2);
|
||||
assert.equal(content[0].type, "text");
|
||||
assert.equal(content[1].type, "image_url", "image_url part preserved as-is");
|
||||
assert.equal(
|
||||
(content[1].image_url as { url: string }).url,
|
||||
"data:image/png;base64,iVBORw0KGgo="
|
||||
);
|
||||
assert.equal((content[1].image_url as { url: string }).url, "data:image/png;base64,iVBORw0KGgo=");
|
||||
});
|
||||
|
||||
test("Anthropic Messages-style source image blocks pass through unchanged", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "xiaomi/mimo-v2.5",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -161,12 +158,15 @@ test("Anthropic Messages-style source image blocks pass through unchanged", asyn
|
||||
assert.equal(content.length, 2, "text + image parts preserved");
|
||||
assert.equal(content[1].type, "image");
|
||||
assert.equal((content[1].source as { type: string }).type, "base64");
|
||||
assert.equal((content[1].source as { data: string }).data, "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg==");
|
||||
assert.equal(
|
||||
(content[1].source as { data: string }).data,
|
||||
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mP8z8BQDwAEhQGAhKmMIQAAAABJRU5ErkJggg=="
|
||||
);
|
||||
});
|
||||
|
||||
test("Anthropic source.url image block passes through unchanged", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "xiaomi/mimo-v2.5",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -191,7 +191,7 @@ test("Anthropic source.url image block passes through unchanged", async () => {
|
||||
|
||||
test("multiple image parts are all preserved", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "minimax-m3",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -217,7 +217,7 @@ test("multiple image parts are all preserved", async () => {
|
||||
|
||||
test("plain string content passes through unchanged", async () => {
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "minimax-m3",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -233,7 +233,7 @@ test("text-only model still forwards image parts (passthrough, no CLI stripping)
|
||||
// The /provider/v1 OpenAI surface accepts image content for any model id; the
|
||||
// executor forwards content untouched, so there is no text-only stripping.
|
||||
const calls = captureFetch(okResponse());
|
||||
await getExecutor("command-code").execute({
|
||||
(await getExecutor("command-code")).execute({
|
||||
model: "deepseek/deepseek-v4-pro",
|
||||
stream: false,
|
||||
credentials: { apiKey: "cc_test_key" },
|
||||
@@ -253,4 +253,4 @@ test("text-only model still forwards image parts (passthrough, no CLI stripping)
|
||||
const content = userContent(calls) as Record<string, unknown>[];
|
||||
assert.equal(content.length, 2, "content array forwarded unchanged");
|
||||
assert.equal(content[1].type, "image_url");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -15,8 +15,9 @@ const { cursorProvider, cursor_apiProvider } =
|
||||
await import("../../open-sse/config/providers/registry/cursor/index.ts");
|
||||
const { REGISTRY, generateAliasMap, getProviderCategory } =
|
||||
await import("../../open-sse/config/providerRegistry.ts");
|
||||
const { CursorExecutor, getExecutor, hasSpecializedExecutor } =
|
||||
const { getExecutor, hasSpecializedExecutor } =
|
||||
await import("../../open-sse/executors/index.ts");
|
||||
const { CursorExecutor } = await import("../../open-sse/executors/cursor.ts");
|
||||
const { __resetCursorApiKeyAuthForTest } =
|
||||
await import("../../open-sse/services/cursorApiKeyAuth.ts");
|
||||
const { validateProviderApiKey } = await import("../../src/lib/providers/validation.ts");
|
||||
@@ -54,14 +55,14 @@ describe("cursor-api provider wiring", () => {
|
||||
assert.equal(isManagedProviderConnectionId("cursor-api"), true);
|
||||
});
|
||||
|
||||
it("routes cursor-api and its alias to a CursorExecutor bound to the cursor-api id", () => {
|
||||
it("routes cursor-api and its alias to a CursorExecutor bound to the cursor-api id", async () => {
|
||||
for (const key of ["cursor-api", "cua"]) {
|
||||
assert.equal(hasSpecializedExecutor(key), true, key);
|
||||
const executor = getExecutor(key);
|
||||
const executor = await getExecutor(key);
|
||||
assert.ok(executor instanceof CursorExecutor, key);
|
||||
assert.equal(executor.getProvider(), "cursor-api");
|
||||
}
|
||||
assert.equal(getExecutor("cursor").getProvider(), "cursor");
|
||||
assert.equal((await getExecutor("cursor")).getProvider(), "cursor");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -154,6 +155,9 @@ describe("CursorExecutor credential resolution", () => {
|
||||
body: { messages: [] },
|
||||
stream: false,
|
||||
credentials: { apiKey: API_KEY, connectionId: "cursor-api-test" },
|
||||
signal: null,
|
||||
log: null,
|
||||
upstreamExtraHeaders: null,
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 500);
|
||||
|
||||
@@ -18,13 +18,13 @@ test("DeepSeekWebExecutor registered as deepseek-web and ds-web", () => {
|
||||
assert.ok(hasSpecializedExecutor("ds-web"));
|
||||
});
|
||||
|
||||
test("getExecutor returns DeepSeekWebWithAutoRefreshExecutor", () => {
|
||||
const exec = getExecutor("deepseek-web");
|
||||
test("getExecutor returns DeepSeekWebWithAutoRefreshExecutor", async () => {
|
||||
const exec = await getExecutor("deepseek-web");
|
||||
assert.ok(exec instanceof DeepSeekWebWithAutoRefreshExecutor);
|
||||
});
|
||||
|
||||
test("alias ds-web resolves same executor", () => {
|
||||
assert.ok(getExecutor("ds-web") instanceof DeepSeekWebWithAutoRefreshExecutor);
|
||||
test("alias ds-web resolves same executor", async () => {
|
||||
assert.ok(await getExecutor("ds-web") instanceof DeepSeekWebWithAutoRefreshExecutor);
|
||||
});
|
||||
|
||||
test("provider name is deepseek-web", () => {
|
||||
|
||||
@@ -4,6 +4,7 @@ import { readFile } from "node:fs/promises";
|
||||
|
||||
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
|
||||
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { DevinDesktopExecutor } from "../../open-sse/executors/devin-desktop.ts";
|
||||
import { OAUTH_PROVIDERS } from "../../src/shared/constants/providers/oauth.ts";
|
||||
|
||||
test("Devin Desktop exposes the supported BYOK-free catalog", () => {
|
||||
@@ -20,26 +21,29 @@ test("public registries do not expose windsurf or ws aliases", () => {
|
||||
assert.ok(Object.values(REGISTRY).every((entry) => entry.alias !== "ws"));
|
||||
});
|
||||
|
||||
test("executor factory exposes only the dedicated Devin Desktop executor", () => {
|
||||
test("executor factory exposes only the dedicated Devin Desktop executor", async () => {
|
||||
assert.equal(hasSpecializedExecutor("devin-desktop"), true);
|
||||
assert.equal(hasSpecializedExecutor("windsurf"), false);
|
||||
assert.equal(hasSpecializedExecutor("ws"), false);
|
||||
assert.equal(getExecutor("devin-desktop").constructor.name, "DevinDesktopExecutor");
|
||||
assert.equal((await getExecutor("devin-desktop")).constructor.name, "DevinDesktopExecutor");
|
||||
});
|
||||
|
||||
test("Devin Desktop executor uses the live endpoint and verified default identity", () => {
|
||||
const executor = getExecutor("devin-desktop");
|
||||
test("Devin Desktop executor uses the live endpoint and verified default identity", async () => {
|
||||
const executor = await getExecutor("devin-desktop");
|
||||
delete process.env.DEVIN_DESKTOP_VERSION;
|
||||
|
||||
// getExecutor() widens to BaseExecutor whose buildUrl requires args; the concrete
|
||||
// DevinDesktopExecutor override takes none.
|
||||
const desktop = executor as DevinDesktopExecutor;
|
||||
assert.equal(
|
||||
executor.buildUrl(),
|
||||
desktop.buildUrl(),
|
||||
"https://server.codeium.com/exa.api_server_pb.ApiServerService/GetChatMessage"
|
||||
);
|
||||
assert.equal(executor.buildHeaders({ accessToken: "token" })["User-Agent"], "windsurf/3.6.27");
|
||||
});
|
||||
|
||||
test("Devin Desktop executor applies only valid version overrides to its user agent", () => {
|
||||
const executor = getExecutor("devin-desktop");
|
||||
test("Devin Desktop executor applies only valid version overrides to its user agent", async () => {
|
||||
const executor = await getExecutor("devin-desktop");
|
||||
process.env.DEVIN_DESKTOP_VERSION = "3.5.1";
|
||||
try {
|
||||
assert.equal(executor.buildHeaders({ accessToken: "token" })["User-Agent"], "windsurf/3.5.1");
|
||||
@@ -51,7 +55,7 @@ test("Devin Desktop executor applies only valid version overrides to its user ag
|
||||
});
|
||||
|
||||
test("Devin Desktop executor returns 401 before the upstream call without a token", async () => {
|
||||
const executor = getExecutor("devin-desktop");
|
||||
const executor = await getExecutor("devin-desktop");
|
||||
const originalFetch = globalThis.fetch;
|
||||
let fetchCalled = false;
|
||||
globalThis.fetch = async () => {
|
||||
@@ -67,16 +71,17 @@ test("Devin Desktop executor returns 401 before the upstream call without a toke
|
||||
credentials: {},
|
||||
});
|
||||
|
||||
const response = result instanceof Response ? result : result.response;
|
||||
assert.equal(fetchCalled, false);
|
||||
assert.equal(result.response.status, 401);
|
||||
assert.match(await result.response.text(), /Devin Desktop API key is required/);
|
||||
assert.equal(response.status, 401);
|
||||
assert.match(await response.text(), /Devin Desktop API key is required/);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
test("Devin Desktop stream errors do not expose local paths or stack traces", async () => {
|
||||
const executor = getExecutor("devin-desktop");
|
||||
const executor = await getExecutor("devin-desktop");
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async () =>
|
||||
new Response(
|
||||
@@ -95,7 +100,7 @@ test("Devin Desktop stream errors do not expose local paths or stack traces", as
|
||||
stream: true,
|
||||
credentials: { accessToken: "test-token" },
|
||||
});
|
||||
const text = await result.response.text();
|
||||
const text = await (result instanceof Response ? result : result.response).text();
|
||||
|
||||
assert.match(text, /stream failed/);
|
||||
assert.doesNotMatch(text, /private\.ts|\/Users\/example|\bat\s+\//);
|
||||
|
||||
@@ -241,7 +241,7 @@ describe("DuckDuckGoWebExecutor", () => {
|
||||
|
||||
it("should be registered in executor index", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const executor = getExecutor("duckduckgo-web");
|
||||
const executor = await getExecutor("duckduckgo-web");
|
||||
assert.ok(executor, "executor should be registered in index");
|
||||
assert.equal(
|
||||
typeof executor.execute,
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { getExecutor, AntigravityExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { processAntigravitySSEPayload } from "../../open-sse/executors/antigravity.ts";
|
||||
import { getExecutor } from "../../open-sse/executors/index.ts";
|
||||
import {
|
||||
AntigravityExecutor,
|
||||
processAntigravitySSEPayload,
|
||||
} from "../../open-sse/executors/antigravity.ts";
|
||||
|
||||
function emptyCollected(): any {
|
||||
return {
|
||||
@@ -14,21 +17,21 @@ function emptyCollected(): any {
|
||||
};
|
||||
}
|
||||
|
||||
test("getExecutor('agy') returns AntigravityExecutor (not DefaultExecutor)", () => {
|
||||
const executor = getExecutor("agy");
|
||||
test("getExecutor('agy') returns AntigravityExecutor (not DefaultExecutor)", async () => {
|
||||
const executor = await getExecutor("agy");
|
||||
assert.ok(executor instanceof AntigravityExecutor, "agy provider should use AntigravityExecutor");
|
||||
});
|
||||
|
||||
test("getExecutor('antigravity') returns AntigravityExecutor", () => {
|
||||
const executor = getExecutor("antigravity");
|
||||
test("getExecutor('antigravity') returns AntigravityExecutor", async () => {
|
||||
const executor = await getExecutor("antigravity");
|
||||
assert.ok(
|
||||
executor instanceof AntigravityExecutor,
|
||||
"antigravity provider should use AntigravityExecutor"
|
||||
);
|
||||
});
|
||||
|
||||
test("getExecutor('agy') builds valid streaming URL", () => {
|
||||
const executor = getExecutor("agy");
|
||||
test("getExecutor('agy') builds valid streaming URL", async () => {
|
||||
const executor = await getExecutor("agy");
|
||||
const url = executor.buildUrl("gemini-3.7-flash-high", true);
|
||||
assert.ok(
|
||||
url.includes("streamGenerateContent?alt=sse"),
|
||||
@@ -36,8 +39,8 @@ test("getExecutor('agy') builds valid streaming URL", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getExecutor('agy') builds valid non-streaming URL", () => {
|
||||
const executor = getExecutor("agy");
|
||||
test("getExecutor('agy') builds valid non-streaming URL", async () => {
|
||||
const executor = await getExecutor("agy");
|
||||
const url = executor.buildUrl("gemini-3.7-flash-high", false);
|
||||
// Antigravity executor always uses streaming endpoint (buildUrl ignores stream flag)
|
||||
assert.ok(
|
||||
@@ -46,8 +49,8 @@ test("getExecutor('agy') builds valid non-streaming URL", () => {
|
||||
);
|
||||
});
|
||||
|
||||
test("getExecutor('agy') buildHeaders returns Bearer auth", () => {
|
||||
const executor = getExecutor("agy");
|
||||
test("getExecutor('agy') buildHeaders returns Bearer auth", async () => {
|
||||
const executor = await getExecutor("agy");
|
||||
const headers = executor.buildHeaders({ accessToken: "test-token" });
|
||||
assert.equal(headers.Authorization, "Bearer test-token");
|
||||
});
|
||||
|
||||
@@ -19,11 +19,11 @@ function jsonResponse(body: unknown, status = 200) {
|
||||
});
|
||||
}
|
||||
|
||||
test("GitlabExecutor is registered in the executor index", () => {
|
||||
test("GitlabExecutor is registered in the executor index", async () => {
|
||||
assert.equal(hasSpecializedExecutor("gitlab"), true);
|
||||
assert.ok(getExecutor("gitlab") instanceof GitlabExecutor);
|
||||
assert.ok((await getExecutor("gitlab")) instanceof GitlabExecutor);
|
||||
assert.equal(hasSpecializedExecutor("gitlab-duo"), true);
|
||||
assert.ok(getExecutor("gitlab-duo") instanceof GitlabExecutor);
|
||||
assert.ok((await getExecutor("gitlab-duo")) instanceof GitlabExecutor);
|
||||
});
|
||||
|
||||
test("GitlabExecutor posts PAT-backed code suggestion requests to the configured instance", async () => {
|
||||
@@ -147,7 +147,7 @@ test("GitlabExecutor maps upstream auth failures to OpenAI-style errors", async
|
||||
});
|
||||
|
||||
test("GitlabExecutor uses GitLab direct_access for gitlab-duo and persists the cache", async () => {
|
||||
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
|
||||
const executor = (await getExecutor("gitlab-duo")) as GitlabExecutor;
|
||||
const originalFetch = globalThis.fetch;
|
||||
const calls: Array<{ url: string; headers: Record<string, string> }> = [];
|
||||
const refreshedPatches: Array<Record<string, unknown>> = [];
|
||||
@@ -223,7 +223,7 @@ test("GitlabExecutor uses GitLab direct_access for gitlab-duo and persists the c
|
||||
});
|
||||
|
||||
test("GitlabExecutor falls back to the public Code Suggestions endpoint when direct_access is disabled", async () => {
|
||||
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
|
||||
const executor = (await getExecutor("gitlab-duo")) as GitlabExecutor;
|
||||
const originalFetch = globalThis.fetch;
|
||||
const calls: string[] = [];
|
||||
|
||||
@@ -274,7 +274,7 @@ test("GitlabExecutor falls back to the public Code Suggestions endpoint when dir
|
||||
// Code Suggestions completions endpoint (same resilience as the 403-disabled case
|
||||
// above), instead of surfacing an opaque 401 token error with no fallback.
|
||||
test("GitlabExecutor falls back to the public Code Suggestions endpoint when direct_access returns 401", async () => {
|
||||
const executor = getExecutor("gitlab-duo") as GitlabExecutor;
|
||||
const executor = (await getExecutor("gitlab-duo")) as GitlabExecutor;
|
||||
const originalFetch = globalThis.fetch;
|
||||
const calls: string[] = [];
|
||||
|
||||
|
||||
@@ -47,10 +47,10 @@ function credentials(
|
||||
}
|
||||
|
||||
describe("KimiExecutor", () => {
|
||||
it("forces the primary Kimi upstream to stream while preserving JSON client semantics", () => {
|
||||
it("forces the primary Kimi upstream to stream while preserving JSON client semantics", async () => {
|
||||
assert.equal(REGISTRY.kimi?.forceStream, true);
|
||||
|
||||
const executor = getExecutor("kimi");
|
||||
const executor = await getExecutor("kimi");
|
||||
assert.ok(executor instanceof MoonshotExecutor);
|
||||
assert.equal(
|
||||
executor.buildUrl("kimi-k2.5", true, 0, credentials(FORMATS.OPENAI)),
|
||||
|
||||
@@ -37,11 +37,11 @@ function readSpecializedKeys(): string[] {
|
||||
path.resolve(path.dirname(new URL(import.meta.url).pathname), "../../open-sse/executors/index.ts"),
|
||||
"utf8"
|
||||
);
|
||||
const mapMatch = src.match(/const executors = \{([\s\S]*?)\n\};/);
|
||||
assert.ok(mapMatch, "executors map literal not found in open-sse/executors/index.ts");
|
||||
const mapMatch = src.match(/const lazyExecutors[^\n]*= \{([\s\S]*?)\n\};/);
|
||||
assert.ok(mapMatch, "lazyExecutors map literal not found in open-sse/executors/index.ts");
|
||||
const keys: string[] = [];
|
||||
for (const line of mapMatch[1].split("\n")) {
|
||||
const m = line.match(/^\s*(?:"([^"]+)"|([A-Za-z0-9_$-]+)):\s*new /);
|
||||
const m = line.match(/^\s*(?:"([^"]+)"|([A-Za-z0-9_$-]+)):\s*(?:async )?\(\)\s*=>/);
|
||||
if (m) keys.push(m[1] ?? m[2]);
|
||||
}
|
||||
return keys;
|
||||
@@ -74,7 +74,7 @@ function describeExecutor(instance: unknown): {
|
||||
|
||||
const specializedKeys = readSpecializedKeys();
|
||||
|
||||
test("golden: specialized executor map — key → class + provider identity + config source", () => {
|
||||
test("golden: specialized executor map — key → class + provider identity + config source", async () => {
|
||||
assert.ok(specializedKeys.length >= 100, `suspiciously few keys: ${specializedKeys.length}`);
|
||||
|
||||
const entries: Record<
|
||||
@@ -85,7 +85,7 @@ test("golden: specialized executor map — key → class + provider identity + c
|
||||
|
||||
for (const key of [...specializedKeys].sort()) {
|
||||
assert.equal(hasSpecializedExecutor(key), true, `hasSpecializedExecutor(${key})`);
|
||||
const instance = getExecutor(key);
|
||||
const instance = await getExecutor(key);
|
||||
entries[key] = describeExecutor(instance);
|
||||
const group = byInstance.get(instance) ?? [];
|
||||
group.push(key);
|
||||
@@ -106,18 +106,18 @@ test("golden: specialized executor map — key → class + provider identity + c
|
||||
});
|
||||
});
|
||||
|
||||
test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", () => {
|
||||
test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", async () => {
|
||||
// 1. Unknown provider → DefaultExecutor for that provider, memoized.
|
||||
const unknown = "golden-test-unknown-provider";
|
||||
assert.equal(hasSpecializedExecutor(unknown), false);
|
||||
const fallback = getExecutor(unknown);
|
||||
const fallback = await getExecutor(unknown);
|
||||
assert.ok(fallback instanceof DefaultExecutor, "fallback must be DefaultExecutor");
|
||||
assert.equal(getExecutor(unknown), fallback, "DefaultExecutor fallback must be cached");
|
||||
assert.equal(await getExecutor(unknown), fallback, "DefaultExecutor fallback must be cached");
|
||||
|
||||
// 2. Cloud-agent guard (#6699) and search guard (#10274) → status-400 throw.
|
||||
const guardOutcome = (provider: string) => {
|
||||
const guardOutcome = async (provider: string) => {
|
||||
try {
|
||||
getExecutor(provider);
|
||||
await getExecutor(provider);
|
||||
return { throws: false as const };
|
||||
} catch (err) {
|
||||
const e = err as Error & { status?: number };
|
||||
@@ -128,7 +128,9 @@ test("golden: getExecutor dispatch rules — fallback, cache and 400-guards", ()
|
||||
const searchProviders = Object.keys(SEARCH_PROVIDERS).sort();
|
||||
goldenSnapshot("executors/dispatch-rules", {
|
||||
fallback: describeExecutor(fallback),
|
||||
cloudAgentGuard: { jules: guardOutcome("jules") },
|
||||
searchGuard: Object.fromEntries(searchProviders.map((p) => [p, guardOutcome(p)])),
|
||||
cloudAgentGuard: { jules: await guardOutcome("jules") },
|
||||
searchGuard: Object.fromEntries(
|
||||
await Promise.all(searchProviders.map(async (p) => [p, await guardOutcome(p)]))
|
||||
),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,9 +30,9 @@ function sseResponse(events: string[]) {
|
||||
);
|
||||
}
|
||||
|
||||
test("NlpCloudExecutor is registered in the executor index", () => {
|
||||
test("NlpCloudExecutor is registered in the executor index", async () => {
|
||||
assert.equal(hasSpecializedExecutor("nlpcloud"), true);
|
||||
assert.ok(getExecutor("nlpcloud") instanceof NlpCloudExecutor);
|
||||
assert.ok((await getExecutor("nlpcloud")) instanceof NlpCloudExecutor);
|
||||
});
|
||||
|
||||
test.skip("NlpCloudExecutor converts OpenAI messages into chatbot input/context/history and wraps JSON responses", async () => {
|
||||
|
||||
@@ -21,13 +21,13 @@ test.after(() => {
|
||||
fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true });
|
||||
});
|
||||
|
||||
test("built-ins are registered at module load and resolve through the registry", () => {
|
||||
test("built-ins are registered at module load and resolve through the registry", async () => {
|
||||
const aliases = listExecutorAliases();
|
||||
assert.ok(aliases.length >= 100, `expected the built-in table, got ${aliases.length} aliases`);
|
||||
for (const alias of ["antigravity", "kiro", "glm", "9router", "conol-web"]) {
|
||||
assert.ok(hasRegisteredExecutor(alias), `missing built-in: ${alias}`);
|
||||
assert.equal(getExecutor(alias), getRegisteredExecutor(alias));
|
||||
assert.ok(getExecutor(alias) instanceof BaseExecutor);
|
||||
assert.equal(await getExecutor(alias), getRegisteredExecutor(alias));
|
||||
assert.ok((await getExecutor(alias)) instanceof BaseExecutor);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -37,21 +37,21 @@ test("registerExecutor throws on duplicate alias", () => {
|
||||
});
|
||||
});
|
||||
|
||||
test("registering a new alias makes it resolvable via getExecutor and hasSpecializedExecutor", () => {
|
||||
test("registering a new alias makes it resolvable via getExecutor and hasSpecializedExecutor", async () => {
|
||||
const alias = "registry-test-provider";
|
||||
assert.equal(hasSpecializedExecutor(alias), false);
|
||||
const instance = new DefaultExecutor(alias);
|
||||
registerExecutor(alias, instance);
|
||||
assert.equal(hasSpecializedExecutor(alias), true);
|
||||
assert.equal(getExecutor(alias), instance);
|
||||
assert.equal(await getExecutor(alias), instance);
|
||||
});
|
||||
|
||||
test("registry lookup is exact — Object.prototype names are not executors", () => {
|
||||
test("registry lookup is exact — Object.prototype names are not executors", async () => {
|
||||
// The old object-literal lookup (`executors[provider]`) leaked prototype
|
||||
// members: getExecutor("constructor") returned Object's constructor. The Map
|
||||
// registry must treat these as unknown providers (DefaultExecutor fallback).
|
||||
for (const name of ["constructor", "toString", "hasOwnProperty", "__proto__"]) {
|
||||
assert.equal(hasSpecializedExecutor(name), false, name);
|
||||
assert.ok(getExecutor(name) instanceof DefaultExecutor, name);
|
||||
assert.ok((await getExecutor(name)) instanceof DefaultExecutor, name);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -119,7 +119,7 @@ describe("web-cookie + noauth executor wrapper contract sweep", () => {
|
||||
describe("WEB_COOKIE_PROVIDERS (26)", () => {
|
||||
for (const providerId of WEB_COOKIE_IDS) {
|
||||
it(`${providerId} executor returns wrapper shape`, async () => {
|
||||
const executor = getExecutor(providerId);
|
||||
const executor = await getExecutor(providerId);
|
||||
assert.ok(executor, `[${providerId}] getExecutor must return an executor`);
|
||||
|
||||
const result = await executor.execute({
|
||||
@@ -160,7 +160,7 @@ describe("web-cookie + noauth executor wrapper contract sweep", () => {
|
||||
|
||||
for (const providerId of TARGETS) {
|
||||
it(`${providerId} noauth executor returns wrapper shape`, async () => {
|
||||
const executor = getExecutor(providerId);
|
||||
const executor = await getExecutor(providerId);
|
||||
assert.ok(executor, `[${providerId}] getExecutor must return an executor`);
|
||||
|
||||
// Use a pre-aborted signal so the executor short-circuits via
|
||||
|
||||
@@ -15,9 +15,9 @@ import { xaiProvider } from "../../open-sse/config/providers/registry/xai/index.
|
||||
|
||||
const credentials = { apiKey: "test-key" };
|
||||
|
||||
test("XaiExecutor is registered under the 'xai' key and set as the registry executor", () => {
|
||||
test("XaiExecutor is registered under the 'xai' key and set as the registry executor", async () => {
|
||||
assert.equal(hasSpecializedExecutor("xai"), true);
|
||||
assert.ok(getExecutor("xai") instanceof XaiExecutor);
|
||||
assert.ok((await getExecutor("xai")) instanceof XaiExecutor);
|
||||
assert.equal(xaiProvider.executor, "xai");
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ const providers = [
|
||||
] as const;
|
||||
|
||||
for (const [id, alias, endpoint] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, async () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
@@ -35,7 +35,7 @@ for (const [id, alias, endpoint] of providers) {
|
||||
assert.equal(metadata.hasFree, true);
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.ok((await getExecutor(id)) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.equal(isValidModel(alias, "future/live-catalog-model"), true);
|
||||
});
|
||||
|
||||
@@ -25,7 +25,7 @@ const providers = [
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint, modelIds] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, async () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
@@ -41,7 +41,7 @@ for (const [id, endpoint, modelIds] of providers) {
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
assert.ok((await getExecutor(id)) instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.deepEqual(
|
||||
registry.models.map((model) => model.id),
|
||||
|
||||
@@ -20,7 +20,7 @@ const providers = [
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint] of providers) {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, () => {
|
||||
test(`${id} is wired through registry, metadata, endpoint and default executor`, async () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
@@ -36,7 +36,8 @@ for (const [id, endpoint] of providers) {
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.ok(typeof metadata.freeNote === "string" && metadata.freeNote.length > 0);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.ok(getExecutor(id) instanceof DefaultExecutor);
|
||||
const executor = await getExecutor(id);
|
||||
assert.ok(executor instanceof DefaultExecutor);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
assert.deepEqual(registry.models, []);
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ test("FreeInference exposes an OpenAI-compatible Bearer registry", () => {
|
||||
assert.equal(freeinferenceProvider.passthroughModels, true);
|
||||
});
|
||||
|
||||
test("FreeInference uses DefaultExecutor without specialized behavior", () => {
|
||||
test("FreeInference uses DefaultExecutor without specialized behavior", async () => {
|
||||
assert.equal(hasSpecializedExecutor("freeinference"), false);
|
||||
assert.ok(getExecutor("freeinference") instanceof DefaultExecutor);
|
||||
assert.ok(await getExecutor("freeinference") instanceof DefaultExecutor);
|
||||
});
|
||||
|
||||
@@ -21,7 +21,7 @@ test("Free.ai exposes its exact OpenAI-compatible endpoint and live catalog", ()
|
||||
assert.equal(freeAiProvider.passthroughModels, true);
|
||||
});
|
||||
|
||||
test("Free.ai uses DefaultExecutor without a specialized executor", () => {
|
||||
assert.ok(getExecutor("free-ai") instanceof DefaultExecutor);
|
||||
test("Free.ai uses DefaultExecutor without a specialized executor", async () => {
|
||||
assert.ok(await getExecutor("free-ai") instanceof DefaultExecutor);
|
||||
assert.equal(hasSpecializedExecutor("free-ai"), false);
|
||||
});
|
||||
|
||||
@@ -17,7 +17,7 @@ const providers = [
|
||||
] as const;
|
||||
|
||||
for (const [id, endpoint] of providers) {
|
||||
test(`${id} is fully wired without a specialized executor`, () => {
|
||||
test(`${id} is fully wired without a specialized executor`, async () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
@@ -33,7 +33,7 @@ for (const [id, endpoint] of providers) {
|
||||
assert.equal(metadata.passthroughModels, true);
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.equal(hasSpecializedExecutor(id), false);
|
||||
const executor = getExecutor(id);
|
||||
const executor = await getExecutor(id);
|
||||
assert.ok(executor instanceof DefaultExecutor);
|
||||
assert.equal(executor.buildUrl("live-model", false), endpoint);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
|
||||
@@ -27,7 +27,7 @@ const providers = [
|
||||
] as const;
|
||||
|
||||
for (const { id, endpoint, modelsUrl, hasFree } of providers) {
|
||||
test(`${id} is fully wired through the public provider interfaces`, () => {
|
||||
test(`${id} is fully wired through the public provider interfaces`, async () => {
|
||||
const registry = REGISTRY[id];
|
||||
const metadata = APIKEY_PROVIDERS[id];
|
||||
|
||||
@@ -52,7 +52,7 @@ for (const { id, endpoint, modelsUrl, hasFree } of providers) {
|
||||
assert.equal(AGGREGATOR_PROVIDER_IDS.has(id), true);
|
||||
assert.equal(hasSpecializedExecutor(id), false);
|
||||
|
||||
const executor = getExecutor(id);
|
||||
const executor = await getExecutor(id);
|
||||
assert.ok(executor instanceof DefaultExecutor);
|
||||
assert.equal(executor.buildUrl("live-model", false), endpoint);
|
||||
assert.equal(isValidModel(id, "future/live-catalog-model"), true);
|
||||
|
||||
@@ -73,8 +73,8 @@ for (const [id, subPath] of Object.entries(SUB_PATHS)) {
|
||||
);
|
||||
});
|
||||
|
||||
test(`#6650 ${id} resolves through getExecutor() as a DefaultExecutor instance`, () => {
|
||||
const executor = getExecutor(id);
|
||||
test(`#6650 ${id} resolves through getExecutor() as a DefaultExecutor instance`, async () => {
|
||||
const executor = await getExecutor(id);
|
||||
assert.ok(
|
||||
executor instanceof DefaultExecutor,
|
||||
`${id} has no custom executor — must fall through to DefaultExecutor`
|
||||
|
||||
@@ -7,9 +7,9 @@ const { getExecutor, hasSpecializedExecutor } = await import("../../open-sse/exe
|
||||
|
||||
// ─── Registration ───────────────────────────────────────────────────────────
|
||||
|
||||
test("GeminiWebExecutor is registered in executor index", () => {
|
||||
test("GeminiWebExecutor is registered in executor index", async () => {
|
||||
assert.ok(hasSpecializedExecutor("gemini-web"));
|
||||
const executor = getExecutor("gemini-web");
|
||||
const executor = await getExecutor("gemini-web");
|
||||
assert.ok(executor instanceof GeminiWebExecutor);
|
||||
});
|
||||
|
||||
|
||||
@@ -129,10 +129,10 @@ test("GlmExecutor normalizes GLM coding and Anthropic URLs without duplicating e
|
||||
);
|
||||
});
|
||||
|
||||
test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic headers", () => {
|
||||
assert.equal(getExecutor("glm") instanceof GlmExecutor, true);
|
||||
assert.equal(getExecutor("glm-cn") instanceof GlmExecutor, true);
|
||||
assert.equal(getExecutor("glmt") instanceof GlmExecutor, true);
|
||||
test("GlmExecutor separates OpenAI-compatible coding headers from Anthropic headers", async () => {
|
||||
assert.equal(await getExecutor("glm") instanceof GlmExecutor, true);
|
||||
assert.equal(await getExecutor("glm-cn") instanceof GlmExecutor, true);
|
||||
assert.equal(await getExecutor("glmt") instanceof GlmExecutor, true);
|
||||
|
||||
const executor = new GlmExecutor("glm");
|
||||
const codingHeaders = executor.buildHeaders(
|
||||
|
||||
@@ -83,9 +83,9 @@ test.afterEach(() => {
|
||||
|
||||
// ─── Registration ───────────────────────────────────────────────────────────
|
||||
|
||||
test("GrokWebExecutor is registered in executor index", () => {
|
||||
test("GrokWebExecutor is registered in executor index", async () => {
|
||||
assert.ok(hasSpecializedExecutor("grok-web"));
|
||||
const executor = getExecutor("grok-web");
|
||||
const executor = await getExecutor("grok-web");
|
||||
assert.ok(executor instanceof GrokWebExecutor);
|
||||
});
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import { DefaultExecutor } from "../../open-sse/executors/default.ts";
|
||||
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { KieExecutor } from "../../open-sse/executors/kie.ts";
|
||||
|
||||
test("KIE chat traffic uses the default executor while media keeps its task executor", () => {
|
||||
test("KIE chat traffic uses the default executor while media keeps its task executor", async () => {
|
||||
assert.equal(hasSpecializedExecutor("kie"), false);
|
||||
assert.ok(getExecutor("kie") instanceof DefaultExecutor);
|
||||
assert.ok(await getExecutor("kie") instanceof DefaultExecutor);
|
||||
assert.equal(typeof KieExecutor, "function");
|
||||
});
|
||||
|
||||
@@ -4,11 +4,8 @@ import assert from "node:assert/strict";
|
||||
import { getRegistryEntry } from "../../open-sse/config/providerRegistry.ts";
|
||||
import { supportsXHighEffort } from "../../open-sse/config/providerModels.ts";
|
||||
import { sanitizeReasoningEffortForProvider } from "../../open-sse/executors/base.ts";
|
||||
import {
|
||||
getExecutor,
|
||||
hasSpecializedExecutor,
|
||||
MoonshotExecutor,
|
||||
} from "../../open-sse/executors/index.ts";
|
||||
import { getExecutor, hasSpecializedExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { MoonshotExecutor } from "../../open-sse/executors/moonshot.ts";
|
||||
import {
|
||||
sanitizeOpenAIResponse,
|
||||
sanitizeResponsesApiResponse,
|
||||
@@ -63,11 +60,11 @@ test("Kimi K3 advertises its 1M context/output and native capabilities", () => {
|
||||
assert.equal(capabilities.interleavedField, "reasoning_content");
|
||||
});
|
||||
|
||||
test("Moonshot ids use the specialized request normalizer", () => {
|
||||
test("Moonshot ids use the specialized request normalizer", async () => {
|
||||
assert.equal(hasSpecializedExecutor("moonshot"), true);
|
||||
assert.equal(hasSpecializedExecutor("kimi"), true);
|
||||
assert.ok(getExecutor("moonshot") instanceof MoonshotExecutor);
|
||||
assert.ok(getExecutor("kimi") instanceof MoonshotExecutor);
|
||||
assert.ok((await getExecutor("moonshot")) instanceof MoonshotExecutor);
|
||||
assert.ok((await getExecutor("kimi")) instanceof MoonshotExecutor);
|
||||
});
|
||||
|
||||
test("Kimi K3 uses max reasoning, fixed sampling, and max_completion_tokens", () => {
|
||||
|
||||
@@ -498,13 +498,13 @@ describe("NineRouterExecutor", () => {
|
||||
describe("getExecutor registration", () => {
|
||||
it("getExecutor('9router') returns a NineRouterExecutor", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const exec = getExecutor("9router");
|
||||
const exec = await getExecutor("9router");
|
||||
assert.equal(exec.getProvider(), "9router");
|
||||
});
|
||||
|
||||
it("getExecutor('nr') alias resolves to NineRouterExecutor", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const exec = getExecutor("nr");
|
||||
const exec = await getExecutor("nr");
|
||||
assert.equal(exec.getProvider(), "9router");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,8 +107,7 @@ test("Openference OAuth postExchange fetches userinfo when id_token lacks email"
|
||||
assert.equal(mapped.email, "from-userinfo@openference.com");
|
||||
assert.equal(mapped.name, "Userinfo Name");
|
||||
});
|
||||
|
||||
test("Openference is registered as an OAuth gateway with default executor", () => {
|
||||
test("Openference is registered as an OAuth gateway with default executor", async () => {
|
||||
assert.ok(OAUTH_PROVIDERS.openference);
|
||||
assert.equal(OAUTH_PROVIDERS.openference.alias, "of");
|
||||
assert.equal(OAUTH_PROVIDERS.openference.color, "#6366F1");
|
||||
@@ -129,7 +128,7 @@ test("Openference is registered as an OAuth gateway with default executor", () =
|
||||
);
|
||||
assert.equal(hasSpecializedExecutor("openference"), false);
|
||||
|
||||
const headers = getExecutor("openference").buildHeaders({ accessToken: "oauth-access" }, false);
|
||||
const headers = (await getExecutor("openference")).buildHeaders({ accessToken: "oauth-access" }, false);
|
||||
assert.equal(headers.Authorization, "Bearer oauth-access");
|
||||
});
|
||||
|
||||
|
||||
@@ -91,16 +91,16 @@ function mockFetchError(error) {
|
||||
|
||||
// ─── Test: Executor registration ────────────────────────────────────────────
|
||||
|
||||
test("PerplexityWebExecutor is registered in executor index", () => {
|
||||
test("PerplexityWebExecutor is registered in executor index", async () => {
|
||||
assert.ok(hasSpecializedExecutor("perplexity-web"));
|
||||
assert.ok(hasSpecializedExecutor("pplx-web"));
|
||||
const executor = getExecutor("perplexity-web");
|
||||
const executor = await getExecutor("perplexity-web");
|
||||
assert.ok(executor instanceof PerplexityWebExecutor);
|
||||
});
|
||||
|
||||
test("PerplexityWebExecutor alias resolves to same type", () => {
|
||||
const a = getExecutor("perplexity-web");
|
||||
const b = getExecutor("pplx-web");
|
||||
test("PerplexityWebExecutor alias resolves to same type", async () => {
|
||||
const a = await getExecutor("perplexity-web");
|
||||
const b = await getExecutor("pplx-web");
|
||||
assert.ok(a instanceof PerplexityWebExecutor);
|
||||
assert.ok(b instanceof PerplexityWebExecutor);
|
||||
});
|
||||
|
||||
@@ -47,17 +47,18 @@ function headerRecord(headers: Record<string, string>): Record<string, string> {
|
||||
return out;
|
||||
}
|
||||
|
||||
test("#8969: getExecutor(poe) selects DefaultExecutor, not PoeWebExecutor", () => {
|
||||
test("#8969: getExecutor(poe) selects DefaultExecutor, not PoeWebExecutor", async () => {
|
||||
assert.equal(hasSpecializedExecutor("poe"), false);
|
||||
const executor = getExecutor("poe");
|
||||
const executor = await getExecutor("poe");
|
||||
assert.ok(executor instanceof DefaultExecutor);
|
||||
assert.equal(executor instanceof PoeWebExecutor, false);
|
||||
assert.equal(executor.provider, "poe");
|
||||
});
|
||||
|
||||
test("#8969: getExecutor(poe-web) still selects PoeWebExecutor", () => {
|
||||
test("#8969: getExecutor(poe-web) still selects PoeWebExecutor", async () => {
|
||||
assert.equal(hasSpecializedExecutor("poe-web"), true);
|
||||
assert.ok(getExecutor("poe-web") instanceof PoeWebExecutor);
|
||||
const executor = await getExecutor("poe-web");
|
||||
assert.ok(executor instanceof PoeWebExecutor);
|
||||
});
|
||||
|
||||
test("#8969: registry declares API-key executor + all three Poe protocol URLs", () => {
|
||||
@@ -80,8 +81,8 @@ test("#8969: registry declares API-key executor + all three Poe protocol URLs",
|
||||
assert.notEqual(gpt.targetFormat, "claude");
|
||||
});
|
||||
|
||||
test("#8969: buildUrl routes chat / responses / messages correctly", () => {
|
||||
const executor = getExecutor("poe") as DefaultExecutor;
|
||||
test("#8969: buildUrl routes chat / responses / messages correctly", async () => {
|
||||
const executor = (await getExecutor("poe")) as DefaultExecutor;
|
||||
const creds = { apiKey: "poe-test-key", providerSpecificData: {} };
|
||||
|
||||
assert.equal(executor.buildUrl("gemma-4-31b", false, 0, creds), CHAT_URL);
|
||||
@@ -175,8 +176,8 @@ test("#8969: resolvePoeUpstreamUrl normalizes registry-default / bare-host /v1/
|
||||
}
|
||||
});
|
||||
|
||||
test("#8969: buildHeaders uses Bearer auth and never sends Cookie", () => {
|
||||
const executor = getExecutor("poe") as DefaultExecutor;
|
||||
test("#8969: buildHeaders uses Bearer auth and never sends Cookie", async () => {
|
||||
const executor = (await getExecutor("poe")) as DefaultExecutor;
|
||||
for (const stream of [false, true]) {
|
||||
const headers = headerRecord(
|
||||
executor.buildHeaders({ apiKey: "poe-test-key", providerSpecificData: {} }, stream)
|
||||
@@ -200,7 +201,7 @@ test("#8969: resolveExecutionCredentials forces responses upstream for poe", ()
|
||||
});
|
||||
|
||||
test("#8969: mocked execute posts Chat Completions with Bearer, no Cookie, stripped model", async () => {
|
||||
const executor = getExecutor("poe") as DefaultExecutor;
|
||||
const executor = (await getExecutor("poe")) as DefaultExecutor;
|
||||
const originalFetch = globalThis.fetch;
|
||||
const seen: Array<{
|
||||
url: string;
|
||||
@@ -259,7 +260,7 @@ test("#8969: mocked execute posts Chat Completions with Bearer, no Cookie, strip
|
||||
});
|
||||
|
||||
test("#8969: mocked execute routes Responses + Messages fixtures to the right URLs", async () => {
|
||||
const executor = getExecutor("poe") as DefaultExecutor;
|
||||
const executor = (await getExecutor("poe")) as DefaultExecutor;
|
||||
const originalFetch = globalThis.fetch;
|
||||
let lastUrl = "";
|
||||
|
||||
@@ -326,7 +327,7 @@ test("#8969: mocked execute routes Responses + Messages fixtures to the right UR
|
||||
});
|
||||
|
||||
test("#8969: mocked upstream 405 is preserved (not swallowed)", async () => {
|
||||
const executor = getExecutor("poe") as DefaultExecutor;
|
||||
const executor = (await getExecutor("poe")) as DefaultExecutor;
|
||||
const originalFetch = globalThis.fetch;
|
||||
|
||||
globalThis.fetch = (async () => {
|
||||
|
||||
@@ -16,7 +16,7 @@ test("#6699: jules has no specialized executor (falls through to DefaultExecutor
|
||||
assert.equal(hasSpecializedExecutor("jules"), false);
|
||||
});
|
||||
|
||||
test("#6699: a chat-completion request routed to provider 'jules' must not silently hit OpenAI's endpoint", () => {
|
||||
test("#6699: a chat-completion request routed to provider 'jules' must not silently hit OpenAI's endpoint", async () => {
|
||||
// Desired behavior: the Jules provider (a cloud-agent, registered only in
|
||||
// CLOUD_AGENT_PROVIDERS/staticModels, never in the chat REGISTRY) must not silently
|
||||
// resolve to OpenAI's chat/completions endpoint when routed through the normal
|
||||
@@ -27,9 +27,9 @@ test("#6699: a chat-completion request routed to provider 'jules' must not silen
|
||||
// genuine Jules key). Before the fix, getExecutor("jules") returned a working
|
||||
// executor whose buildUrl() resolved to OpenAI's endpoint -- this assertion FAILS on
|
||||
// unfixed release/v3.8.49 code because no error is thrown at all.
|
||||
assert.throws(
|
||||
() => getExecutor("jules"),
|
||||
(err) => {
|
||||
await assert.rejects(
|
||||
getExecutor("jules"),
|
||||
(err: Error & { status?: number }) => {
|
||||
assert.match(err.message, /cloud-agent provider/i);
|
||||
assert.match(err.message, /does not support direct chat completions/i);
|
||||
assert.equal(err.status, 400);
|
||||
|
||||
@@ -39,7 +39,7 @@ function geminiConnection() {
|
||||
}
|
||||
|
||||
test("falls back to the existing accessToken for a non-github provider when refreshCredentials returns null", async () => {
|
||||
const exec = getExecutor("gemini");
|
||||
const exec = await getExecutor("gemini");
|
||||
const origNeeds = exec.needsRefresh;
|
||||
const origRefresh = exec.refreshCredentials;
|
||||
exec.needsRefresh = () => true; // force the refresh attempt
|
||||
@@ -65,7 +65,7 @@ test("falls back to the existing accessToken for a non-github provider when refr
|
||||
});
|
||||
|
||||
test("still throws when refresh fails AND there is no accessToken to fall back on", async () => {
|
||||
const exec = getExecutor("gemini");
|
||||
const exec = await getExecutor("gemini");
|
||||
const origNeeds = exec.needsRefresh;
|
||||
const origRefresh = exec.refreshCredentials;
|
||||
exec.needsRefresh = () => true;
|
||||
|
||||
@@ -33,7 +33,7 @@ function importedCodexConnection() {
|
||||
}
|
||||
|
||||
test("force re-mints an imported rotating account that needsRefresh would skip (#3019 reactive)", async () => {
|
||||
const exec = getExecutor("codex");
|
||||
const exec = await getExecutor("codex");
|
||||
const origNeeds = exec.needsRefresh;
|
||||
const origRefresh = exec.refreshCredentials;
|
||||
let refreshCalls = 0;
|
||||
@@ -65,7 +65,7 @@ test("force re-mints an imported rotating account that needsRefresh would skip (
|
||||
});
|
||||
|
||||
test("force does NOT override the bulk #3019 guard (no allowRotatingRefresh → no mint)", async () => {
|
||||
const exec = getExecutor("codex");
|
||||
const exec = await getExecutor("codex");
|
||||
const origNeeds = exec.needsRefresh;
|
||||
const origRefresh = exec.refreshCredentials;
|
||||
let refreshCalls = 0;
|
||||
|
||||
@@ -33,8 +33,8 @@ test("#6670 freetheai is registered in the executor registry with an OpenAI-comp
|
||||
assert.ok(Array.isArray(entry.models) && entry.models.length > 0, "must seed a fallback model list");
|
||||
});
|
||||
|
||||
test("#6670 freetheai resolves through getExecutor() as a DefaultExecutor instance", () => {
|
||||
const executor = getExecutor("freetheai");
|
||||
test("#6670 freetheai resolves through getExecutor() as a DefaultExecutor instance", async () => {
|
||||
const executor = await getExecutor("freetheai");
|
||||
assert.ok(executor instanceof DefaultExecutor, "freetheai has no custom executor — must fall through to DefaultExecutor");
|
||||
});
|
||||
|
||||
|
||||
@@ -153,7 +153,7 @@ test("network failure persisted call log includes providerRequest in pipeline pa
|
||||
|
||||
test("network timeout persisted call log includes providerRequest in pipeline payloads", async () => {
|
||||
const { getExecutor } = await import("../../open-sse/executors/index.ts");
|
||||
const executor = getExecutor("openai");
|
||||
const executor = await getExecutor("openai");
|
||||
const originalGetTimeoutMs = executor.getTimeoutMs?.bind(executor);
|
||||
executor.getTimeoutMs = () => 200;
|
||||
|
||||
|
||||
@@ -16,7 +16,8 @@ import assert from "node:assert/strict";
|
||||
|
||||
import { WEB_COOKIE_PROVIDERS } from "../../src/shared/constants/providers/web-cookie.ts";
|
||||
import { REGISTRY } from "../../open-sse/config/providers/index.ts";
|
||||
import { getExecutor, TinyCmsExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { getExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { TinyCmsExecutor } from "../../open-sse/executors/tinycms.ts";
|
||||
import { setupDomMocks, type DomMockRestore } from "../../open-sse/executors/tinycmsSigner.ts";
|
||||
|
||||
// tinycmsSigner.ts intentionally does NOT install its window/document/canvas
|
||||
@@ -123,13 +124,13 @@ test("supportsReasoning is set on gpt-5.3-thinking-free", () => {
|
||||
|
||||
// ── Executor ──────────────────────────────────────────────────────────────────
|
||||
|
||||
test("getExecutor returns TinyCmsExecutor for 'tinycms-web'", () => {
|
||||
const e = getExecutor("tinycms-web");
|
||||
test("getExecutor returns TinyCmsExecutor for 'tinycms-web'", async () => {
|
||||
const e = await getExecutor("tinycms-web");
|
||||
assert.ok(e instanceof TinyCmsExecutor, "executor must be TinyCmsExecutor");
|
||||
});
|
||||
|
||||
test("getExecutor returns TinyCmsExecutor for 'tcw' alias", () => {
|
||||
const e = getExecutor("tcw");
|
||||
test("getExecutor returns TinyCmsExecutor for 'tcw' alias", async () => {
|
||||
const e = await getExecutor("tcw");
|
||||
assert.ok(e instanceof TinyCmsExecutor, "alias 'tcw' must resolve to TinyCmsExecutor");
|
||||
});
|
||||
|
||||
@@ -150,7 +151,7 @@ test("TinyCmsExecutor returns 401 when UUID is missing", async () => {
|
||||
credentials: {},
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
assert.ok(result.response, "response must be present");
|
||||
assert.ok("response" in result, "response must be present");
|
||||
assert.equal(result.response.status, 401);
|
||||
const body = await result.response.json();
|
||||
const errMsg = body?.error?.message || "";
|
||||
@@ -168,7 +169,7 @@ test("TinyCmsExecutor returns 401 when UUID does not start with 'R'", async () =
|
||||
credentials: { apiKey: "abc123" }, // does not start with 'R'
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
assert.ok(result.response, "response must be present");
|
||||
assert.ok("response" in result, "response must be present");
|
||||
assert.equal(result.response.status, 401);
|
||||
const body = await result.response.json();
|
||||
const errMsg = body?.error?.message || "";
|
||||
@@ -256,7 +257,7 @@ test("TinyCmsExecutor sanitizes errors (no stack traces in error response)", asy
|
||||
signal: AbortSignal.timeout(5000),
|
||||
});
|
||||
|
||||
assert.ok(result.response, "response must be present");
|
||||
assert.ok("response" in result, "response must be present");
|
||||
const body = await result.response.json();
|
||||
const errMsg = body?.error?.message || "";
|
||||
assert.ok(errMsg.includes("Invalid or missing device UUID"), "error must mention missing UUID");
|
||||
|
||||
@@ -68,8 +68,8 @@ for (const [id, info] of Object.entries(PROVIDERS)) {
|
||||
assert.equal(entry.passthroughModels, true);
|
||||
});
|
||||
|
||||
test(`#6674 ${id} resolves through getExecutor() as a DefaultExecutor instance`, () => {
|
||||
const executor = getExecutor(id);
|
||||
test(`#6674 ${id} resolves through getExecutor() as a DefaultExecutor instance`, async () => {
|
||||
const executor = await getExecutor(id);
|
||||
assert.ok(
|
||||
executor instanceof DefaultExecutor,
|
||||
`${id} has no custom executor — must fall through to DefaultExecutor`
|
||||
|
||||
@@ -39,11 +39,11 @@ test("yuanbao-web appears in the web-cookie catalog with a cookie authHint", ()
|
||||
assert.match(String(entry.website), /yuanbao\.tencent\.com/);
|
||||
});
|
||||
|
||||
test("YuanbaoWebExecutor is wired under id and alias", () => {
|
||||
test("YuanbaoWebExecutor is wired under id and alias", async () => {
|
||||
assert.ok(hasSpecializedExecutor("yuanbao-web"));
|
||||
assert.ok(hasSpecializedExecutor("ybw"));
|
||||
assert.ok(getExecutor("yuanbao-web") instanceof YuanbaoWebExecutor);
|
||||
assert.ok(getExecutor("ybw") instanceof YuanbaoWebExecutor);
|
||||
assert.ok(await getExecutor("yuanbao-web") instanceof YuanbaoWebExecutor);
|
||||
assert.ok(await getExecutor("ybw") instanceof YuanbaoWebExecutor);
|
||||
});
|
||||
|
||||
// ── Behavioral: SSE → OpenAI translation (mocked upstream) ─────────────────────
|
||||
|
||||
@@ -27,8 +27,8 @@ describe("Issue #9550 - amazon-q alias resolution", () => {
|
||||
assert.equal(parsed.model, "amazon-q");
|
||||
});
|
||||
|
||||
it('getExecutor("amazon-q") should exist and be a KiroExecutor', () => {
|
||||
const executor = getExecutor("amazon-q");
|
||||
it('getExecutor("amazon-q") should exist and be a KiroExecutor', async () => {
|
||||
const executor = await getExecutor("amazon-q");
|
||||
assert.ok(executor, "getExecutor('amazon-q') should return an executor");
|
||||
assert.equal(
|
||||
executor.constructor.name,
|
||||
|
||||
@@ -29,7 +29,7 @@ test("#10274: no search provider has a specialized chat executor", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("#10274: a chat-completion request routed to a search provider must not silently hit OpenAI's endpoint", () => {
|
||||
test("#10274: a chat-completion request routed to a search provider must not silently hit OpenAI's endpoint", async () => {
|
||||
// Desired behavior: search providers (registered only in SEARCH_PROVIDERS, never in the
|
||||
// chat REGISTRY) must not silently resolve to OpenAI's chat/completions endpoint when
|
||||
// routed through the normal chat-completions executor path. getExecutor() must throw a
|
||||
@@ -40,9 +40,9 @@ test("#10274: a chat-completion request routed to a search provider must not sil
|
||||
// OpenAI's endpoint -- this assertion FAILS on unfixed release/v3.8.50 code because no
|
||||
// error is thrown at all.
|
||||
for (const id of SEARCH_PROVIDER_IDS) {
|
||||
assert.throws(
|
||||
() => getExecutor(id),
|
||||
(err) => {
|
||||
await assert.rejects(
|
||||
getExecutor(id),
|
||||
(err: Error & { status?: number }) => {
|
||||
assert.match(err.message, /search provider/i);
|
||||
assert.match(err.message, /does not support chat completions/i);
|
||||
assert.match(err.message, /\/v1\/search/i);
|
||||
|
||||
@@ -20,13 +20,13 @@ test("hasSpecializedExecutor returns true for t3chat alias", () => {
|
||||
assert.ok(hasSpecializedExecutor("t3chat"));
|
||||
});
|
||||
|
||||
test("getExecutor returns T3ChatWebExecutor for t3-web", () => {
|
||||
const exec = getExecutor("t3-web");
|
||||
test("getExecutor returns T3ChatWebExecutor for t3-web", async () => {
|
||||
const exec = await getExecutor("t3-web");
|
||||
assert.ok(exec instanceof T3ChatWebExecutor);
|
||||
});
|
||||
|
||||
test("getExecutor returns T3ChatWebExecutor for t3chat alias", () => {
|
||||
const exec = getExecutor("t3chat");
|
||||
test("getExecutor returns T3ChatWebExecutor for t3chat alias", async () => {
|
||||
const exec = await getExecutor("t3chat");
|
||||
assert.ok(exec instanceof T3ChatWebExecutor);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
import test, { before, after } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { TinyCmsExecutor } from "../../open-sse/executors/index.ts";
|
||||
import { TinyCmsExecutor } from "../../open-sse/executors/tinycms.ts";
|
||||
import { setupDomMocks, type DomMockRestore } from "../../open-sse/executors/tinycmsSigner.ts";
|
||||
|
||||
let restoreDomMocks: DomMockRestore;
|
||||
|
||||
@@ -110,49 +110,51 @@ const noopExecuteInput = {
|
||||
|
||||
// ── Registration Tests ───────────────────────────────────────────────────────
|
||||
|
||||
test("HuggingChat executor is registered", () => {
|
||||
test("HuggingChat executor is registered", async () => {
|
||||
assert.ok(hasSpecializedExecutor("huggingchat"));
|
||||
assert.ok(hasSpecializedExecutor("hc"));
|
||||
const executor = getExecutor("huggingchat");
|
||||
const executor = await getExecutor("huggingchat");
|
||||
assert.ok(executor instanceof HuggingChatExecutor);
|
||||
});
|
||||
|
||||
test("Poe Web executor is registered", () => {
|
||||
test("Poe Web executor is registered", async () => {
|
||||
assert.ok(hasSpecializedExecutor("poe-web"));
|
||||
const executor = getExecutor("poe-web");
|
||||
const executor = await getExecutor("poe-web");
|
||||
assert.ok(executor instanceof PoeWebExecutor);
|
||||
// #8969: canonical API-key `poe` must not route through PoeWebExecutor.
|
||||
assert.equal(hasSpecializedExecutor("poe"), false);
|
||||
assert.ok(!(getExecutor("poe") instanceof PoeWebExecutor));
|
||||
const poeApiExecutor = await getExecutor("poe");
|
||||
assert.ok(!(poeApiExecutor instanceof PoeWebExecutor));
|
||||
});
|
||||
|
||||
test("Venice Web executor is registered", () => {
|
||||
test("Venice Web executor is registered", async () => {
|
||||
assert.ok(hasSpecializedExecutor("venice-web"));
|
||||
assert.ok(hasSpecializedExecutor("ven"));
|
||||
const executor = getExecutor("venice-web");
|
||||
const executor = await getExecutor("venice-web");
|
||||
assert.ok(executor instanceof VeniceWebExecutor);
|
||||
});
|
||||
|
||||
test("v0 Vercel Web executor is registered", () => {
|
||||
test("v0 Vercel Web executor is registered", async () => {
|
||||
assert.ok(hasSpecializedExecutor("v0-vercel-web"));
|
||||
assert.ok(hasSpecializedExecutor("v0"));
|
||||
const executor = getExecutor("v0-vercel-web");
|
||||
const executor = await getExecutor("v0-vercel-web");
|
||||
assert.ok(executor instanceof V0VercelWebExecutor);
|
||||
});
|
||||
|
||||
test("Kimi Web executor is registered", () => {
|
||||
assert.ok(getExecutor("kimi-web") instanceof KimiWebExecutor);
|
||||
test("Kimi Web executor is registered", async () => {
|
||||
const kimiWebExecutor = await getExecutor("kimi-web");
|
||||
assert.ok(kimiWebExecutor instanceof KimiWebExecutor);
|
||||
// #4699: the legacy `kimi` API-key id must never route through Kimi Web.
|
||||
assert.ok(hasSpecializedExecutor("kimi"));
|
||||
const legacyExecutor = getExecutor("kimi");
|
||||
const legacyExecutor = await getExecutor("kimi");
|
||||
assert.ok(legacyExecutor instanceof MoonshotExecutor);
|
||||
assert.ok(!(legacyExecutor instanceof KimiWebExecutor));
|
||||
});
|
||||
|
||||
test("Doubao Web executor is registered", () => {
|
||||
test("Doubao Web executor is registered", async () => {
|
||||
assert.ok(hasSpecializedExecutor("doubao-web"));
|
||||
assert.ok(hasSpecializedExecutor("db"));
|
||||
const executor = getExecutor("doubao-web");
|
||||
const executor = await getExecutor("doubao-web");
|
||||
assert.ok(executor instanceof DoubaoWebExecutor);
|
||||
});
|
||||
|
||||
@@ -190,9 +192,9 @@ test("Doubao Web sets correct provider", () => {
|
||||
|
||||
// ── Registration Tests (Qwen Web) ────────────────────────────────────────────
|
||||
|
||||
test("Qwen Web executor is registered", () => {
|
||||
test("Qwen Web executor is registered", async () => {
|
||||
assert.ok(hasSpecializedExecutor("qwen-web"));
|
||||
const executor = getExecutor("qwen-web");
|
||||
const executor = await getExecutor("qwen-web");
|
||||
assert.ok(executor instanceof QwenWebExecutor);
|
||||
});
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ function containsBytes(haystack: Uint8Array, needle: Uint8Array): boolean {
|
||||
}
|
||||
|
||||
test("Devin Desktop sends the curated raw model id without alias rewriting", async () => {
|
||||
const executor = getExecutor("devin-desktop");
|
||||
const executor = await getExecutor("devin-desktop");
|
||||
const originalFetch = globalThis.fetch;
|
||||
let requestBody: Uint8Array | null = null;
|
||||
globalThis.fetch = async (url, init) => {
|
||||
@@ -37,7 +37,7 @@ test("Devin Desktop sends the curated raw model id without alias rewriting", asy
|
||||
credentials: { accessToken: "test-devin-desktop-token" },
|
||||
});
|
||||
|
||||
assert.equal(result.response.status, 418);
|
||||
assert.equal((result instanceof Response ? result : result.response).status, 418);
|
||||
assert.ok(requestBody);
|
||||
assert.equal(containsBytes(requestBody, new TextEncoder().encode(model)), true);
|
||||
} finally {
|
||||
|
||||
@@ -81,14 +81,14 @@ test("xAI OAuth maps refreshable tokens and safe id_token display metadata", ()
|
||||
assert.equal(mapped.name, "Grok User");
|
||||
});
|
||||
|
||||
test("xAI OAuth is a distinct OAuth registry entry backed by the xAI executor", () => {
|
||||
test("xAI OAuth is a distinct OAuth registry entry backed by the xAI executor", async () => {
|
||||
assert.equal(xai_oauthProvider.authType, "oauth");
|
||||
assert.equal(xai_oauthProvider.baseUrl, "https://api.x.ai/v1/chat/completions");
|
||||
assert.ok(xai_oauthProvider.models?.some((model) => model.id === "grok-4.5"));
|
||||
assert.equal(hasSpecializedExecutor("xai-oauth"), true);
|
||||
assert.ok(getExecutor("xai-oauth") instanceof XaiExecutor);
|
||||
assert.ok(await getExecutor("xai-oauth") instanceof XaiExecutor);
|
||||
|
||||
const headers = getExecutor("xai-oauth").buildHeaders({ accessToken: "oauth-access" }, false);
|
||||
const headers = (await getExecutor("xai-oauth")).buildHeaders({ accessToken: "oauth-access" }, false);
|
||||
assert.equal(headers.Authorization, "Bearer oauth-access");
|
||||
});
|
||||
|
||||
|
||||
@@ -45,9 +45,9 @@ describe("zed-hosted registry entry", () => {
|
||||
assert.equal(entry.oauth, undefined);
|
||||
});
|
||||
|
||||
test("executor is wired in the executors map", () => {
|
||||
test("executor is wired in the executors map", async () => {
|
||||
assert.ok(hasSpecializedExecutor("zed-hosted"));
|
||||
assert.ok(getExecutor("zed-hosted") instanceof ZedHostedExecutor);
|
||||
assert.ok(await getExecutor("zed-hosted") instanceof ZedHostedExecutor);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -86,13 +86,13 @@ test("zenmux-free model names are human-readable strings", () => {
|
||||
|
||||
// ── Executor ──────────────────────────────────────────────────────────────────
|
||||
|
||||
test("getExecutor returns ZenmuxFreeExecutor for 'zenmux-free'", () => {
|
||||
const e = getExecutor("zenmux-free");
|
||||
test("getExecutor returns ZenmuxFreeExecutor for 'zenmux-free'", async () => {
|
||||
const e = await getExecutor("zenmux-free");
|
||||
assert.ok(e instanceof ZenmuxFreeExecutor, "executor must be ZenmuxFreeExecutor");
|
||||
});
|
||||
|
||||
test("getExecutor returns ZenmuxFreeExecutor for 'zmf' alias", () => {
|
||||
const e = getExecutor("zmf");
|
||||
test("getExecutor returns ZenmuxFreeExecutor for 'zmf' alias", async () => {
|
||||
const e = await getExecutor("zmf");
|
||||
assert.ok(e instanceof ZenmuxFreeExecutor, "alias 'zmf' must resolve to ZenmuxFreeExecutor");
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user