chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:12 +08:00
commit d8dcd5f6d1
8604 changed files with 2479390 additions and 0 deletions
@@ -0,0 +1,147 @@
/**
* T-02 auth-hook contract tests.
*
* Covers the `createOmniRouteAuthHook(opts)` factory and its loader behaviour
* against every Auth flavor (`api`, `oauth`, null, empty key). Validates the
* multi-instance fix: provider id flows from plugin options, not a module
* constant.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { createOmniRouteAuthHook } from "../src/index.js";
test("createOmniRouteAuthHook: default providerId is 'omniroute'", () => {
const hook = createOmniRouteAuthHook();
assert.equal(hook.provider, "opencode-omniroute");
});
test("createOmniRouteAuthHook: custom providerId binds to hook.provider (multi-instance)", () => {
const hook = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
assert.equal(hook.provider, "opencode-omniroute-preprod");
});
test("createOmniRouteAuthHook: methods[0] is type 'api' with label including displayName", () => {
const hook = createOmniRouteAuthHook();
assert.equal(Array.isArray(hook.methods), true);
assert.equal(hook.methods.length, 1);
const m = hook.methods[0];
assert.equal(m.type, "api");
assert.equal(m.label, "OmniRoute API Key");
const custom = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
assert.equal(custom.methods[0].label, "OmniRoute (opencode-omniroute-preprod) API Key");
});
test("createOmniRouteAuthHook: prompts[0] uses key='apiKey' per @opencode-ai/plugin contract", () => {
// NOTE: spec referenced `name: "apiKey"`; the official
// @opencode-ai/plugin@1.15.6 prompt shape uses `key` + `message` (no
// `name`/`label`/`mask` fields). Asserting against the real type contract.
const hook = createOmniRouteAuthHook();
const m = hook.methods[0];
assert.equal(m.type, "api");
// narrow: api method may carry prompts
const prompts = "prompts" in m ? m.prompts : undefined;
assert.ok(Array.isArray(prompts) && prompts.length === 1, "expected one prompt");
const p = prompts![0];
assert.equal(p.type, "text");
assert.equal((p as { key: string }).key, "apiKey");
assert.ok(
typeof (p as { message: string }).message === "string" &&
(p as { message: string }).message.includes("omniroute"),
"prompt message should mention provider id"
);
});
test("loader: valid api auth → {apiKey} when no baseURL option (T-04: fetch omitted)", async () => {
// T-04 changed the loader return shape: without a resolvable baseURL the
// interceptor cannot gate-keep requests, so the loader falls back to
// apiKey-only and the AI-SDK uses its default fetch. See fetch-interceptor
// tests for the wired-fetch branches.
const hook = createOmniRouteAuthHook();
assert.ok(hook.loader, "loader must be defined");
const result = await hook.loader!(
async () => ({ type: "api", key: "sk-test" }) as never,
{} as never
);
assert.deepEqual(result, { apiKey: "sk-test" });
});
test("loader: valid api auth → {apiKey, baseURL, fetch} when baseURL option set (T-04)", async () => {
const hook = createOmniRouteAuthHook({ baseURL: "https://or.example.com/v1" });
const result = await hook.loader!(
async () => ({ type: "api", key: "sk-x" }) as never,
{} as never
);
assert.equal((result as { apiKey: string }).apiKey, "sk-x");
assert.equal((result as { baseURL: string }).baseURL, "https://or.example.com/v1");
assert.equal(
typeof (result as { fetch?: unknown }).fetch,
"function",
"T-04: loader must wire fetch interceptor when baseURL resolves"
);
});
test("loader: features.fetchInterceptor=false AND geminiSanitization=false → no custom fetch (flags honored)", async () => {
// Regression: both fetch-layer flags were documented + schema-validated but
// silently ignored. Disabling both must fall back to the SDK default fetch.
const hook = createOmniRouteAuthHook({
baseURL: "https://or.example.com/v1",
features: { fetchInterceptor: false, geminiSanitization: false },
});
const result = await hook.loader!(
async () => ({ type: "api", key: "sk-x" }) as never,
{} as never
);
assert.deepEqual(result, { apiKey: "sk-x", baseURL: "https://or.example.com/v1" });
assert.equal(
(result as { fetch?: unknown }).fetch,
undefined,
"both flags off must omit the custom fetch"
);
});
test("loader: features.fetchInterceptor=false but geminiSanitization=true → fetch still wired (sanitizer only)", async () => {
const hook = createOmniRouteAuthHook({
baseURL: "https://or.example.com/v1",
features: { fetchInterceptor: false, geminiSanitization: true },
});
const result = await hook.loader!(
async () => ({ type: "api", key: "sk-x" }) as never,
{} as never
);
assert.equal(
typeof (result as { fetch?: unknown }).fetch,
"function",
"geminiSanitization alone must still provide a fetch wrapper"
);
});
test("loader: null/undefined auth → {} (no creds yet, OC surfaces /connect)", async () => {
const hook = createOmniRouteAuthHook();
const r1 = await hook.loader!(async () => null as never, {} as never);
assert.deepEqual(r1, {});
const r2 = await hook.loader!(async () => undefined as never, {} as never);
assert.deepEqual(r2, {});
});
test("loader: oauth-flavored auth → {} (wrong method type, ignored)", async () => {
const hook = createOmniRouteAuthHook();
const result = await hook.loader!(
async () =>
({
type: "oauth",
refresh: "r",
access: "a",
expires: 0,
}) as never,
{} as never
);
assert.deepEqual(result, {});
});
test("loader: api auth with empty key → {} (empty creds rejected)", async () => {
const hook = createOmniRouteAuthHook();
const result = await hook.loader!(async () => ({ type: "api", key: "" }) as never, {} as never);
assert.deepEqual(result, {});
});
@@ -0,0 +1,74 @@
/**
* TDD regression — auto combos must never advertise `limit.context: 0`.
*
* opencode's overflow guard (packages/opencode/src/session/overflow.ts)
* short-circuits when `model.limit.context === 0`:
*
* if (input.model.limit.context === 0) return false // never overflow
*
* so a zero context silently DISABLES opencode's smart auto-compaction for
* auto combos. The session then grows unbounded until OmniRoute's
* server-side purifyHistory() destructively drops old messages — the
* "coding agent keeps forgetting things" bug.
*
* Fix under test: mapAutoComboToStaticEntry consumes the context_length /
* max_output_tokens now served by GET /api/combos/auto, and falls back to a
* safe positive default (128000 / 8192) for older servers that do not send
* the fields yet.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { mapAutoComboToStaticEntry } from "../src/index.ts";
import type { OmniRouteRawAutoCombo } from "../src/index.ts";
test("uses server-provided context_length and max_output_tokens", () => {
const raw = {
id: "auto/coding",
name: "Auto Coding",
variant: "coding",
candidateCount: 5,
context_length: 1048576,
max_output_tokens: 65536,
} as OmniRouteRawAutoCombo;
const entry = mapAutoComboToStaticEntry(raw);
assert.equal(entry.limit?.context, 1048576);
assert.equal(entry.limit?.output, 65536);
});
test("falls back to a safe positive default when the server omits limits (old servers)", () => {
const raw = {
id: "auto",
name: "Auto",
candidateCount: 3,
} as OmniRouteRawAutoCombo;
const entry = mapAutoComboToStaticEntry(raw);
assert.ok(
typeof entry.limit?.context === "number" && entry.limit.context > 0,
`context must be a positive number (never 0 — zero disables opencode auto-compaction), got ${entry.limit?.context}`
);
assert.ok(
typeof entry.limit?.output === "number" && entry.limit.output > 0,
`output must be a positive number, got ${entry.limit?.output}`
);
});
test("ignores non-positive server values and keeps the safe fallback", () => {
const raw = {
id: "auto/fast",
name: "Auto Fast",
variant: "fast",
candidateCount: 2,
context_length: 0,
max_output_tokens: -1,
} as OmniRouteRawAutoCombo;
const entry = mapAutoComboToStaticEntry(raw);
assert.ok(
typeof entry.limit?.context === "number" && entry.limit.context > 0,
"zero/negative server values must not propagate"
);
assert.ok(typeof entry.limit?.output === "number" && entry.limit.output > 0);
});
@@ -0,0 +1,711 @@
/**
* T-05 combo-discovery contract tests.
*
* Covers:
* - `defaultOmniRouteCombosFetcher(baseURL, apiKey, timeoutMs?)`
* — envelope tolerance (`{combos: [...]}` and bare array), non-2xx errors.
* - `mapComboToModelV2(combo, members, providerId, baseURL)`
* — LCD policy across capabilities, limits, modalities; defensive
* posture on empty members; nice-name preference.
* - `createOmniRouteProviderHook(opts, deps)` extension
* — combos merged into the models map; collision resolution (combo
* wins, warn-once); soft-fail when the combos fetcher throws;
* combos cached + reused under the same TTL key as models.
*
* Mocking strategy mirrors `provider.test.ts`: both fetchers are
* dependency-injected at hook construction, no `fetch` monkey-patch.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
createOmniRouteProviderHook,
defaultOmniRouteCombosFetcher,
mapComboToModelV2,
type OmniRouteCombosFetcher,
type OmniRouteModelsFetcher,
type OmniRouteRawCombo,
type OmniRouteRawModelEntry,
} from "../src/index.js";
// ────────────────────────────────────────────────────────────────────────────
// Fixtures
// ────────────────────────────────────────────────────────────────────────────
const MODEL_PRIMARY: OmniRouteRawModelEntry = {
id: "claude-primary",
capabilities: {
tool_calling: true,
reasoning: true,
vision: true,
thinking: true,
temperature: true,
},
context_length: 200_000,
max_output_tokens: 64_000,
max_input_tokens: 180_000,
input_modalities: ["text", "image"],
output_modalities: ["text"],
};
const MODEL_SECONDARY: OmniRouteRawModelEntry = {
id: "claude-secondary",
capabilities: {
tool_calling: true,
reasoning: false,
vision: true,
thinking: false,
temperature: true,
},
context_length: 100_000,
max_output_tokens: 32_000,
max_input_tokens: 96_000,
input_modalities: ["text", "image"],
output_modalities: ["text"],
};
const MODEL_NO_TOOLS: OmniRouteRawModelEntry = {
id: "gemini-3-flash",
capabilities: { tool_calling: false, reasoning: false, vision: false, thinking: false },
context_length: 1_000_000,
max_output_tokens: 8_192,
input_modalities: ["text"],
output_modalities: ["text"],
};
const COMBO_CLAUDE_TIER: OmniRouteRawCombo = {
id: "combo-claude-tier",
name: "Claude Tier",
strategy: "priority",
models: [
{ id: "s1", kind: "model", model: "claude-primary", weight: 100 },
{ id: "s2", kind: "model", model: "claude-secondary", weight: 80 },
],
};
// ────────────────────────────────────────────────────────────────────────────
// Helpers
// ────────────────────────────────────────────────────────────────────────────
function stubModelsFetcher(
payload: OmniRouteRawModelEntry[]
): OmniRouteModelsFetcher & { callCount: () => number } {
let n = 0;
const f: OmniRouteModelsFetcher = async () => {
n++;
return payload;
};
return Object.assign(f, { callCount: () => n });
}
function stubCombosFetcher(
payload: OmniRouteRawCombo[]
): OmniRouteCombosFetcher & { callCount: () => number; callsBy: () => Array<[string, string]> } {
let n = 0;
const calls: Array<[string, string]> = [];
const f: OmniRouteCombosFetcher = async (baseURL, apiKey) => {
n++;
calls.push([baseURL, apiKey]);
return payload;
};
return Object.assign(f, {
callCount: () => n,
callsBy: () => calls,
});
}
function failingCombosFetcher(
err = new Error("boom")
): OmniRouteCombosFetcher & { callCount: () => number } {
let n = 0;
const f: OmniRouteCombosFetcher = async () => {
n++;
throw err;
};
return Object.assign(f, { callCount: () => n });
}
const apiAuth = (key: string): unknown => ({ type: "api", key });
// Capture console.warn invocations for the duration of a callback, then
// restore the original. Needed because the collision + soft-fail paths
// emit warnings we want to assert on.
async function withWarnCapture<T>(
fn: (warnings: Array<{ args: unknown[] }>) => Promise<T>
): Promise<{ result: T; warnings: Array<{ args: unknown[] }> }> {
const original = console.warn;
const warnings: Array<{ args: unknown[] }> = [];
console.warn = (...args: unknown[]) => {
warnings.push({ args });
};
try {
const result = await fn(warnings);
return { result, warnings };
} finally {
console.warn = original;
}
}
// ────────────────────────────────────────────────────────────────────────────
// defaultOmniRouteCombosFetcher — envelope tolerance + error surfacing
// ────────────────────────────────────────────────────────────────────────────
test("defaultOmniRouteCombosFetcher: parses {combos:[…]} envelope", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = (async (input: unknown) => {
const url = typeof input === "string" ? input : (input as { url: string }).url;
assert.equal(url, "https://or.example.com/api/combos");
return new Response(
JSON.stringify({
combos: [
{ id: "c1", name: "Combo One", strategy: "priority", models: [] },
{ id: "c2", name: "Combo Two", strategy: "weighted", models: [] },
],
}),
{ status: 200, headers: { "Content-Type": "application/json" } }
);
}) as typeof fetch;
try {
const combos = await defaultOmniRouteCombosFetcher("https://or.example.com", "sk-test");
assert.equal(combos.length, 2);
assert.equal(combos[0].id, "c1");
assert.equal(combos[1].id, "c2");
} finally {
globalThis.fetch = originalFetch;
}
});
test("defaultOmniRouteCombosFetcher: parses bare array envelope", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => {
return new Response(JSON.stringify([{ id: "c1" }, { id: "c2" }, { not_an_id: 42 }]), {
status: 200,
});
}) as typeof fetch;
try {
const combos = await defaultOmniRouteCombosFetcher("https://or.example.com/v1", "sk-test");
// Strip /v1 before /api/combos, AND filter out entries with no string id.
assert.equal(combos.length, 2);
assert.equal(combos[0].id, "c1");
assert.equal(combos[1].id, "c2");
} finally {
globalThis.fetch = originalFetch;
}
});
test("defaultOmniRouteCombosFetcher: strips trailing /v1 before /api/combos", async () => {
const originalFetch = globalThis.fetch;
let observedUrl = "";
globalThis.fetch = (async (input: unknown) => {
observedUrl = typeof input === "string" ? input : (input as { url: string }).url;
return new Response(JSON.stringify({ combos: [] }), { status: 200 });
}) as typeof fetch;
try {
await defaultOmniRouteCombosFetcher("https://or.example.com/v1/", "sk-test");
assert.equal(observedUrl, "https://or.example.com/api/combos");
} finally {
globalThis.fetch = originalFetch;
}
});
test("defaultOmniRouteCombosFetcher: throws on non-2xx with status code in message", async () => {
const originalFetch = globalThis.fetch;
globalThis.fetch = (async () => {
return new Response(JSON.stringify({ error: "Invalid token" }), {
status: 403,
statusText: "Forbidden",
});
}) as typeof fetch;
try {
await assert.rejects(
async () => {
await defaultOmniRouteCombosFetcher("https://or.example.com", "sk-bad");
},
(err: unknown) => {
const msg = err instanceof Error ? err.message : String(err);
assert.match(msg, /403/, "status code must appear in message");
assert.match(msg, /\/api\/combos/, "url must appear in message");
return true;
}
);
} finally {
globalThis.fetch = originalFetch;
}
});
test("defaultOmniRouteCombosFetcher: throws when apiKey missing", async () => {
await assert.rejects(
async () => defaultOmniRouteCombosFetcher("https://or.example.com", ""),
/apiKey required/
);
});
test("defaultOmniRouteCombosFetcher: throws when baseURL missing", async () => {
await assert.rejects(
async () => defaultOmniRouteCombosFetcher("", "sk-test"),
/baseURL required/
);
});
// ────────────────────────────────────────────────────────────────────────────
// mapComboToModelV2 — LCD semantics
// ────────────────────────────────────────────────────────────────────────────
test("mapComboToModelV2: empty members → capabilities all false (defensive)", () => {
const m = mapComboToModelV2(
{ id: "combo-empty", name: "Empty Combo" },
[],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m.id, "combo-empty");
assert.equal(m.name, "Empty Combo");
assert.equal(m.capabilities.temperature, false);
assert.equal(m.capabilities.reasoning, false);
assert.equal(m.capabilities.attachment, false);
assert.equal(m.capabilities.toolcall, false);
assert.equal(m.capabilities.input.text, false);
assert.equal(m.capabilities.output.text, false);
assert.equal(m.limit.context, 0);
assert.equal(m.limit.output, 0);
assert.equal(m.limit.input, undefined);
assert.deepEqual(m.cost, { input: 0, output: 0, cache: { read: 0, write: 0 } });
});
test("mapComboToModelV2: all members reasoning=true → combo reasoning=true", () => {
const m = mapComboToModelV2(
{ id: "c", models: [] },
[
MODEL_PRIMARY,
{
...MODEL_PRIMARY,
id: "p2",
capabilities: { ...MODEL_PRIMARY.capabilities, thinking: false, reasoning: true },
},
],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m.capabilities.reasoning, true);
});
test("mapComboToModelV2: any member reasoning=false → combo reasoning=false", () => {
const m = mapComboToModelV2(
{ id: "c", models: [] },
[MODEL_PRIMARY, MODEL_NO_TOOLS], // gemini-3-flash has reasoning:false, thinking:false
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m.capabilities.reasoning, false);
});
test("mapComboToModelV2: limit.context is min of members'", () => {
const m = mapComboToModelV2(
{ id: "c", models: [] },
[MODEL_PRIMARY, MODEL_SECONDARY, MODEL_NO_TOOLS],
"omniroute",
"https://or.example.com/v1"
);
// min(200_000, 100_000, 1_000_000) = 100_000
assert.equal(m.limit.context, 100_000);
// min(64_000, 32_000, 8_192) = 8_192
assert.equal(m.limit.output, 8_192);
});
test("mapComboToModelV2: limit.input only emitted when EVERY member declares one", () => {
const m1 = mapComboToModelV2(
{ id: "c", models: [] },
[MODEL_PRIMARY, MODEL_SECONDARY],
"omniroute",
"https://or.example.com/v1"
);
// Both declare max_input_tokens → limit.input = min(180000, 96000)
assert.equal(m1.limit.input, 96_000);
const m2 = mapComboToModelV2(
{ id: "c", models: [] },
[MODEL_PRIMARY, MODEL_NO_TOOLS], // gemini-3-flash doesn't declare max_input_tokens
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m2.limit.input, undefined);
});
test("mapComboToModelV2: nice name preferred from combo.name", () => {
const m1 = mapComboToModelV2(
{ id: "combo-x", name: "Pretty Name" },
[MODEL_PRIMARY],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m1.name, "Pretty Name");
// Falls back to id when name is absent or empty.
const m2 = mapComboToModelV2(
{ id: "combo-y" },
[MODEL_PRIMARY],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m2.name, "combo-y");
const m3 = mapComboToModelV2(
{ id: "combo-z", name: " " },
[MODEL_PRIMARY],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m3.name, "combo-z");
});
test("mapComboToModelV2: attachment AND vision flag both honored across members", () => {
// MODEL_PRIMARY: vision=true; MODEL_SECONDARY: vision=true → combo attachment=true
const yes = mapComboToModelV2(
{ id: "c1", models: [] },
[MODEL_PRIMARY, MODEL_SECONDARY],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(yes.capabilities.attachment, true);
// Add a member with no vision/attachment → AND collapses to false
const no = mapComboToModelV2(
{ id: "c2", models: [] },
[MODEL_PRIMARY, MODEL_NO_TOOLS],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(no.capabilities.attachment, false);
});
test("mapComboToModelV2: modalities AND'd across members", () => {
const m = mapComboToModelV2(
{ id: "c", models: [] },
[MODEL_PRIMARY, MODEL_SECONDARY], // both have text+image
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m.capabilities.input.text, true);
assert.equal(m.capabilities.input.image, true);
assert.equal(m.capabilities.input.audio, false);
// Add a text-only member → image collapses to false.
const m2 = mapComboToModelV2(
{ id: "c", models: [] },
[MODEL_PRIMARY, MODEL_NO_TOOLS],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m2.capabilities.input.text, true);
assert.equal(m2.capabilities.input.image, false);
});
test("mapComboToModelV2: api block matches providerId + baseURL", () => {
const m = mapComboToModelV2(
{ id: "c" },
[MODEL_PRIMARY],
"omniroute-preprod",
"https://or-preprod.example.com/v1"
);
assert.equal(m.providerID, "omniroute-preprod");
assert.equal(m.api.id, "openai-compatible");
assert.equal(m.api.url, "https://or-preprod.example.com/v1");
assert.equal(m.api.npm, "@ai-sdk/openai-compatible");
assert.equal(m.status, "active");
});
test("mapComboToModelV2: explicit member temperature=false drops combo temperature=false", () => {
const tempFalse: OmniRouteRawModelEntry = {
id: "no-temp",
capabilities: { tool_calling: true, temperature: false },
context_length: 100_000,
max_output_tokens: 8_000,
input_modalities: ["text"],
output_modalities: ["text"],
};
const m = mapComboToModelV2(
{ id: "c" },
[MODEL_PRIMARY, tempFalse],
"omniroute",
"https://or.example.com/v1"
);
assert.equal(m.capabilities.temperature, false);
});
// ────────────────────────────────────────────────────────────────────────────
// createOmniRouteProviderHook — combos merge + collision + soft-fail + cache
// ────────────────────────────────────────────────────────────────────────────
test("models() returns combo entries merged into the map", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY, MODEL_SECONDARY, MODEL_NO_TOOLS]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
// 3 raw models + 1 combo = 4 entries
assert.equal(Object.keys(out).length, 4);
assert.ok(out["opencode-omniroute/claude-primary"]);
assert.ok(out["opencode-omniroute/claude-secondary"]);
assert.ok(out["opencode-omniroute/gemini-3-flash"]);
assert.ok(out["opencode-omniroute/claude-tier"]);
const combo = out["opencode-omniroute/claude-tier"];
assert.equal(combo.name, "Claude Tier");
assert.equal(combo.providerID, "opencode-omniroute");
// LCD over claude-primary (200k, reasoning) + claude-secondary (100k, no reasoning)
assert.equal(combo.limit.context, 100_000);
assert.equal(combo.capabilities.reasoning, false);
assert.equal(combo.capabilities.toolcall, true);
});
test("models(): combo with unknown member ids degrades to all-false LCD posture", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY]); // catalog only has claude-primary
const combosFetcher = stubCombosFetcher([
{
id: "phantom",
name: "Phantom Combo",
models: [
{ id: "s1", kind: "model", model: "does-not-exist-1", weight: 50 },
{ id: "s2", kind: "model", model: "does-not-exist-2", weight: 50 },
],
},
]);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.ok(out["opencode-omniroute/phantom-combo"]);
// With zero resolvable members, LCD = all-false (defensive posture).
assert.equal(out["opencode-omniroute/phantom-combo"].capabilities.toolcall, false);
assert.equal(out["opencode-omniroute/phantom-combo"].capabilities.reasoning, false);
assert.equal(out["opencode-omniroute/phantom-combo"].limit.context, 0);
});
test("models(): hidden combos are excluded from the map", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY]);
const combosFetcher = stubCombosFetcher([
{
id: "visible",
name: "Visible",
models: [{ id: "s1", kind: "model", model: "claude-primary", weight: 100 }],
},
{
id: "hidden",
name: "Hidden",
isHidden: true,
models: [{ id: "s1", kind: "model", model: "claude-primary", weight: 100 }],
},
]);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.ok(out["opencode-omniroute/visible"]);
assert.ok(!out["opencode-omniroute/hidden"], "hidden combo must be omitted");
});
test("models(): combo name exactly matches raw model id → raw deleted, raw deleted, no warn", async () => {
// Combo.name === raw model id triggers the dedup deletion. This mirrors
// the real OmniRoute payload where /v1/models pre-mirrors combos as
// no-slash raw entries whose ids match /api/combos friendly names.
const colliderCombo: OmniRouteRawCombo = {
id: "uuid-collider",
name: "claude-primary", // EXACT match to MODEL_PRIMARY.id
models: [{ id: "s1", kind: "model", model: "claude-secondary", weight: 100 }],
};
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY, MODEL_SECONDARY]);
const combosFetcher = stubCombosFetcher([colliderCombo]);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
const { result: out, warnings } = await withWarnCapture(async (_w) => {
return hook.models!({} as never, { auth: apiAuth("sk-z") as never });
});
// Raw model replaced by combo of the same key; combo now lives at the bare slug.
assert.ok(out["opencode-omniroute/claude-primary"], "combo surfaces under prefixed key");
assert.equal(out["opencode-omniroute/claude-primary"].name, "claude-primary");
// No collision warning fires — dedup makes keys disjoint.
const collisionWarns = warnings.filter((w) => {
const msg = w.args[0];
return typeof msg === "string" && msg.includes("collides");
});
assert.equal(collisionWarns.length, 0, "no collision warn after dedup");
});
test("models(): two combos with same slug → second gets disambiguator suffix", async () => {
// Both combos slug to `claude` — second must get `claude-<id-prefix>`.
const combos: OmniRouteRawCombo[] = [
{
id: "uuid-a",
name: "Claude",
models: [{ id: "s", kind: "model", model: "claude-primary", weight: 1 }],
},
{
id: "uuid-b",
name: "Claude",
models: [{ id: "s", kind: "model", model: "claude-secondary", weight: 1 }],
},
];
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{
fetcher: stubModelsFetcher([MODEL_PRIMARY, MODEL_SECONDARY]),
combosFetcher: stubCombosFetcher(combos),
}
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
// First combo gets the bare slug; second gets disambiguated.
assert.ok(out["opencode-omniroute/claude"], "first combo at prefixed slug");
assert.ok(out["opencode-omniroute/claude-uuid"], "second combo disambiguated by id prefix");
});
test("models(): combos fetch fails → falls back to models-only, warn emitted, no throw", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY, MODEL_SECONDARY]);
const combosFetcher = failingCombosFetcher(new Error("ECONNRESET"));
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
const { result: out, warnings } = await withWarnCapture(async () => {
return hook.models!({} as never, { auth: apiAuth("sk-z") as never });
});
// Catalog includes the models but NOT any combo entries.
assert.equal(Object.keys(out).length, 2);
assert.ok(out["opencode-omniroute/claude-primary"]);
assert.ok(out["opencode-omniroute/claude-secondary"]);
// Soft-fail warning surfaced.
const softFail = warnings.find((w) => {
const msg = w.args[0];
return typeof msg === "string" && msg.includes("combos fetch failed");
});
assert.ok(softFail, "soft-fail warning must be emitted on combos fetch error");
assert.equal(combosFetcher.callCount(), 1);
});
test("models(): combos cached + reused within TTL (one combo fetch per TTL window)", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY, MODEL_SECONDARY]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
let nowMs = 1_000_000;
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1", modelCacheTtl: 60_000 },
{ fetcher: modelsFetcher, combosFetcher, now: () => nowMs }
);
await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
nowMs += 30_000; // half the TTL
const second = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.equal(combosFetcher.callCount(), 1, "combos fetched only once within TTL");
assert.equal(modelsFetcher.callCount(), 1, "models fetched only once within TTL");
assert.ok(second["opencode-omniroute/claude-tier"]);
});
test("models(): combos refetched after TTL expiry (same key as models)", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
let nowMs = 1_000_000;
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1", modelCacheTtl: 60_000 },
{ fetcher: modelsFetcher, combosFetcher, now: () => nowMs }
);
await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
nowMs += 60_001;
await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.equal(combosFetcher.callCount(), 2, "combos must refetch past TTL");
assert.equal(modelsFetcher.callCount(), 2, "models must refetch past TTL");
});
test("models(): combos fetcher receives the resolved baseURL + apiKey", async () => {
const modelsFetcher = stubModelsFetcher([MODEL_PRIMARY]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
await hook.models!({} as never, { auth: apiAuth("sk-spy") as never });
assert.deepEqual(combosFetcher.callsBy()[0], ["https://or.example.com/v1", "sk-spy"]);
});
test("models(): nested combo-ref context is the min of nested + raw members", async () => {
// Top-level combo MASTER-LIGHT has 1 raw model (claude-primary, 200k)
// and 2 combo-refs: OldLLM (8k member) and KIRO (32k member). The OLD
// plugin would advertise 200k (only the raw model); the fix should
// make it advertise 8k (the bottleneck across the member graph).
const modelsFetcher = stubModelsFetcher([
MODEL_PRIMARY,
{
id: "oldllm-member-1",
context_length: 8_000,
max_output_tokens: 4_000,
capabilities: {
tool_calling: false,
reasoning: false,
vision: false,
thinking: false,
temperature: true,
},
input_modalities: ["text"],
output_modalities: ["text"],
},
{
id: "kiro-member-1",
context_length: 32_000,
max_output_tokens: 8_000,
capabilities: {
tool_calling: true,
reasoning: false,
vision: false,
thinking: false,
temperature: true,
},
input_modalities: ["text"],
output_modalities: ["text"],
},
]);
const combosFetcher = stubCombosFetcher([
{
id: "oldllm",
name: "OldLLM",
models: [{ id: "s1", kind: "model", model: "oldllm-member-1", weight: 100 }],
},
{
id: "kiro",
name: "KIRO",
models: [{ id: "s1", kind: "model", model: "kiro-member-1", weight: 100 }],
},
{
id: "master-light",
name: "MASTER-LIGHT",
models: [
{ id: "r1", kind: "model", model: "claude-primary", weight: 50 },
{ id: "r2", kind: "combo-ref", comboName: "OldLLM", weight: 25 },
{ id: "r3", kind: "combo-ref", comboName: "KIRO", weight: 25 },
],
},
]);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher: modelsFetcher, combosFetcher }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
const masterLight = out["opencode-omniroute/master-light"];
assert.ok(masterLight, "MASTER-LIGHT entry must exist");
assert.equal(
masterLight.limit.context,
8_000,
`expected 8_000 (OldLLM bottleneck), got ${masterLight.limit.context}`
);
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,66 @@
/**
* Regression test for the disk-snapshot file permissions (release/v3.8.2
* review finding C2). The snapshot embeds provider topology + connection
* records and lives alongside auth.json (0o600), so it must NOT be readable by
* group/other. Before the fix it was written with the default (typically
* world-readable 0o644) mode.
*/
import test from "node:test";
import assert from "node:assert/strict";
import fs from "node:fs";
import os from "node:os";
import path from "node:path";
import {
defaultDiskSnapshotWriter,
diskSnapshotPath,
type OmniRouteFetchCacheEntry,
} from "../src/index.js";
function makeEntry(): Omit<OmniRouteFetchCacheEntry, "expiresAt"> {
return {
rawModels: [],
rawCombos: [],
rawEnrichment: new Map(),
rawCompressionCombos: [],
rawConnections: [],
};
}
test("defaultDiskSnapshotWriter writes an owner-only (no group/other) snapshot", async (t) => {
// POSIX-only assertion; Windows does not honor numeric file modes.
if (process.platform === "win32") {
t.skip("file mode semantics are POSIX-only");
return;
}
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-disk-perms-"));
const prevDataDir = process.env.OPENCODE_DATA_DIR;
process.env.OPENCODE_DATA_DIR = tmp;
try {
await defaultDiskSnapshotWriter("perm-test", makeEntry());
const file = diskSnapshotPath("perm-test");
assert.ok(fs.existsSync(file), "snapshot file should be written");
const fileMode = fs.statSync(file).mode & 0o777;
assert.equal(
fileMode & 0o077,
0,
`snapshot must not be group/other accessible (got ${fileMode.toString(8)})`
);
const dirMode = fs.statSync(path.dirname(file)).mode & 0o777;
assert.equal(
dirMode & 0o077,
0,
`plugins dir must not be group/other accessible (got ${dirMode.toString(8)})`
);
} finally {
if (prevDataDir === undefined) delete process.env.OPENCODE_DATA_DIR;
else process.env.OPENCODE_DATA_DIR = prevDataDir;
fs.rmSync(tmp, { recursive: true, force: true });
}
});
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,269 @@
/**
* T-04 fetch-interceptor contract tests.
*
* Covers `createOmniRouteFetchInterceptor` (URL-prefix gating, header merge,
* Content-Type defaulting, input-shape polymorphism) plus the loader
* integration that wires it into the AuthHook return shape.
*
* Strategy: replace `globalThis.fetch` with a closure-based recorder for the
* duration of each test (saved-and-restored in try/finally — node:test has
* no built-in spy/restore lifecycle). The recorder captures `(input, init)`
* as observed by the wrapped global call so we can assert on what was
* forwarded after header injection.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { createOmniRouteAuthHook, createOmniRouteFetchInterceptor } from "../src/index.js";
type FetchCall = { input: Parameters<typeof fetch>[0]; init?: RequestInit };
function installFetchRecorder(response: Response = new Response("ok")) {
const calls: FetchCall[] = [];
const original = globalThis.fetch;
globalThis.fetch = (async (input: any, init?: any) => {
calls.push({ input, init });
return response;
}) as typeof fetch;
const restore = () => {
globalThis.fetch = original;
};
return { calls, restore };
}
const BASE = "https://or.example.com/v1";
const KEY = "sk-test-fetch";
test("createOmniRouteFetchInterceptor: targets baseURL → Authorization header injected", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f(`${BASE}/chat/completions`, {
method: "POST",
body: JSON.stringify({ x: 1 }),
});
assert.equal(calls.length, 1);
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: targets baseURL → Authorization OVERRIDES caller-supplied Bearer", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f(`${BASE}/chat/completions`, {
method: "POST",
body: "{}",
headers: { Authorization: "Bearer attacker-key" },
});
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
// We own the apiKey for this provider — caller-supplied Bearer must lose.
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: targets baseURL + body → Content-Type defaults to application/json", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f(`${BASE}/chat/completions`, {
method: "POST",
body: JSON.stringify({ m: "x" }),
});
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Content-Type"), "application/json");
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: caller-set Content-Type is NOT overwritten", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f(`${BASE}/v2/whatever`, {
method: "POST",
body: "raw",
headers: { "Content-Type": "text/plain; charset=utf-8" },
});
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Content-Type"), "text/plain; charset=utf-8");
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: non-baseURL host → passthrough, no Authorization injected", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f("https://third-party.example.org/v1/chat", {
method: "POST",
body: "{}",
headers: { "X-Caller": "yes" },
});
const sent = calls[0]!;
// Init forwarded verbatim — no header injection.
const sentHeaders = new Headers((sent.init as RequestInit | undefined)?.headers);
assert.equal(sentHeaders.get("Authorization"), null, "MUST NOT leak apiKey");
assert.equal(sentHeaders.get("X-Caller"), "yes");
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: refuses suffix-spoof — `${base}-attacker.evil` does NOT match baseURL", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
// baseURL is `https://or.example.com/v1`. A spoofed
// `https://or.example.com/v1-attacker.evil/chat` shares the literal prefix
// but is NOT under our origin path — must be treated as passthrough.
await f("https://or.example.com/v1-attacker.evil/chat", {
method: "POST",
body: "{}",
});
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit | undefined)?.headers);
assert.equal(sentHeaders.get("Authorization"), null);
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: URL object input is handled", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f(new URL(`${BASE}/models`), {});
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: Request input is handled (reads .url)", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
const req = new Request(`${BASE}/chat/completions`, {
method: "POST",
body: JSON.stringify({ a: 1 }),
headers: { "X-Caller": "preserved" },
});
await f(req);
const sent = calls[0]!;
// The interceptor forwards the original Request as `input` but layers our
// headers into the `init`. We assert against the init view since fetch()
// resolves headers from init first when both are present.
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
assert.equal(
sentHeaders.get("X-Caller"),
"preserved",
"Request-attached headers must survive the merge"
);
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: trailing slash in baseURL is normalized", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({
apiKey: KEY,
baseURL: `${BASE}////`,
});
await f(`${BASE}/models`, {});
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
} finally {
restore();
}
});
test("createOmniRouteFetchInterceptor: GET without body does NOT set Content-Type", async () => {
const { calls, restore } = installFetchRecorder();
try {
const f = createOmniRouteFetchInterceptor({ apiKey: KEY, baseURL: BASE });
await f(`${BASE}/models`); // no init at all
const sent = calls[0]!;
const sentHeaders = new Headers((sent.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
assert.equal(
sentHeaders.get("Content-Type"),
null,
"Content-Type should only default when a body exists"
);
} finally {
restore();
}
});
// ----------------------------------------------------------------------------
// loader integration
// ----------------------------------------------------------------------------
test("loader: returns fetch fn when apiKey + baseURL both present (via opts)", async () => {
const hook = createOmniRouteAuthHook({ baseURL: BASE });
const result = await hook.loader!(async () => ({ type: "api", key: KEY }) as never, {} as never);
assert.equal((result as { apiKey: string }).apiKey, KEY);
assert.equal((result as { baseURL: string }).baseURL, BASE);
assert.equal(
typeof (result as { fetch?: unknown }).fetch,
"function",
"loader must wire fetch interceptor when baseURL resolves"
);
});
test("loader: returns fetch fn when baseURL is stashed on the auth credential", async () => {
// Some auth backends attach baseURL alongside the key (post-/connect flow).
// The loader should pick it up even when plugin opts.baseURL is unset.
const hook = createOmniRouteAuthHook();
const result = await hook.loader!(
async () => ({ type: "api", key: KEY, baseURL: BASE }) as never,
{} as never
);
assert.equal((result as { baseURL?: string }).baseURL, BASE);
assert.equal(typeof (result as { fetch?: unknown }).fetch, "function");
});
test("loader: omits fetch fn when baseURL missing (apiKey-only return)", async () => {
const hook = createOmniRouteAuthHook(); // no baseURL opt
const result = await hook.loader!(async () => ({ type: "api", key: KEY }) as never, {} as never);
// Interceptor needs a baseURL to gate-keep; without one, fall back to
// apiKey-only and let the SDK use its default fetch.
assert.deepEqual(result, { apiKey: KEY });
});
test("loader integration: wired interceptor actually injects Bearer when invoked", async () => {
// End-to-end: pull the fetch fn out of the loader return and exercise it,
// proving the wiring matches the standalone interceptor's contract.
const { calls, restore } = installFetchRecorder();
try {
const hook = createOmniRouteAuthHook({ baseURL: BASE });
const result = await hook.loader!(
async () => ({ type: "api", key: KEY }) as never,
{} as never
);
const wiredFetch = (result as { fetch: typeof fetch }).fetch;
await wiredFetch(`${BASE}/v1/models`, {});
assert.equal(calls.length, 1);
const sentHeaders = new Headers((calls[0]!.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), `Bearer ${KEY}`);
} finally {
restore();
}
});
@@ -0,0 +1,291 @@
/**
* Tests for the 3 mrmm-fork features backported to @omniroute/opencode-plugin:
*
* 1. `normaliseFreeLabel` — free-tier model display names get a consistent
* `[Free] ` prefix instead of trailing "(Free)" or ad-hoc "free" words.
*
* 2. `resolveApiBlock` — per-provider-prefix API format routing. Anthropic
* prefixes (`cc/`, `claude/`, `anthropic/`, `kiro/`, `kr/`) get the
* Anthropic SDK block; everything else gets OpenAI-compat.
*
* 3. `debugLog` — JSONL request/response capture, gated by
* `features.debugLog` and togglable at runtime via
* `debugLogEnabled/SetEnabled`.
*/
import { test } from "node:test";
import assert from "node:assert/strict";
import {
normaliseFreeLabel,
resolveApiBlock,
DEFAULT_ANTHROPIC_PREFIXES,
ensureV1Suffix,
debugLogEnabled,
debugLogSetEnabled,
debugLogClear,
debugLogRead,
debugLogAppend,
createDebugLoggingFetch,
DebugLogEntry,
} from "../src/index.js";
// ── 1. normaliseFreeLabel ────────────────────────────────────────────────────
test("normaliseFreeLabel: '(Free)' suffix becomes [Free] prefix", () => {
assert.equal(normaliseFreeLabel("GPT-4.1 (Free)"), "[Free] GPT-4.1");
});
test("normaliseFreeLabel: trailing ' Free' word becomes [Free] prefix", () => {
assert.equal(
normaliseFreeLabel("DeepSeek V4 Flash Free"),
"[Free] DeepSeek V4 Flash"
);
});
test("normaliseFreeLabel: trailing '-free' (hyphen) becomes [Free] prefix", () => {
assert.equal(normaliseFreeLabel("Llama 4 Scout-free"), "[Free] Llama 4 Scout");
});
test("normaliseFreeLabel: case-insensitive (FREE, Free, free all match)", () => {
assert.equal(normaliseFreeLabel("Model A FREE"), "[Free] Model A");
assert.equal(normaliseFreeLabel("Model A free"), "[Free] Model A");
assert.equal(normaliseFreeLabel("Model A Free"), "[Free] Model A");
});
test("normaliseFreeLabel: names without 'free' pass through unchanged", () => {
assert.equal(normaliseFreeLabel("Claude 4.7 Opus"), "Claude 4.7 Opus");
assert.equal(normaliseFreeLabel("GPT-5"), "GPT-5");
});
test("normaliseFreeLabel: 'free' in the middle of a name is NOT rewritten", () => {
// Only trailing/standalone "free" markers count; embedded "freedom" stays
assert.equal(
normaliseFreeLabel("Freedom Model"),
"Freedom Model"
);
});
test("normaliseFreeLabel: empty / whitespace-only inputs are handled", () => {
// Empty input returns empty; pure whitespace input passes through (no Free marker)
assert.equal(normaliseFreeLabel(""), "");
assert.equal(normaliseFreeLabel(" "), " ");
});
// ── 2. resolveApiBlock ───────────────────────────────────────────────────────
test("resolveApiBlock: cc/* models get the Anthropic SDK block (no /v1)", () => {
const block = resolveApiBlock("cc/claude-opus-4-7", "https://api.example.com");
assert.equal(block.id, "anthropic");
assert.equal(block.npm, "@ai-sdk/anthropic");
assert.equal(block.url, "https://api.example.com"); // NO /v1 suffix
});
test("resolveApiBlock: claude/*, anthropic/*, kiro/*, kr/* all route to Anthropic", () => {
for (const id of [
"claude/claude-opus-4-7",
"anthropic/claude-sonnet-4",
"kiro/claude-sonnet-4-5",
"kr/claude-opus-4-6",
]) {
const block = resolveApiBlock(id, "https://api.example.com");
assert.equal(block.id, "anthropic", `${id} should route to Anthropic`);
assert.equal(block.npm, "@ai-sdk/anthropic");
}
});
test("resolveApiBlock: non-Anthropic models get OpenAI-compat with /v1", () => {
const block = resolveApiBlock("gpt-4o", "https://api.example.com");
assert.equal(block.id, "openai-compatible");
assert.equal(block.npm, "@ai-sdk/openai-compatible");
assert.equal(block.url, "https://api.example.com/v1");
});
test("resolveApiBlock: user can override anthropicPrefixes to add custom prefixes", () => {
const block = resolveApiBlock("myproxy/claude-opus", "https://api.example.com", {
anthropicPrefixes: ["myproxy"],
});
assert.equal(block.id, "anthropic");
assert.equal(block.npm, "@ai-sdk/anthropic");
});
test("resolveApiBlock: empty anthropicPrefixes forces OpenAI-compat for everything", () => {
const block = resolveApiBlock("cc/claude-opus", "https://api.example.com", {
anthropicPrefixes: [],
});
assert.equal(block.id, "openai-compatible");
});
test("resolveApiBlock: baseURL that already ends in /v1 is not double-suffixed (OpenAI path)", () => {
const block = resolveApiBlock("gpt-4o", "https://api.example.com/v1");
assert.equal(block.url, "https://api.example.com/v1"); // idempotent
});
test("resolveApiBlock: model id without '/' uses the id as prefix", () => {
const block = resolveApiBlock("claude-opus-4-7", "https://api.example.com");
// The whole id is the prefix, which doesn't match "cc"/"claude" etc.
// So it falls through to OpenAI-compat.
assert.equal(block.id, "openai-compatible");
});
test("DEFAULT_ANTHROPIC_PREFIXES: contains the canonical Anthropic aliases", () => {
assert.deepEqual(DEFAULT_ANTHROPIC_PREFIXES, [
"cc",
"claude",
"anthropic",
"kiro",
"kr",
]);
});
test("ensureV1Suffix: idempotent for URLs that already end in /v1", () => {
assert.equal(ensureV1Suffix("https://api.example.com/v1"), "https://api.example.com/v1");
assert.equal(
ensureV1Suffix("https://api.example.com/v1/"),
"https://api.example.com/v1" // trailing slash is stripped
);
});
test("ensureV1Suffix: appends /v1 when missing", () => {
assert.equal(ensureV1Suffix("https://api.example.com"), "https://api.example.com/v1");
assert.equal(ensureV1Suffix("https://api.example.com/"), "https://api.example.com/v1");
});
// ── 3. debugLog ──────────────────────────────────────────────────────────────
test("debugLog: default state is disabled", () => {
debugLogClear("test-provider-disabled-default");
assert.equal(debugLogEnabled("test-provider-disabled-default"), false);
});
test("debugLogSetEnabled + debugLogEnabled: roundtrip", () => {
debugLogSetEnabled("test-provider-toggle", true);
assert.equal(debugLogEnabled("test-provider-toggle"), true);
debugLogSetEnabled("test-provider-toggle", false);
assert.equal(debugLogEnabled("test-provider-toggle"), false);
});
test("debugLogAppend + debugLogRead: roundtrip preserves entry shape", () => {
const providerId = "test-provider-readroundtrip";
debugLogClear(providerId);
const entry: DebugLogEntry = {
reqId: "req-1",
providerId,
ts: 1700000000000,
url: "https://api.example.com/v1/chat",
method: "POST",
reqHeaders: { "content-type": "application/json" },
reqBody: { model: "gpt-4o", messages: [] },
resStatus: 200,
resHeaders: { "content-type": "application/json" },
resBody: { choices: [] },
durationMs: 42,
};
debugLogAppend(entry);
const read = debugLogRead(providerId, 10);
assert.equal(read.length, 1);
assert.deepEqual(read[0], entry);
});
test("createDebugLoggingFetch: passes through when disabled", async () => {
const providerId = "test-provider-passthrough";
debugLogClear(providerId);
debugLogSetEnabled(providerId, false);
const calls: unknown[] = [];
const inner: typeof fetch = async (input) => {
calls.push(input);
return new Response("ok", { status: 200 });
};
const wrapped = createDebugLoggingFetch(inner, providerId, false);
const res = await wrapped("https://api.example.com/v1/chat");
assert.equal(res.status, 200);
assert.equal(calls.length, 1);
// No log entry should be written when disabled
assert.equal(debugLogRead(providerId).length, 0);
});
test("createDebugLoggingFetch: captures request/response when enabled", async () => {
const providerId = "test-provider-captures";
debugLogClear(providerId);
const inner: typeof fetch = async () =>
new Response(JSON.stringify({ ok: true }), {
status: 200,
headers: { "content-type": "application/json" },
});
const wrapped = createDebugLoggingFetch(inner, providerId, true);
const res = await wrapped("https://api.example.com/v1/chat", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ model: "gpt-4o" }),
});
assert.equal(res.status, 200);
const entries = debugLogRead(providerId);
assert.equal(entries.length, 1);
assert.equal(entries[0].method, "POST");
assert.equal(entries[0].resStatus, 200);
assert.equal(entries[0].url, "https://api.example.com/v1/chat");
assert.deepEqual(entries[0].reqBody, { model: "gpt-4o" });
});
test("createDebugLoggingFetch: records error without crashing the wrapped fetch", async () => {
const providerId = "test-provider-error";
debugLogClear(providerId);
const inner: typeof fetch = async () => {
throw new Error("network down");
};
const wrapped = createDebugLoggingFetch(inner, providerId, true);
await assert.rejects(wrapped("https://api.example.com/v1/chat"), /network down/);
const entries = debugLogRead(providerId);
assert.equal(entries.length, 1);
assert.equal(entries[0].resStatus, null);
assert.equal(entries[0].error, "network down");
});
// ── Regression tests for the 3 HIGH-priority bot review fixes ───────────────
test("createDebugLoggingFetch: URL instance input is captured (not 'undefined')", async () => {
const providerId = "test-provider-url-input";
debugLogClear(providerId);
const inner: typeof fetch = async () =>
new Response("ok", { status: 200 });
const wrapped = createDebugLoggingFetch(inner, providerId, true);
await wrapped(new URL("https://api.example.com/v1/chat"));
const entries = debugLogRead(providerId);
assert.equal(entries.length, 1);
assert.equal(entries[0].url, "https://api.example.com/v1/chat");
assert.notEqual(entries[0].url, undefined);
});
test("createDebugLoggingFetch: Request object input captures URL and headers", async () => {
const providerId = "test-provider-request-input";
debugLogClear(providerId);
const inner: typeof fetch = async () =>
new Response("ok", { status: 200 });
const wrapped = createDebugLoggingFetch(inner, providerId, true);
const req = new Request("https://api.example.com/v1/chat", {
method: "POST",
headers: { "x-test": "yes" },
});
await wrapped(req);
const entries = debugLogRead(providerId);
assert.equal(entries.length, 1);
assert.equal(entries[0].url, "https://api.example.com/v1/chat");
assert.equal(entries[0].reqHeaders["x-test"], "yes");
});
test("createDebugLoggingFetch: SSE response is NOT buffered (resBody is the stream marker)", async () => {
const providerId = "test-provider-sse";
debugLogClear(providerId);
const inner: typeof fetch = async () =>
new Response("data: hello\n\n", {
status: 200,
headers: { "content-type": "text/event-stream" },
});
const wrapped = createDebugLoggingFetch(inner, providerId, true);
const res = await wrapped("https://api.example.com/v1/stream");
// The response body must remain readable downstream
const txt = await res.text();
assert.equal(txt, "data: hello\n\n");
const entries = debugLogRead(providerId);
assert.equal(entries.length, 1);
assert.equal(entries[0].resBody, "[stream]", "SSE responses must not be buffered");
});
@@ -0,0 +1,410 @@
/**
* T-06 Gemini tool-schema sanitisation contract tests.
*
* Three layers under test:
* 1. `sanitizeGeminiToolSchemas` — pure function; key stripping + clone
* semantics on chat-completion + Responses-API shapes.
* 2. `shouldSanitizeForGemini` — model-string detection (liberal).
* 3. `createGeminiSanitizingFetch` — wrapper composition; URL gating,
* body-shape polymorphism, streaming-body bypass, fail-open behaviour,
* composition with the T-04 Bearer interceptor.
*
* Strategy: same posture as fetch-interceptor.test.ts — install a
* closure-based fetch recorder; assert on the `(input, init)` observed by
* the inner fetch after the sanitising wrapper has had its say.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
__resetGeminiStreamingWarning,
createGeminiSanitizingFetch,
createOmniRouteFetchInterceptor,
sanitizeGeminiToolSchemas,
shouldSanitizeForGemini,
} from "../src/index.js";
// ────────────────────────────────────────────────────────────────────────────
// Helpers
// ────────────────────────────────────────────────────────────────────────────
type FetchCall = { input: Parameters<typeof fetch>[0]; init?: RequestInit };
function recorder(response: Response = new Response("ok")): {
fn: typeof fetch;
calls: FetchCall[];
} {
const calls: FetchCall[] = [];
const fn = (async (input: any, init?: any) => {
calls.push({ input, init });
return response;
}) as typeof fetch;
return { fn, calls };
}
function bodyAsRecord(init: RequestInit | undefined): Record<string, unknown> {
const b = init?.body;
if (typeof b !== "string") {
throw new Error(`expected string body, got ${typeof b}`);
}
return JSON.parse(b) as Record<string, unknown>;
}
// Sample tool payloads — small enough to inline, big enough to cover
// chat-completion + Responses-API + nested properties.
function chatCompletionsWithDollarSchema(): Record<string, unknown> {
return {
model: "gemini-2.5-pro",
tools: [
{
type: "function",
function: {
name: "search",
parameters: {
$schema: "http://json-schema.org/draft-07/schema#",
type: "object",
additionalProperties: false,
properties: {
q: { type: "string" },
},
required: ["q"],
},
},
},
],
};
}
function responsesApiWithRef(): Record<string, unknown> {
return {
model: "gemini-2.5-flash",
tools: [
{
type: "function",
name: "lookup",
input_schema: {
type: "object",
$ref: "#/definitions/Lookup",
properties: {
id: { type: "string", ref: "Id" },
},
},
},
],
};
}
function nestedPropertiesPayload(): Record<string, unknown> {
return {
model: "gemini-pro",
tools: [
{
type: "function",
function: {
name: "deep",
parameters: {
type: "object",
properties: {
outer: {
type: "object",
$schema: "http://json-schema.org/draft-07/schema#",
properties: {
inner: {
type: "object",
additionalProperties: true,
$ref: "#/inner",
properties: {
leaf: { type: "string" },
},
},
},
},
},
},
},
},
],
};
}
// ────────────────────────────────────────────────────────────────────────────
// sanitizeGeminiToolSchemas — pure function
// ────────────────────────────────────────────────────────────────────────────
test("sanitizeGeminiToolSchemas: strips $schema from top-level", () => {
const input = {
model: "gemini-2.5-pro",
$schema: "http://json-schema.org/draft-07/schema#",
tools: [],
};
const out = sanitizeGeminiToolSchemas(input) as Record<string, unknown>;
assert.equal(out.$schema, undefined);
assert.equal(out.model, "gemini-2.5-pro");
});
test("sanitizeGeminiToolSchemas: strips $ref + additionalProperties from tools[].function.parameters", () => {
const input = chatCompletionsWithDollarSchema();
const out = sanitizeGeminiToolSchemas(input) as Record<string, unknown>;
const params = (out.tools as Array<{ function: { parameters: Record<string, unknown> } }>)[0]!
.function.parameters;
assert.equal(params.$schema, undefined);
assert.equal(params.additionalProperties, undefined);
// Untouched keys survive.
assert.equal(params.type, "object");
assert.deepEqual(params.required, ["q"]);
});
test("sanitizeGeminiToolSchemas: strips nested $schema from properties.x.properties.y", () => {
const input = nestedPropertiesPayload();
const out = sanitizeGeminiToolSchemas(input) as Record<string, unknown>;
const params = (out.tools as Array<{ function: { parameters: Record<string, unknown> } }>)[0]!
.function.parameters;
const outer = (params.properties as Record<string, Record<string, unknown>>).outer!;
const inner = (outer.properties as Record<string, Record<string, unknown>>).inner!;
assert.equal(outer.$schema, undefined);
assert.equal(inner.$ref, undefined);
assert.equal(inner.additionalProperties, undefined);
// Leaf still intact.
assert.deepEqual(inner.properties, { leaf: { type: "string" } });
});
test("sanitizeGeminiToolSchemas: handles Responses-API tools[].input_schema shape", () => {
const input = responsesApiWithRef();
const out = sanitizeGeminiToolSchemas(input) as Record<string, unknown>;
const inputSchema = (out.tools as Array<{ input_schema: Record<string, unknown> }>)[0]!
.input_schema;
assert.equal(inputSchema.$ref, undefined);
// Nested `ref` (lowercase) also stripped.
const props = inputSchema.properties as Record<string, Record<string, unknown>>;
assert.equal(props.id!.ref, undefined);
assert.equal(props.id!.type, "string");
});
test("sanitizeGeminiToolSchemas: leaves payload without tools untouched", () => {
const input = { model: "gemini-2.5-pro", messages: [{ role: "user", content: "hi" }] };
const out = sanitizeGeminiToolSchemas(input) as Record<string, unknown>;
assert.deepEqual(out, input);
});
test("sanitizeGeminiToolSchemas: does not mutate input (returned object is distinct)", () => {
const input = chatCompletionsWithDollarSchema();
const beforeJson = JSON.stringify(input);
const out = sanitizeGeminiToolSchemas(input);
// Input bit-identical to its pre-sanitise serialisation.
assert.equal(JSON.stringify(input), beforeJson);
// Output is a different reference.
assert.notEqual(out, input);
});
// ────────────────────────────────────────────────────────────────────────────
// shouldSanitizeForGemini — detection
// ────────────────────────────────────────────────────────────────────────────
test("shouldSanitizeForGemini: gemini-2.5-pro → true", () => {
assert.equal(shouldSanitizeForGemini({ model: "gemini-2.5-pro" }), true);
});
test("shouldSanitizeForGemini: models/gemini-pro → true", () => {
assert.equal(shouldSanitizeForGemini({ model: "models/gemini-pro" }), true);
});
test("shouldSanitizeForGemini: google-vertex/gemini-1.5-flash → true", () => {
assert.equal(shouldSanitizeForGemini({ model: "google-vertex/gemini-1.5-flash" }), true);
});
test("shouldSanitizeForGemini: gemini/gemini-2.5-pro → true", () => {
assert.equal(shouldSanitizeForGemini({ model: "gemini/gemini-2.5-pro" }), true);
});
test("shouldSanitizeForGemini: claude-sonnet-4 → false", () => {
assert.equal(shouldSanitizeForGemini({ model: "claude-sonnet-4" }), false);
});
test("shouldSanitizeForGemini: payload.model missing → false", () => {
assert.equal(shouldSanitizeForGemini({ messages: [] }), false);
});
test("shouldSanitizeForGemini: payload is null → false", () => {
assert.equal(shouldSanitizeForGemini(null), false);
});
test("shouldSanitizeForGemini: payload.model is non-string → false", () => {
assert.equal(shouldSanitizeForGemini({ model: 42 }), false);
});
// ────────────────────────────────────────────────────────────────────────────
// createGeminiSanitizingFetch — wrapper
// ────────────────────────────────────────────────────────────────────────────
const URL_CHAT = "https://or.example.com/v1/chat/completions";
const URL_RESPONSES = "https://or.example.com/v1/responses";
const URL_MODELS = "https://or.example.com/v1/models";
test("createGeminiSanitizingFetch: gemini model + chat/completions → tool schemas stripped before forward", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
await wrapped(URL_CHAT, {
method: "POST",
body: JSON.stringify(chatCompletionsWithDollarSchema()),
});
assert.equal(rec.calls.length, 1);
const forwarded = bodyAsRecord(rec.calls[0]!.init);
const params = (
forwarded.tools as Array<{ function: { parameters: Record<string, unknown> } }>
)[0]!.function.parameters;
assert.equal(params.$schema, undefined);
assert.equal(params.additionalProperties, undefined);
});
test("createGeminiSanitizingFetch: non-gemini model + chat/completions → body passed through unchanged", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
const originalBody = JSON.stringify({
model: "claude-sonnet-4",
tools: [
{
type: "function",
function: {
name: "x",
parameters: { $schema: "keep-me", type: "object" },
},
},
],
});
await wrapped(URL_CHAT, { method: "POST", body: originalBody });
// Identity check on body — wrapper must NOT mutate non-Gemini payloads.
assert.equal(rec.calls[0]!.init!.body, originalBody);
});
test("createGeminiSanitizingFetch: gemini model + /v1/models (non-completion endpoint) → body passed through unchanged", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
// GET /v1/models has no body in production; assert that even if a caller
// attached a Gemini-shaped body to a non-completion URL, the wrapper
// doesn't touch it.
const body = JSON.stringify(chatCompletionsWithDollarSchema());
await wrapped(URL_MODELS, { method: "POST", body });
assert.equal(rec.calls[0]!.init!.body, body);
});
test("createGeminiSanitizingFetch: gemini model + /responses endpoint → input_schema stripped", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
await wrapped(URL_RESPONSES, {
method: "POST",
body: JSON.stringify(responsesApiWithRef()),
});
const forwarded = bodyAsRecord(rec.calls[0]!.init);
const schema = (forwarded.tools as Array<{ input_schema: Record<string, unknown> }>)[0]!
.input_schema;
assert.equal(schema.$ref, undefined);
});
test("createGeminiSanitizingFetch: gemini model + Request input with body → tool schemas stripped", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
const req = new Request(URL_CHAT, {
method: "POST",
body: JSON.stringify(chatCompletionsWithDollarSchema()),
headers: { "Content-Type": "application/json" },
});
await wrapped(req);
const forwarded = bodyAsRecord(rec.calls[0]!.init);
const params = (
forwarded.tools as Array<{ function: { parameters: Record<string, unknown> } }>
)[0]!.function.parameters;
assert.equal(params.$schema, undefined);
});
test("createGeminiSanitizingFetch: gemini model + ReadableStream body → skipped + warn emitted once", async () => {
__resetGeminiStreamingWarning();
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
// Capture console.warn for the duration of this test.
const warnings: string[] = [];
const originalWarn = console.warn;
console.warn = (...args: unknown[]) => {
warnings.push(args.map(String).join(" "));
};
try {
const stream1 = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("{}"));
controller.close();
},
});
const stream2 = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode("{}"));
controller.close();
},
});
// Two streaming calls — only one warn expected.
await wrapped(URL_CHAT, { method: "POST", body: stream1 });
await wrapped(URL_CHAT, { method: "POST", body: stream2 });
} finally {
console.warn = originalWarn;
}
// Both calls forwarded to inner fetch with their streams intact.
assert.equal(rec.calls.length, 2);
// ONE warning total — one-shot latch held.
assert.equal(warnings.length, 1);
assert.match(warnings[0]!, /streaming Request body, skipping schema strip/);
});
test("createGeminiSanitizingFetch: invalid JSON body → pass through, no throw", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
// Garbage body must not crash the wrapper.
await wrapped(URL_CHAT, { method: "POST", body: "this is not json{{" });
assert.equal(rec.calls.length, 1);
assert.equal(rec.calls[0]!.init!.body, "this is not json{{");
});
test("createGeminiSanitizingFetch: empty body → pass through unchanged", async () => {
const rec = recorder();
const wrapped = createGeminiSanitizingFetch(rec.fn);
await wrapped(URL_CHAT, { method: "POST" });
assert.equal(rec.calls.length, 1);
});
test("createGeminiSanitizingFetch: composes correctly with createOmniRouteFetchInterceptor (Bearer + sanitization)", async () => {
// Save and replace globalThis.fetch — the Bearer interceptor calls global
// fetch when the URL targets its baseURL.
const originalFetch = globalThis.fetch;
const observed: FetchCall[] = [];
globalThis.fetch = (async (input: any, init?: any) => {
observed.push({ input, init });
return new Response("ok");
}) as typeof fetch;
try {
const composed = createGeminiSanitizingFetch(
createOmniRouteFetchInterceptor({
apiKey: "sk-test",
baseURL: "https://or.example.com/v1",
})
);
await composed(URL_CHAT, {
method: "POST",
body: JSON.stringify(chatCompletionsWithDollarSchema()),
});
assert.equal(observed.length, 1);
// Bearer injected (header concern).
const sentHeaders = new Headers((observed[0]!.init as RequestInit).headers);
assert.equal(sentHeaders.get("Authorization"), "Bearer sk-test");
// Schema sanitised (body concern).
const forwarded = bodyAsRecord(observed[0]!.init);
const params = (
forwarded.tools as Array<{ function: { parameters: Record<string, unknown> } }>
)[0]!.function.parameters;
assert.equal(params.$schema, undefined);
assert.equal(params.additionalProperties, undefined);
} finally {
globalThis.fetch = originalFetch;
}
});
@@ -0,0 +1,136 @@
/**
* T-08 multi-instance smoke.
*
* Validates that two `OmniRoutePlugin(input, opts)` invocations with
* different `providerId` values coexist without sharing mutable state.
* This is the contract that lets opencode.json declare prod + preprod
* side by side:
*
* "plugin": [
* ["@omniroute/opencode-plugin", {"providerId": "omniroute-prod", "baseURL": "https://or.example/v1"}],
* ["@omniroute/opencode-plugin", {"providerId": "omniroute-preprod", "baseURL": "https://or-preprod.example/v1"}]
* ]
*
* Assertions:
* - Each invocation returns its own hooks object (no identity reuse).
* - Each `auth` hook carries its own `provider` matching opts.providerId.
* - Each `auth.methods` array is its own array (not the same reference).
* - Calling the factory twice with IDENTICAL opts still yields two
* independent objects (no instance reuse / no shared closure cache).
* - Mutating one instance's auth hook does NOT bleed into the other.
* - Each instance's loader closure captures its OWN baseURL — no
* last-write-wins module-scope state.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { OmniRoutePlugin } from "../src/index.js";
const fakeInput = {} as Parameters<typeof OmniRoutePlugin>[0];
test("multi-instance: two plugin invocations bind to their own providerId", async () => {
const a = await OmniRoutePlugin(fakeInput, {
providerId: "omniroute-prod",
baseURL: "https://a.example/v1",
});
const b = await OmniRoutePlugin(fakeInput, {
providerId: "omniroute-preprod",
baseURL: "https://b.example/v1",
});
assert.equal(a.auth?.provider, "opencode-omniroute-prod");
assert.equal(b.auth?.provider, "opencode-omniroute-preprod");
});
test("multi-instance: hook objects + nested arrays are independent references", async () => {
const a = await OmniRoutePlugin(fakeInput, {
providerId: "alpha",
baseURL: "https://a.example/v1",
});
const b = await OmniRoutePlugin(fakeInput, {
providerId: "bravo",
baseURL: "https://b.example/v1",
});
assert.notEqual(a, b, "top-level hooks objects must not be the same reference");
assert.notEqual(a.auth, b.auth, "auth hooks must not be the same reference");
assert.notEqual(
a.auth?.methods,
b.auth?.methods,
"methods arrays must not be the same reference"
);
});
test("multi-instance: identical opts twice still yield independent objects", async () => {
const opts = { providerId: "twin", baseURL: "https://twin.example/v1" };
const first = await OmniRoutePlugin(fakeInput, { ...opts });
const second = await OmniRoutePlugin(fakeInput, { ...opts });
assert.notEqual(first, second);
assert.notEqual(first.auth, second.auth);
assert.notEqual(first.auth?.methods, second.auth?.methods);
// Same provider id is fine — what matters is no shared mutable state.
assert.equal(first.auth?.provider, "opencode-twin");
assert.equal(second.auth?.provider, "opencode-twin");
});
test("multi-instance: mutating instance A's auth.methods does not affect instance B", async () => {
const a = await OmniRoutePlugin(fakeInput, {
providerId: "iso-a",
baseURL: "https://a.example/v1",
});
const b = await OmniRoutePlugin(fakeInput, {
providerId: "iso-b",
baseURL: "https://b.example/v1",
});
const beforeLen = b.auth?.methods?.length ?? 0;
// Mutate a's methods array — extend it; b's must be untouched.
// We don't know the concrete method shape so push a sentinel cast.
a.auth?.methods?.push({ type: "api", label: "sentinel" } as never);
assert.equal(b.auth?.methods?.length, beforeLen, "instance B leaked from instance A mutation");
});
test("multi-instance: loader closures see their own opts (not last-write-wins)", async () => {
// Each plugin's loader builds its loader payload from the providerId/baseURL
// captured at invocation time. If the factory accidentally shared a closure
// (e.g. a module-scope let that the last invocation overwrites), both
// loaders would emit the same baseURL. Verify they don't.
const a = await OmniRoutePlugin(fakeInput, {
providerId: "omniroute-prod",
baseURL: "https://prod.example/v1",
});
const b = await OmniRoutePlugin(fakeInput, {
providerId: "omniroute-preprod",
baseURL: "https://preprod.example/v1",
});
assert.ok(a.auth?.loader, "instance A must have a loader");
assert.ok(b.auth?.loader, "instance B must have a loader");
const getAuthA = async () => ({ type: "api", key: "sk-prod" }) as never;
const getAuthB = async () => ({ type: "api", key: "sk-preprod" }) as never;
const rA = (await a.auth!.loader!(getAuthA, {} as never)) as Record<string, unknown>;
const rB = (await b.auth!.loader!(getAuthB, {} as never)) as Record<string, unknown>;
assert.equal(rA.apiKey, "sk-prod");
assert.equal(rA.baseURL, "https://prod.example/v1");
assert.equal(rB.apiKey, "sk-preprod");
assert.equal(rB.baseURL, "https://preprod.example/v1");
});
test("multi-instance: invalid opts on one instance does not poison the other", async () => {
// Sequencing: bad opts → good opts. The bad call must throw cleanly; the
// good call must still produce a working hooks object. Confirms no
// half-built module-level state survives a failed parse.
await assert.rejects(
() => OmniRoutePlugin(fakeInput, { providerId: "bad id!" } as never),
/providerId/
);
const ok = await OmniRoutePlugin(fakeInput, {
providerId: "recovered",
baseURL: "https://ok.example/v1",
});
assert.equal(ok.auth?.provider, "opencode-recovered");
});
@@ -0,0 +1,104 @@
/**
* T-08 options-schema tests.
*
* Covers `parseOmniRoutePluginOptions(opts)` — the strict Zod gate that
* validates the second-arg `PluginOptions` bag from opencode.json before
* any hook is wired. Anti-pattern checklist mirrored here:
*
* - `null` / `undefined` must collapse to `{}` (defaults apply downstream).
* - Unknown keys must THROW (`.strict()` catches opencode.json typos).
* - Validation runs at parse time, not import time (module loads cleanly).
*/
import test from "node:test";
import assert from "node:assert/strict";
import { parseOmniRoutePluginOptions } from "../src/index.js";
test("parseOmniRoutePluginOptions: undefined → {}", () => {
assert.deepEqual(parseOmniRoutePluginOptions(undefined), {});
});
test("parseOmniRoutePluginOptions: null → {}", () => {
assert.deepEqual(parseOmniRoutePluginOptions(null), {});
});
test("parseOmniRoutePluginOptions: empty object → {}", () => {
assert.deepEqual(parseOmniRoutePluginOptions({}), {});
});
test("parseOmniRoutePluginOptions: valid providerId → returns it", () => {
const r = parseOmniRoutePluginOptions({ providerId: "omniroute-preprod" });
assert.equal(r.providerId, "omniroute-preprod");
});
test("parseOmniRoutePluginOptions: invalid providerId (special chars) → throws", () => {
assert.throws(
() => parseOmniRoutePluginOptions({ providerId: "omniroute prod!" }),
/providerId.*slug/i
);
});
test("parseOmniRoutePluginOptions: empty providerId → throws", () => {
assert.throws(() => parseOmniRoutePluginOptions({ providerId: "" }), /providerId/i);
});
test("parseOmniRoutePluginOptions: valid modelCacheTtl → returns it", () => {
const r = parseOmniRoutePluginOptions({ modelCacheTtl: 60_000 });
assert.equal(r.modelCacheTtl, 60_000);
});
test("parseOmniRoutePluginOptions: negative modelCacheTtl → throws", () => {
assert.throws(() => parseOmniRoutePluginOptions({ modelCacheTtl: -1 }), /modelCacheTtl/i);
});
test("parseOmniRoutePluginOptions: zero modelCacheTtl → throws (positive required)", () => {
assert.throws(() => parseOmniRoutePluginOptions({ modelCacheTtl: 0 }), /modelCacheTtl/i);
});
test("parseOmniRoutePluginOptions: invalid baseURL (not a URL) → throws", () => {
assert.throws(() => parseOmniRoutePluginOptions({ baseURL: "not-a-url" }), /baseURL/i);
});
test("parseOmniRoutePluginOptions: unknown key → throws (strict mode catches typos)", () => {
assert.throws(
() =>
parseOmniRoutePluginOptions({
providerId: "omniroute",
provider_id: "typo-here",
}),
/provider_id|unrecognized/i
);
});
test("parseOmniRoutePluginOptions: all four fields populated correctly → returns them", () => {
const opts = {
providerId: "omniroute-prod",
displayName: "OmniRoute Production",
modelCacheTtl: 120_000,
baseURL: "https://or.example.com/v1",
};
const r = parseOmniRoutePluginOptions(opts);
assert.deepEqual(r, opts);
});
test("parseOmniRoutePluginOptions: error message lists every issue path", () => {
// Two bad fields at once → error string should mention BOTH.
try {
parseOmniRoutePluginOptions({
providerId: "",
baseURL: "garbage",
});
assert.fail("expected throw");
} catch (err) {
const msg = (err as Error).message;
assert.match(msg, /providerId/);
assert.match(msg, /baseURL/);
}
});
test("parseOmniRoutePluginOptions: module import alone does NOT throw", async () => {
// Re-importing the entry must not trigger validation; validation only fires
// on explicit parseOmniRoutePluginOptions / OmniRoutePlugin invocation.
const mod = await import("../src/index.js");
assert.equal(typeof mod.parseOmniRoutePluginOptions, "function");
});
@@ -0,0 +1,271 @@
/**
* T-03 provider-hook contract tests.
*
* Covers `createOmniRouteProviderHook(opts, deps)`:
* - hook.id binds to resolved providerId (single + multi-instance)
* - models() narrows ctx.auth, fetches via injected fetcher, caches per
* (baseURL, apiKey) tuple, refetches after TTL
* - mapRawModelToModelV2 emits a v2 Model shape matching the
* @opencode-ai/sdk/v2 type
*
* Mocking strategy: the fetcher is dependency-injected at hook construction
* (`deps.fetcher`). No global fetch monkey-patch needed. `deps.now` lets us
* fast-forward time deterministically for TTL assertions.
*/
import test from "node:test";
import assert from "node:assert/strict";
import {
createOmniRouteProviderHook,
mapRawModelToModelV2,
type OmniRouteRawModelEntry,
type OmniRouteModelsFetcher,
} from "../src/index.js";
const FIXTURE: OmniRouteRawModelEntry[] = [
{
id: "claude-primary",
object: "model",
owned_by: "combo",
capabilities: { tool_calling: true, reasoning: true, vision: true, thinking: true },
context_length: 200000,
max_output_tokens: 64000,
input_modalities: ["text", "image"],
output_modalities: ["text"],
},
{
id: "claude-low",
object: "model",
owned_by: "combo",
capabilities: { tool_calling: true, reasoning: true, vision: true, thinking: false },
context_length: 200000,
max_output_tokens: 64000,
input_modalities: ["text", "image"],
output_modalities: ["text"],
},
{
id: "gemini-3-flash",
object: "model",
owned_by: "google",
capabilities: { tool_calling: true, reasoning: false, vision: true, thinking: false },
context_length: 1000000,
max_output_tokens: 8192,
input_modalities: ["text", "image"],
output_modalities: ["text"],
},
];
function stubFetcher(payload: OmniRouteRawModelEntry[]): OmniRouteModelsFetcher & {
callCount: () => number;
callsBy: () => Array<[string, string]>;
} {
let calls: Array<[string, string]> = [];
const f: OmniRouteModelsFetcher = async (baseURL, apiKey) => {
calls.push([baseURL, apiKey]);
return payload;
};
return Object.assign(f, {
callCount: () => calls.length,
callsBy: () => calls,
});
}
const apiAuth = (key: string, baseURL?: string): unknown =>
baseURL ? { type: "api", key, baseURL } : { type: "api", key };
test("createOmniRouteProviderHook: default providerId is 'omniroute'", () => {
const hook = createOmniRouteProviderHook(undefined, { combosFetcher: async () => [] });
assert.equal(hook.id, "opencode-omniroute");
});
test("createOmniRouteProviderHook: custom providerId binds to hook.id (multi-instance)", () => {
const a = createOmniRouteProviderHook(
{ providerId: "omniroute-preprod" },
{ combosFetcher: async () => [] }
);
const b = createOmniRouteProviderHook(
{ providerId: "omniroute-local" },
{ combosFetcher: async () => [] }
);
assert.equal(a.id, "opencode-omniroute-preprod");
assert.equal(b.id, "opencode-omniroute-local");
});
test("models: extracts apiKey from ctx.auth (type=api) and calls fetcher with it", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher, combosFetcher: async () => [] }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-abc") as never });
assert.equal(fetcher.callCount(), 1);
assert.deepEqual(fetcher.callsBy()[0], ["https://or.example.com/v1", "sk-abc"]);
assert.equal(Object.keys(out).length, 3);
assert.ok(out["opencode-omniroute/claude-primary"]);
});
test("models: returns {} when ctx.auth is null/undefined/wrong-type/empty-key", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1" },
{ fetcher, combosFetcher: async () => [] }
);
assert.deepEqual(await hook.models!({} as never, {} as never), {});
assert.deepEqual(await hook.models!({} as never, { auth: undefined } as never), {});
assert.deepEqual(
await hook.models!({} as never, {
auth: { type: "oauth", refresh: "r", access: "a", expires: 0 } as never,
}),
{}
);
assert.deepEqual(
await hook.models!({} as never, { auth: { type: "api", key: "" } as never }),
{}
);
assert.equal(fetcher.callCount(), 0, "fetcher must not be called on auth rejection");
});
test("models: returns {} when no baseURL resolvable (no opts.baseURL and no auth.baseURL)", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook({}, { fetcher, combosFetcher: async () => [] });
// valid api auth but neither opts nor auth carries a baseURL
assert.deepEqual(await hook.models!({} as never, { auth: apiAuth("sk-x") as never }), {});
assert.equal(fetcher.callCount(), 0);
});
test("models: baseURL falls back to auth.baseURL when opts.baseURL absent", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook({}, { fetcher, combosFetcher: async () => [] });
const out = await hook.models!({} as never, {
auth: apiAuth("sk-y", "https://or.creds-attached.example/v1") as never,
});
assert.equal(fetcher.callCount(), 1);
assert.equal(fetcher.callsBy()[0][0], "https://or.creds-attached.example/v1");
assert.equal(Object.keys(out).length, 3);
});
test("models: maps a sample /v1/models entry to ModelV2 (sanity)", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook(
{ providerId: "omniroute", baseURL: "https://or.example.com/v1" },
{ fetcher, combosFetcher: async () => [] }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-abc") as never });
const claude = out["opencode-omniroute/claude-primary"];
assert.ok(claude, "claude-primary present");
// `mapRawModelToModelV2` stamps the provider prefix on the id so OC's
// static-catalog reader resolves `(providerID, modelID)` from the key.
assert.equal(claude.id, "opencode-omniroute/claude-primary");
assert.equal(claude.name, "claude-primary");
assert.equal(claude.providerID, "opencode-omniroute");
assert.equal(claude.api.id, "openai-compatible");
assert.equal(claude.api.url, "https://or.example.com/v1");
assert.equal(claude.api.npm, "@ai-sdk/openai-compatible");
// capabilities: toolcall (one word), reasoning OR thinking, attachment = vision
assert.equal(claude.capabilities.toolcall, true);
assert.equal(claude.capabilities.reasoning, true);
assert.equal(claude.capabilities.attachment, true);
assert.equal(claude.capabilities.temperature, true);
// modalities mapped from arrays
assert.equal(claude.capabilities.input.text, true);
assert.equal(claude.capabilities.input.image, true);
assert.equal(claude.capabilities.input.audio, false);
assert.equal(claude.capabilities.output.text, true);
assert.equal(claude.capabilities.output.image, false);
// cost is zeroed (OmniRoute /v1/models has no pricing)
assert.deepEqual(claude.cost, { input: 0, output: 0, cache: { read: 0, write: 0 } });
// limits
assert.equal(claude.limit.context, 200000);
assert.equal(claude.limit.output, 64000);
assert.equal(claude.status, "active");
});
test("mapRawModelToModelV2: thinking-only model still surfaces reasoning=true", () => {
const m = mapRawModelToModelV2(
{
id: "thinking-only",
capabilities: { thinking: true, reasoning: false },
context_length: 100000,
max_output_tokens: 8192,
},
{ providerId: "omniroute", baseURL: "https://or.example.com/v1" }
);
assert.equal(m.capabilities.reasoning, true);
});
test("mapRawModelToModelV2: missing capabilities defaults to all-false (except temperature)", () => {
const m = mapRawModelToModelV2(
{ id: "minimal" },
{ providerId: "omniroute", baseURL: "https://or.example.com/v1" }
);
assert.equal(m.capabilities.temperature, true);
assert.equal(m.capabilities.reasoning, false);
assert.equal(m.capabilities.attachment, false);
assert.equal(m.capabilities.toolcall, false);
// default modalities = text only
assert.equal(m.capabilities.input.text, true);
assert.equal(m.capabilities.output.text, true);
// missing context / output tokens → 0 fallback (ModelV2.limit.{context,output} required)
assert.equal(m.limit.context, 0);
assert.equal(m.limit.output, 0);
});
test("models: caches result for second call within TTL (fetcher called once)", async () => {
const fetcher = stubFetcher(FIXTURE);
let nowMs = 1_000_000;
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1", modelCacheTtl: 60_000 },
{ fetcher, now: () => nowMs, combosFetcher: async () => [] }
);
const a = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
nowMs += 30_000; // half the TTL
const b = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.equal(fetcher.callCount(), 1, "second call within TTL must hit the cache");
assert.equal(Object.keys(a).length, 3);
assert.equal(Object.keys(b).length, 3);
});
test("models: refetches after TTL expires", async () => {
const fetcher = stubFetcher(FIXTURE);
let nowMs = 1_000_000;
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1", modelCacheTtl: 60_000 },
{ fetcher, now: () => nowMs, combosFetcher: async () => [] }
);
await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
nowMs += 60_001; // just past the TTL
await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.equal(fetcher.callCount(), 2, "call past TTL must refetch");
});
test("models: caches per (baseURL, apiKey) tuple (different keys → independent fetches)", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook(
{ baseURL: "https://or.example.com/v1", modelCacheTtl: 300_000 },
{ fetcher, combosFetcher: async () => [] }
);
await hook.models!({} as never, { auth: apiAuth("sk-A") as never });
await hook.models!({} as never, { auth: apiAuth("sk-B") as never });
await hook.models!({} as never, { auth: apiAuth("sk-A") as never }); // cached
await hook.models!({} as never, { auth: apiAuth("sk-B") as never }); // cached
assert.equal(fetcher.callCount(), 2, "one fetch per distinct apiKey, then cache hits");
});
test("models: caches per (baseURL, apiKey) tuple (different baseURL → independent fetches)", async () => {
const fetcher = stubFetcher(FIXTURE);
const hook = createOmniRouteProviderHook(
{ modelCacheTtl: 300_000 }, // no opts.baseURL → falls back to auth.baseURL
{ fetcher, combosFetcher: async () => [] }
);
await hook.models!({} as never, { auth: apiAuth("sk-same", "https://prod.example/v1") as never });
await hook.models!({} as never, {
auth: apiAuth("sk-same", "https://preprod.example/v1") as never,
});
await hook.models!({} as never, { auth: apiAuth("sk-same", "https://prod.example/v1") as never }); // cached
assert.equal(fetcher.callCount(), 2, "distinct baseURLs share apiKey but not cache");
});
@@ -0,0 +1,73 @@
import test from "node:test";
import assert from "node:assert/strict";
import {
OmniRoutePlugin,
OMNIROUTE_PROVIDER_KEY,
DEFAULT_MODEL_CACHE_TTL_MS,
resolveOmniRoutePluginOptions,
} from "../src/index.js";
test("scaffold: exports public surface", () => {
assert.equal(
typeof OmniRoutePlugin,
"function",
"OmniRoutePlugin must be a function (Plugin factory)"
);
assert.equal(OMNIROUTE_PROVIDER_KEY, "omniroute");
assert.equal(DEFAULT_MODEL_CACHE_TTL_MS, 300_000);
});
test("scaffold: default export is v1 plugin shape { id, server: OmniRoutePlugin }", async () => {
const mod = await import("../src/index.js");
assert.equal(typeof mod.default, "object");
assert.equal(mod.default.id, "@omniroute/opencode-plugin");
assert.equal(mod.default.server, mod.OmniRoutePlugin);
});
test("resolveOmniRoutePluginOptions: defaults", () => {
const r = resolveOmniRoutePluginOptions();
assert.equal(r.providerId, "opencode-omniroute");
assert.equal(r.displayName, "OmniRoute");
assert.equal(r.modelCacheTtl, 300_000);
assert.equal(r.baseURL, undefined);
});
test("resolveOmniRoutePluginOptions: custom providerId derives displayName", () => {
const r = resolveOmniRoutePluginOptions({ providerId: "omniroute-preprod" });
assert.equal(r.providerId, "opencode-omniroute-preprod");
assert.equal(r.displayName, "OmniRoute (opencode-omniroute-preprod)");
});
test("resolveOmniRoutePluginOptions: explicit displayName wins", () => {
const r = resolveOmniRoutePluginOptions({
providerId: "omniroute-x",
displayName: "Custom Label",
});
assert.equal(r.displayName, "Custom Label");
});
test("resolveOmniRoutePluginOptions: invalid TTL falls back to default", () => {
assert.equal(resolveOmniRoutePluginOptions({ modelCacheTtl: 0 }).modelCacheTtl, 300_000);
assert.equal(resolveOmniRoutePluginOptions({ modelCacheTtl: -1 }).modelCacheTtl, 300_000);
});
test("resolveOmniRoutePluginOptions: positive TTL respected", () => {
assert.equal(resolveOmniRoutePluginOptions({ modelCacheTtl: 60_000 }).modelCacheTtl, 60_000);
});
test("OmniRoutePlugin: returns an empty hooks object (scaffold)", async () => {
const fakeCtx = {} as Parameters<typeof OmniRoutePlugin>[0];
const hooks = await OmniRoutePlugin(fakeCtx);
assert.equal(typeof hooks, "object");
assert.notEqual(hooks, null);
});
test("scaffold: built ESM default export resolves with the v1 plugin shape", async () => {
// The plugin is ESM-only now — the CJS bundle was dropped to fix the OpenCode
// loader (#3883), so there is no more ../dist/index.cjs. Validate that the built
// distributable's default export still carries the OpenCode v1 { id, server } shape.
const mod = await import("../dist/index.js");
assert.strictEqual(typeof mod.default, "object");
assert.strictEqual(mod.default.id, "@omniroute/opencode-plugin");
assert.strictEqual(typeof mod.default.server, "function");
});
@@ -0,0 +1,85 @@
/**
* Regression tests for `isUsableCombo` (release/v3.8.2 code review, finding C1).
*
* The combo member refs returned by `/api/combos` do NOT carry a separate
* `providerId` field — OmniRoute's `normalizeComboRecord` folds the provider
* id INTO the full model string (e.g. "cc/claude-opus-4-7"). The previous
* implementation read `step.providerId` (always `undefined`), so the
* `usableOnly` combo filter silently never dropped anything. These tests pin
* the corrected behavior: the verdict is derived from the `step.model` prefix,
* mirroring `isUsableRawModelId`'s subtract-filter semantics.
*/
import test from "node:test";
import assert from "node:assert/strict";
import { isUsableCombo, type OmniRouteRawCombo } from "../src/index.js";
/** Build a `usable` set bundle for the tests. */
function buildUsable(opts: { aliases?: string[]; canonicals?: string[]; known?: string[] }): {
aliases: Set<string>;
canonicals: Set<string>;
knownAliases: Set<string>;
} {
return {
aliases: new Set(opts.aliases ?? []),
canonicals: new Set(opts.canonicals ?? []),
// knownAliases is the union of every prefix the universe is aware of —
// usable or not. Default to including the usable aliases too.
knownAliases: new Set([...(opts.known ?? []), ...(opts.aliases ?? [])]),
};
}
function combo(models: OmniRouteRawCombo["models"]): OmniRouteRawCombo {
return { id: "c1", name: "Test Combo", models };
}
test("isUsableCombo: member with a usable alias prefix → keep", () => {
const usable = buildUsable({ aliases: ["cc"], known: ["cc", "dead"] });
const c = combo([{ kind: "model", model: "cc/claude-opus-4-7" }]);
assert.equal(isUsableCombo(c, usable), true);
});
test("isUsableCombo: all members known-but-NOT-usable → drop (the C1 regression)", () => {
// Before the fix this returned true unconditionally because step.providerId
// was always undefined. Now the known-but-unusable "dead" prefix is dropped.
const usable = buildUsable({ aliases: ["cc"], known: ["cc", "dead"] });
const c = combo([
{ kind: "model", model: "dead/legacy-model" },
{ kind: "model", model: "dead/another" },
]);
assert.equal(isUsableCombo(c, usable), false);
});
test("isUsableCombo: unknown prefix → keep (cannot prove unroutable)", () => {
const usable = buildUsable({ aliases: ["cc"], known: ["cc", "dead"] });
const c = combo([{ kind: "model", model: "agentrouter/mystery" }]);
assert.equal(isUsableCombo(c, usable), true);
});
test("isUsableCombo: mixed non-usable + usable member → keep", () => {
const usable = buildUsable({ aliases: ["cc"], known: ["cc", "dead"] });
const c = combo([
{ kind: "model", model: "dead/legacy" },
{ kind: "model", model: "cc/claude-opus-4-7" },
]);
assert.equal(isUsableCombo(c, usable), true);
});
test("isUsableCombo: zero members → keep", () => {
const usable = buildUsable({ aliases: ["cc"], known: ["cc"] });
assert.equal(isUsableCombo(combo([]), usable), true);
assert.equal(isUsableCombo(combo(undefined), usable), true);
});
test("isUsableCombo: only combo-ref steps (no resolvable model) → keep", () => {
const usable = buildUsable({ aliases: ["cc"], known: ["cc", "dead"] });
const c = combo([{ kind: "combo-ref", comboName: "nested" }]);
assert.equal(isUsableCombo(c, usable), true);
});
test("isUsableCombo: usable canonical prefix → keep", () => {
const usable = buildUsable({ canonicals: ["anthropic"], known: ["anthropic", "dead"] });
const c = combo([{ kind: "model", model: "anthropic/claude-opus-4-7" }]);
assert.equal(isUsableCombo(c, usable), true);
});