| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| 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, "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, "omniroute-preprod");
|
| assert.equal(b.id, "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["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 () => [] });
|
|
|
| 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["claude-primary"];
|
| assert.ok(claude, "claude-primary present");
|
| assert.equal(claude.id, "claude-primary");
|
| assert.equal(claude.name, "claude-primary");
|
| assert.equal(claude.providerID, "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");
|
|
|
| assert.equal(claude.capabilities.toolcall, true);
|
| assert.equal(claude.capabilities.reasoning, true);
|
| assert.equal(claude.capabilities.attachment, true);
|
| assert.equal(claude.capabilities.temperature, true);
|
|
|
| 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);
|
|
|
| assert.deepEqual(claude.cost, { input: 0, output: 0, cache: { read: 0, write: 0 } });
|
|
|
| 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);
|
|
|
| assert.equal(m.capabilities.input.text, true);
|
| assert.equal(m.capabilities.output.text, true);
|
|
|
| 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;
|
| 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;
|
| 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 });
|
| await hook.models!({} as never, { auth: apiAuth("sk-B") as never });
|
| 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 },
|
| { 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 });
|
| assert.equal(fetcher.callCount(), 2, "distinct baseURLs share apiKey but not cache");
|
| });
|
|
|