/** * 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( fn: (warnings: Array<{ args: unknown[] }>) => Promise ): 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-`. 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}` ); });