Spaces:
Runtime error
Runtime error
| /** | |
| * 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"); | |
| }); | |