fix(plugin): auto-prefix providerId with 'opencode-' for OC 1.17.8+ native gate

OC 1.17.8+ native-adapter gate (Ji() in bundled client) rejects any
model whose providerID is not exactly 'openai', 'anthropic', or does
not start with 'opencode'. The plugin's default providerId ('omniroute')
fails this gate, so every model dispatch returns 'Unable to determine
provider'. Auto-prefix in resolveOmniRoutePluginOptions so existing
opencode.json configs (providerId: 'omniroute') keep working without
user-facing changes — internally every catalog key, ModelV2 field,
and auth hook sees 'opencode-omniroute'.

The prefix also fixes the auth.json key mismatch: the resolved
(opencode-omniroute) key is what OC 1.17.8+ dispatcher reads when
looking up credentials.
This commit is contained in:
herjarsa
2026-06-21 17:34:08 +02:00
parent bfaf459f3c
commit 75b52e2867
8 changed files with 204 additions and 156 deletions

View File

@@ -240,10 +240,18 @@ export function resolveOmniRoutePluginOptions(
opts?: OmniRoutePluginOptions opts?: OmniRoutePluginOptions
): Required<Pick<OmniRoutePluginOptions, "providerId" | "displayName" | "modelCacheTtl">> & ): Required<Pick<OmniRoutePluginOptions, "providerId" | "displayName" | "modelCacheTtl">> &
Pick<OmniRoutePluginOptions, "baseURL" | "features"> { Pick<OmniRoutePluginOptions, "baseURL" | "features"> {
const providerId = opts?.providerId ?? OMNIROUTE_PROVIDER_KEY; const rawProviderId = opts?.providerId ?? OMNIROUTE_PROVIDER_KEY;
// OC 1.17.8+ native-adapter gate rejects providerID not in
// {openai, anthropic, opencode*}. Silently prefix so existing
// configs (providerId: "omniroute") keep working.
const providerId = rawProviderId.startsWith("opencode-")
? rawProviderId
: `opencode-${rawProviderId}`;
const displayName = const displayName =
opts?.displayName ?? opts?.displayName ??
(providerId === OMNIROUTE_PROVIDER_KEY ? "OmniRoute" : `OmniRoute (${providerId})`); (providerId === `opencode-${OMNIROUTE_PROVIDER_KEY}`
? "OmniRoute"
: `OmniRoute (${providerId})`);
const modelCacheTtl = const modelCacheTtl =
typeof opts?.modelCacheTtl === "number" && opts.modelCacheTtl > 0 typeof opts?.modelCacheTtl === "number" && opts.modelCacheTtl > 0
? opts.modelCacheTtl ? opts.modelCacheTtl

View File

@@ -13,12 +13,12 @@ import { createOmniRouteAuthHook } from "../src/index.js";
test("createOmniRouteAuthHook: default providerId is 'omniroute'", () => { test("createOmniRouteAuthHook: default providerId is 'omniroute'", () => {
const hook = createOmniRouteAuthHook(); const hook = createOmniRouteAuthHook();
assert.equal(hook.provider, "omniroute"); assert.equal(hook.provider, "opencode-omniroute");
}); });
test("createOmniRouteAuthHook: custom providerId binds to hook.provider (multi-instance)", () => { test("createOmniRouteAuthHook: custom providerId binds to hook.provider (multi-instance)", () => {
const hook = createOmniRouteAuthHook({ providerId: "omniroute-preprod" }); const hook = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
assert.equal(hook.provider, "omniroute-preprod"); assert.equal(hook.provider, "opencode-omniroute-preprod");
}); });
test("createOmniRouteAuthHook: methods[0] is type 'api' with label including displayName", () => { test("createOmniRouteAuthHook: methods[0] is type 'api' with label including displayName", () => {
@@ -30,7 +30,7 @@ test("createOmniRouteAuthHook: methods[0] is type 'api' with label including dis
assert.equal(m.label, "OmniRoute API Key"); assert.equal(m.label, "OmniRoute API Key");
const custom = createOmniRouteAuthHook({ providerId: "omniroute-preprod" }); const custom = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
assert.equal(custom.methods[0].label, "OmniRoute (omniroute-preprod) API Key"); assert.equal(custom.methods[0].label, "OmniRoute (opencode-omniroute-preprod) API Key");
}); });
test("createOmniRouteAuthHook: prompts[0] uses key='apiKey' per @opencode-ai/plugin contract", () => { test("createOmniRouteAuthHook: prompts[0] uses key='apiKey' per @opencode-ai/plugin contract", () => {

View File

@@ -447,14 +447,14 @@ test("models() returns combo entries merged into the map", async () => {
// 3 raw models + 1 combo = 4 entries // 3 raw models + 1 combo = 4 entries
assert.equal(Object.keys(out).length, 4); assert.equal(Object.keys(out).length, 4);
assert.ok(out["omniroute/claude-primary"]); assert.ok(out["opencode-omniroute/claude-primary"]);
assert.ok(out["omniroute/claude-secondary"]); assert.ok(out["opencode-omniroute/claude-secondary"]);
assert.ok(out["omniroute/gemini-3-flash"]); assert.ok(out["opencode-omniroute/gemini-3-flash"]);
assert.ok(out["omniroute/claude-tier"]); assert.ok(out["opencode-omniroute/claude-tier"]);
const combo = out["omniroute/claude-tier"]; const combo = out["opencode-omniroute/claude-tier"];
assert.equal(combo.name, "Claude Tier"); assert.equal(combo.name, "Claude Tier");
assert.equal(combo.providerID, "omniroute"); assert.equal(combo.providerID, "opencode-omniroute");
// LCD over claude-primary (200k, reasoning) + claude-secondary (100k, no reasoning) // LCD over claude-primary (200k, reasoning) + claude-secondary (100k, no reasoning)
assert.equal(combo.limit.context, 100_000); assert.equal(combo.limit.context, 100_000);
assert.equal(combo.capabilities.reasoning, false); assert.equal(combo.capabilities.reasoning, false);
@@ -478,11 +478,11 @@ test("models(): combo with unknown member ids degrades to all-false LCD posture"
{ fetcher: modelsFetcher, combosFetcher } { fetcher: modelsFetcher, combosFetcher }
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.ok(out["omniroute/phantom-combo"]); assert.ok(out["opencode-omniroute/phantom-combo"]);
// With zero resolvable members, LCD = all-false (defensive posture). // With zero resolvable members, LCD = all-false (defensive posture).
assert.equal(out["omniroute/phantom-combo"].capabilities.toolcall, false); assert.equal(out["opencode-omniroute/phantom-combo"].capabilities.toolcall, false);
assert.equal(out["omniroute/phantom-combo"].capabilities.reasoning, false); assert.equal(out["opencode-omniroute/phantom-combo"].capabilities.reasoning, false);
assert.equal(out["omniroute/phantom-combo"].limit.context, 0); assert.equal(out["opencode-omniroute/phantom-combo"].limit.context, 0);
}); });
test("models(): hidden combos are excluded from the map", async () => { test("models(): hidden combos are excluded from the map", async () => {
@@ -505,8 +505,8 @@ test("models(): hidden combos are excluded from the map", async () => {
{ fetcher: modelsFetcher, combosFetcher } { fetcher: modelsFetcher, combosFetcher }
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.ok(out["omniroute/visible"]); assert.ok(out["opencode-omniroute/visible"]);
assert.ok(!out["omniroute/hidden"], "hidden combo must be omitted"); 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 () => { test("models(): combo name exactly matches raw model id → raw deleted, raw deleted, no warn", async () => {
@@ -530,8 +530,8 @@ test("models(): combo name exactly matches raw model id → raw deleted, raw del
}); });
// Raw model replaced by combo of the same key; combo now lives at the bare slug. // Raw model replaced by combo of the same key; combo now lives at the bare slug.
assert.ok(out["omniroute/claude-primary"], "combo surfaces under prefixed key"); assert.ok(out["opencode-omniroute/claude-primary"], "combo surfaces under prefixed key");
assert.equal(out["omniroute/claude-primary"].name, "claude-primary"); assert.equal(out["opencode-omniroute/claude-primary"].name, "claude-primary");
// No collision warning fires — dedup makes keys disjoint. // No collision warning fires — dedup makes keys disjoint.
const collisionWarns = warnings.filter((w) => { const collisionWarns = warnings.filter((w) => {
@@ -565,8 +565,8 @@ test("models(): two combos with same slug → second gets disambiguator suffix",
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
// First combo gets the bare slug; second gets disambiguated. // First combo gets the bare slug; second gets disambiguated.
assert.ok(out["omniroute/claude"], "first combo at prefixed slug"); assert.ok(out["opencode-omniroute/claude"], "first combo at prefixed slug");
assert.ok(out["omniroute/claude-uuid"], "second combo disambiguated by id prefix"); 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 () => { test("models(): combos fetch fails → falls back to models-only, warn emitted, no throw", async () => {
@@ -583,8 +583,8 @@ test("models(): combos fetch fails → falls back to models-only, warn emitted,
// Catalog includes the models but NOT any combo entries. // Catalog includes the models but NOT any combo entries.
assert.equal(Object.keys(out).length, 2); assert.equal(Object.keys(out).length, 2);
assert.ok(out["omniroute/claude-primary"]); assert.ok(out["opencode-omniroute/claude-primary"]);
assert.ok(out["omniroute/claude-secondary"]); assert.ok(out["opencode-omniroute/claude-secondary"]);
// Soft-fail warning surfaced. // Soft-fail warning surfaced.
const softFail = warnings.find((w) => { const softFail = warnings.find((w) => {
@@ -609,7 +609,7 @@ test("models(): combos cached + reused within TTL (one combo fetch per TTL windo
const second = await hook.models!({} as never, { auth: apiAuth("sk-z") as never }); 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(combosFetcher.callCount(), 1, "combos fetched only once within TTL");
assert.equal(modelsFetcher.callCount(), 1, "models fetched only once within TTL"); assert.equal(modelsFetcher.callCount(), 1, "models fetched only once within TTL");
assert.ok(second["omniroute/claude-tier"]); assert.ok(second["opencode-omniroute/claude-tier"]);
}); });
test("models(): combos refetched after TTL expiry (same key as models)", async () => { test("models(): combos refetched after TTL expiry (same key as models)", async () => {
@@ -701,7 +701,7 @@ test("models(): nested combo-ref context is the min of nested + raw members", as
{ fetcher: modelsFetcher, combosFetcher } { fetcher: modelsFetcher, combosFetcher }
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
const masterLight = out["omniroute/master-light"]; const masterLight = out["opencode-omniroute/master-light"];
assert.ok(masterLight, "MASTER-LIGHT entry must exist"); assert.ok(masterLight, "MASTER-LIGHT entry must exist");
assert.equal( assert.equal(
masterLight.limit.context, masterLight.limit.context,

View File

@@ -203,7 +203,7 @@ function makeInput(initialProvider: Record<string, unknown> = {}): Config {
test("config: with valid auth.json + apiKey + baseURL → mutates input.provider[id] with stripped models block", async () => { test("config: with valid auth.json + apiKey + baseURL → mutates input.provider[id] with stripped models block", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test-1", baseURL: "https://or.example.com/v1" }, "opencode-omniroute": { type: "api", key: "sk-test-1", baseURL: "https://or.example.com/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]); const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]); const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
@@ -217,8 +217,8 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
await hook(input); await hook(input);
const provider = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider; const provider = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider;
const entry = provider.omniroute; const entry = provider["opencode-omniroute"];
assert.ok(entry, "input.provider.omniroute set"); assert.ok(entry, "input.provider['opencode-omniroute'] set");
assert.equal(entry.npm, "@ai-sdk/openai-compatible"); assert.equal(entry.npm, "@ai-sdk/openai-compatible");
assert.equal(entry.name, "OmniRoute"); assert.equal(entry.name, "OmniRoute");
assert.equal(entry.options.baseURL, "https://or.example.com/v1"); assert.equal(entry.options.baseURL, "https://or.example.com/v1");
@@ -227,7 +227,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
// Stripped per-model shape: name + cap flags + modalities + (optional) // Stripped per-model shape: name + cap flags + modalities + (optional)
// cost. OC's SDK static schema accepts only `limit.{context,output}` — // cost. OC's SDK static schema accepts only `limit.{context,output}` —
// `limit.input` is NOT in the SDK shape and gets dropped silently. // `limit.input` is NOT in the SDK shape and gets dropped silently.
const claude = entry.models["omniroute/claude-sonnet-4-6"]; const claude = entry.models["opencode-omniroute/claude-sonnet-4-6"];
assert.ok(claude, "claude model surfaced"); assert.ok(claude, "claude model surfaced");
assert.equal(claude.name, "claude-sonnet-4-6"); assert.equal(claude.name, "claude-sonnet-4-6");
assert.equal(claude.attachment, true); assert.equal(claude.attachment, true);
@@ -248,7 +248,7 @@ test("config: with valid auth.json + apiKey + baseURL → mutates input.provider
// Combo surfaces under bare key + LCD'd // Combo surfaces under bare key + LCD'd
// (gemini's reasoning=false → combo reasoning=false). // (gemini's reasoning=false → combo reasoning=false).
const combo = entry.models["omniroute/claude-tier"]; const combo = entry.models["opencode-omniroute/claude-tier"];
assert.ok(combo, "combo surfaced under bare key"); assert.ok(combo, "combo surfaced under bare key");
assert.equal(combo.name, "Claude Tier"); assert.equal(combo.name, "Claude Tier");
assert.equal(combo.reasoning, false, "LCD: any member reasoning=false → combo reasoning=false"); assert.equal(combo.reasoning, false, "LCD: any member reasoning=false → combo reasoning=false");
@@ -323,7 +323,7 @@ test("config: existing input.provider[id] → no overwrite (respect manual overr
models: { "manual-model": { name: "manual-model" } }, models: { "manual-model": { name: "manual-model" } },
}; };
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -333,11 +333,11 @@ test("config: existing input.provider[id] → no overwrite (respect manual overr
{ providerId: "omniroute" }, { providerId: "omniroute" },
{ readAuthJson, fetcher, combosFetcher, logger } { readAuthJson, fetcher, combosFetcher, logger }
); );
const input = makeInput({ omniroute: manual }); const input = makeInput({ "opencode-omniroute": manual });
await hook(input); await hook(input);
const provider = (input as { provider: Record<string, unknown> }).provider; const provider = (input as { provider: Record<string, unknown> }).provider;
assert.equal(provider.omniroute, manual, "manual override preserved by reference"); assert.equal(provider["opencode-omniroute"], manual, "manual override preserved by reference");
assert.equal(fetcher.callCount(), 0, "no fetch — short-circuited before I/O"); assert.equal(fetcher.callCount(), 0, "no fetch — short-circuited before I/O");
assert.equal(readAuthJson.callCount(), 0, "no auth.json read either"); assert.equal(readAuthJson.callCount(), 0, "no auth.json read either");
assert.ok( assert.ok(
@@ -352,7 +352,7 @@ test("config: existing input.provider[id] → no overwrite (respect manual overr
test("config: fetchers throw → warn + emit stub entry with models: {}", async () => { test("config: fetchers throw → warn + emit stub entry with models: {}", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = throwingModelsFetcher(); const fetcher = throwingModelsFetcher();
const combosFetcher = throwingCombosFetcher(); const combosFetcher = throwingCombosFetcher();
@@ -368,8 +368,9 @@ test("config: fetchers throw → warn + emit stub entry with models: {}", async
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry, "stub provider entry published even when fetchers fail"); assert.ok(entry, "stub provider entry published even when fetchers fail");
assert.equal(entry.npm, "@ai-sdk/openai-compatible"); assert.equal(entry.npm, "@ai-sdk/openai-compatible");
assert.deepEqual(entry.models, {}, "models stub is empty object"); assert.deepEqual(entry.models, {}, "models stub is empty object");
@@ -392,7 +393,7 @@ test("config: fetchers throw → warn + emit stub entry with models: {}", async
test("config: combos fetcher throws → emit models-only catalog (no combos in models block)", async () => { test("config: combos fetcher throws → emit models-only catalog (no combos in models block)", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]); const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]);
const combosFetcher = throwingCombosFetcher(); const combosFetcher = throwingCombosFetcher();
@@ -405,12 +406,16 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry); assert.ok(entry);
const ids = Object.keys(entry.models).sort(); const ids = Object.keys(entry.models).sort();
assert.deepEqual(ids, ["omniroute/claude-sonnet-4-6", "omniroute/gemini-3-flash"]); assert.deepEqual(ids, [
assert.equal(entry.models["omniroute/claude-tier"], undefined, "no combo entry"); "opencode-omniroute/claude-sonnet-4-6",
"opencode-omniroute/gemini-3-flash",
]);
assert.equal(entry.models["opencode-omniroute/claude-tier"], undefined, "no combo entry");
assert.ok( assert.ok(
logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")), logger.entries.some((e) => String(e[0]).includes("/api/combos fetch failed")),
"combos-fetch breadcrumb emitted" "combos-fetch breadcrumb emitted"
@@ -423,7 +428,7 @@ test("config: combos fetcher throws → emit models-only catalog (no combos in m
test("config: baseURL from auth.json takes precedence when opts.baseURL absent", async () => { test("config: baseURL from auth.json takes precedence when opts.baseURL absent", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://creds.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://creds.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -437,14 +442,15 @@ test("config: baseURL from auth.json takes precedence when opts.baseURL absent",
await hook(input); await hook(input);
assert.equal(fetcher.callsBy()[0][0], "https://creds.example/v1"); assert.equal(fetcher.callsBy()[0][0], "https://creds.example/v1");
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.equal(entry.options.baseURL, "https://creds.example/v1"); assert.equal(entry.options.baseURL, "https://creds.example/v1");
}); });
test("config: opts.baseURL wins over auth.json's stored baseURL", async () => { test("config: opts.baseURL wins over auth.json's stored baseURL", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://creds.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://creds.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -458,14 +464,15 @@ test("config: opts.baseURL wins over auth.json's stored baseURL", async () => {
await hook(input); await hook(input);
assert.equal(fetcher.callsBy()[0][0], "https://opts.example/v1"); assert.equal(fetcher.callsBy()[0][0], "https://opts.example/v1");
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.equal(entry.options.baseURL, "https://opts.example/v1"); assert.equal(entry.options.baseURL, "https://opts.example/v1");
}); });
test("config: no baseURL resolvable (no opts, no auth.json baseURL) → no-op", async () => { test("config: no baseURL resolvable (no opts, no auth.json baseURL) → no-op", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test" }, // NO baseURL on the credential "opencode-omniroute": { type: "api", key: "sk-test" }, // NO baseURL on the credential
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -493,12 +500,12 @@ test("config: no baseURL resolvable (no opts, no auth.json baseURL) → no-op",
test("config: multi-instance — two plugins with different providerIds publish to their own keys without collision", async () => { test("config: multi-instance — two plugins with different providerIds publish to their own keys without collision", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
"omniroute-prod": { "opencode-omniroute-prod": {
type: "api", type: "api",
key: "sk-prod", key: "sk-prod",
baseURL: "https://prod.example/v1", baseURL: "https://prod.example/v1",
}, },
"omniroute-preprod": { "opencode-omniroute-preprod": {
type: "api", type: "api",
key: "sk-preprod", key: "sk-preprod",
baseURL: "https://preprod.example/v1", baseURL: "https://preprod.example/v1",
@@ -522,15 +529,18 @@ test("config: multi-instance — two plugins with different providerIds publish
await hookB(input); await hookB(input);
const provider = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider; const provider = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider;
assert.ok(provider["omniroute-prod"], "prod block present"); assert.ok(provider["opencode-omniroute-prod"], "prod block present");
assert.ok(provider["omniroute-preprod"], "preprod block present"); assert.ok(provider["opencode-omniroute-preprod"], "preprod block present");
assert.equal(provider["omniroute-prod"].options.apiKey, "sk-prod"); assert.equal(provider["opencode-omniroute-prod"].options.apiKey, "sk-prod");
assert.equal(provider["omniroute-preprod"].options.apiKey, "sk-preprod"); assert.equal(provider["opencode-omniroute-preprod"].options.apiKey, "sk-preprod");
assert.equal(provider["omniroute-prod"].options.baseURL, "https://prod.example/v1"); assert.equal(provider["opencode-omniroute-prod"].options.baseURL, "https://prod.example/v1");
assert.equal(provider["omniroute-preprod"].options.baseURL, "https://preprod.example/v1"); assert.equal(
provider["opencode-omniroute-preprod"].options.baseURL,
"https://preprod.example/v1"
);
assert.notEqual( assert.notEqual(
provider["omniroute-prod"], provider["opencode-omniroute-prod"],
provider["omniroute-preprod"], provider["opencode-omniroute-preprod"],
"blocks are distinct references" "blocks are distinct references"
); );
}); });
@@ -542,7 +552,7 @@ test("config: multi-instance — two plugins with different providerIds publish
test("config + provider share cache: second call uses cached fetch result (single fetch per TTL)", async () => { test("config + provider share cache: second call uses cached fetch result (single fetch per TTL)", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]); const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
@@ -574,7 +584,7 @@ test("config + provider share cache: second call uses cached fetch result (singl
test("provider → config order also dedupes (cache populated by provider, consumed by config)", async () => { test("provider → config order also dedupes (cache populated by provider, consumed by config)", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-reverse", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-reverse", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -654,7 +664,7 @@ test("buildStaticProviderEntry: stripped per-model shape matches sibling @omniro
} }
// Sanity: claude entry has all expected stripped fields. // Sanity: claude entry has all expected stripped fields.
const claude = block.models["omniroute/claude-sonnet-4-6"]; const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
assert.equal(typeof claude.name, "string"); assert.equal(typeof claude.name, "string");
assert.equal(typeof claude.attachment, "boolean"); assert.equal(typeof claude.attachment, "boolean");
assert.equal(typeof claude.reasoning, "boolean"); assert.equal(typeof claude.reasoning, "boolean");
@@ -679,8 +689,8 @@ test("buildStaticProviderEntry: hidden combos are excluded", () => {
"https://or.example/v1", "https://or.example/v1",
"sk-test" "sk-test"
); );
assert.equal(block.models["omniroute/claude-tier"], undefined); assert.equal(block.models["opencode-omniroute/claude-tier"], undefined);
assert.ok(block.models["omniroute/claude-sonnet-4-6"]); assert.ok(block.models["opencode-omniroute/claude-sonnet-4-6"]);
}); });
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────
@@ -696,7 +706,7 @@ test("buildStaticProviderEntry: emits modalities.input from raw.input_modalities
"https://or.example/v1", "https://or.example/v1",
"sk-test" "sk-test"
); );
const claude = block.models["omniroute/claude-sonnet-4-6"]; const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
assert.deepEqual(claude.modalities?.input, ["text", "image"]); assert.deepEqual(claude.modalities?.input, ["text", "image"]);
assert.deepEqual(claude.modalities?.output, ["text"]); assert.deepEqual(claude.modalities?.output, ["text"]);
}); });
@@ -710,7 +720,7 @@ test("buildStaticProviderEntry: never emits limit.input (OC SDK rejects it)", ()
"https://or.example/v1", "https://or.example/v1",
"sk-test" "sk-test"
); );
const claude = block.models["omniroute/claude-sonnet-4-6"]; const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
assert.equal((claude.limit as Record<string, unknown>).input, undefined); assert.equal((claude.limit as Record<string, unknown>).input, undefined);
assert.equal(typeof claude.limit?.context, "number"); assert.equal(typeof claude.limit?.context, "number");
assert.equal(typeof claude.limit?.output, "number"); assert.equal(typeof claude.limit?.output, "number");
@@ -738,7 +748,7 @@ test("buildStaticProviderEntry: emits cost when enrichment carries pricing", ()
"sk-test", "sk-test",
enrichment enrichment
); );
const claude = block.models["omniroute/claude-sonnet-4-6"]; const claude = block.models["opencode-omniroute/claude-sonnet-4-6"];
assert.equal(claude.cost?.input, 3); assert.equal(claude.cost?.input, 3);
assert.equal(claude.cost?.output, 15); assert.equal(claude.cost?.output, 15);
assert.equal(claude.cost?.cache_read, 0.3); assert.equal(claude.cost?.cache_read, 0.3);
@@ -759,8 +769,8 @@ test("buildStaticProviderEntry: emits release_date when raw carries it; omits wh
"https://or.example/v1", "https://or.example/v1",
"sk-test" "sk-test"
); );
assert.equal(block.models["omniroute/claude-with-date"].release_date, "2026-02-19"); assert.equal(block.models["opencode-omniroute/claude-with-date"].release_date, "2026-02-19");
assert.equal(block.models["omniroute/gemini-3-flash"].release_date, undefined); assert.equal(block.models["opencode-omniroute/gemini-3-flash"].release_date, undefined);
}); });
test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)", () => { test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)", () => {
@@ -789,7 +799,7 @@ test("buildStaticProviderEntry: combo modalities = intersection of members (LCD)
"https://or.example/v1", "https://or.example/v1",
"sk-test" "sk-test"
); );
const combo = block.models["omniroute/mixed-tier"]; const combo = block.models["opencode-omniroute/mixed-tier"];
assert.ok(combo, "combo emitted under slug key"); assert.ok(combo, "combo emitted under slug key");
// claude has text+image, text-only has text → intersection drops image. // claude has text+image, text-only has text → intersection drops image.
assert.deepEqual(combo.modalities?.input, ["text"]); assert.deepEqual(combo.modalities?.input, ["text"]);
@@ -813,7 +823,7 @@ test("OmniRoutePlugin factory exposes config hook alongside auth + provider", as
test("config: auth.json entry of wrong type (oauth) → no-op", async () => { test("config: auth.json entry of wrong type (oauth) → no-op", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "oauth", refresh: "r", access: "a", expires: 0 }, "opencode-omniroute": { type: "oauth", refresh: "r", access: "a", expires: 0 },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -850,7 +860,7 @@ test("config: readAuthJson throws → treat as missing file (silent fallback)",
test("config: initialises input.provider when undefined", async () => { test("config: initialises input.provider when undefined", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -865,7 +875,7 @@ test("config: initialises input.provider when undefined", async () => {
await hook(input); await hook(input);
const provider = (input as { provider?: Record<string, unknown> }).provider; const provider = (input as { provider?: Record<string, unknown> }).provider;
assert.ok(provider, "provider bag initialised"); assert.ok(provider, "provider bag initialised");
assert.ok(provider!.omniroute); assert.ok(provider!["opencode-omniroute"]);
}); });
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────
@@ -875,7 +885,7 @@ test("config: initialises input.provider when undefined", async () => {
test("config: enrichment fetched + name overlaid on raw-model entries", async () => { test("config: enrichment fetched + name overlaid on raw-model entries", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]); const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]); const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
@@ -894,19 +904,20 @@ test("config: enrichment fetched + name overlaid on raw-model entries", async ()
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry); assert.ok(entry);
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6"); assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
assert.equal(entry.models["omniroute/gemini-3-flash"].name, "Gemini 3 Flash"); assert.equal(entry.models["opencode-omniroute/gemini-3-flash"].name, "Gemini 3 Flash");
// Combo names still come from /api/combos — enrichment overlay does NOT touch combos. // Combo names still come from /api/combos — enrichment overlay does NOT touch combos.
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier"); assert.equal(entry.models["opencode-omniroute/claude-tier"].name, "Claude Tier");
assert.equal(enrichmentFetcher.callCount(), 1); assert.equal(enrichmentFetcher.callCount(), 1);
}); });
test("config: features.enrichment=false skips enrichment fetch + keeps raw-id names", async () => { test("config: features.enrichment=false skips enrichment fetch + keeps raw-id names", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -924,12 +935,13 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry); assert.ok(entry);
assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag"); assert.equal(enrichmentFetcher.callCount(), 0, "enrichment fetch suppressed by feature flag");
assert.equal( assert.equal(
entry.models["omniroute/claude-sonnet-4-6"].name, entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
"claude-sonnet-4-6", "claude-sonnet-4-6",
"raw id retained" "raw id retained"
); );
@@ -937,7 +949,7 @@ test("config: features.enrichment=false skips enrichment fetch + keeps raw-id na
test("config: enrichment fetcher throws → soft-fail (warn + raw-id static catalog)", async () => { test("config: enrichment fetcher throws → soft-fail (warn + raw-id static catalog)", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -951,11 +963,12 @@ test("config: enrichment fetcher throws → soft-fail (warn + raw-id static cata
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry, "static block still published on enrichment failure"); assert.ok(entry, "static block still published on enrichment failure");
assert.equal( assert.equal(
entry.models["omniroute/claude-sonnet-4-6"].name, entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
"claude-sonnet-4-6", "claude-sonnet-4-6",
"raw id retained" "raw id retained"
); );
@@ -999,7 +1012,7 @@ const MODEL_NV_LLAMA: OmniRouteRawModelEntry = {
test("config: usableOnly=false → no filter (existing behavior)", async () => { test("config: usableOnly=false → no filter (existing behavior)", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CC_OPUS, MODEL_NV_LLAMA]); const fetcher = stubModelsFetcher([MODEL_CC_OPUS, MODEL_NV_LLAMA]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1021,8 +1034,9 @@ test("config: usableOnly=false → no filter (existing behavior)", async () => {
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry.models["cc/claude-opus-4-7"], "claude kept"); assert.ok(entry.models["cc/claude-opus-4-7"], "claude kept");
assert.ok(entry.models["nvidia/llama-3-70b"], "nvidia kept (filter off)"); assert.ok(entry.models["nvidia/llama-3-70b"], "nvidia kept (filter off)");
assert.equal(providersFetcher.callCount(), 0, "providers fetch not called when feature off"); assert.equal(providersFetcher.callCount(), 0, "providers fetch not called when feature off");
@@ -1030,7 +1044,7 @@ test("config: usableOnly=false → no filter (existing behavior)", async () => {
test("config: usableOnly=true → drops models for non-usable providers, keeps usable + unknown", async () => { test("config: usableOnly=true → drops models for non-usable providers, keeps usable + unknown", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([ const fetcher = stubModelsFetcher([
MODEL_CC_OPUS, MODEL_CC_OPUS,
@@ -1070,8 +1084,9 @@ test("config: usableOnly=true → drops models for non-usable providers, keeps u
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry.models["cc/claude-opus-4-7"], "claude kept (active)"); assert.ok(entry.models["cc/claude-opus-4-7"], "claude kept (active)");
assert.equal(entry.models["nvidia/llama-3-70b"], undefined, "nvidia dropped (error status)"); assert.equal(entry.models["nvidia/llama-3-70b"], undefined, "nvidia dropped (error status)");
assert.ok(entry.models["agentrouter/synthetic-1"], "unknown prefix kept (subtract-filter)"); assert.ok(entry.models["agentrouter/synthetic-1"], "unknown prefix kept (subtract-filter)");
@@ -1080,7 +1095,7 @@ test("config: usableOnly=true → drops models for non-usable providers, keeps u
test("config: usableOnly=true + providers fetch fails → soft-fail keeps everything", async () => { test("config: usableOnly=true + providers fetch fails → soft-fail keeps everything", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CC_OPUS, MODEL_NV_LLAMA]); const fetcher = stubModelsFetcher([MODEL_CC_OPUS, MODEL_NV_LLAMA]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1101,8 +1116,9 @@ test("config: usableOnly=true + providers fetch fails → soft-fail keeps everyt
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry.models["cc/claude-opus-4-7"]); assert.ok(entry.models["cc/claude-opus-4-7"]);
assert.ok(entry.models["nvidia/llama-3-70b"], "soft-fail keeps both"); assert.ok(entry.models["nvidia/llama-3-70b"], "soft-fail keeps both");
assert.ok( assert.ok(
@@ -1113,7 +1129,7 @@ test("config: usableOnly=true + providers fetch fails → soft-fail keeps everyt
test("config: diskCache hydrates stale snapshot when /v1/models throws", async () => { test("config: diskCache hydrates stale snapshot when /v1/models throws", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = throwingModelsFetcher(); const fetcher = throwingModelsFetcher();
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1150,14 +1166,15 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok( assert.ok(
entry.models["omniroute/claude-sonnet-4-6"], entry.models["opencode-omniroute/claude-sonnet-4-6"],
"stale snapshot hydrated into static block" "stale snapshot hydrated into static block"
); );
assert.equal( assert.equal(
entry.models["omniroute/claude-sonnet-4-6"].name, entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
"Claude Sonnet 4.6 (cached)", "Claude Sonnet 4.6 (cached)",
"stale enrichment also reused" "stale enrichment also reused"
); );
@@ -1170,7 +1187,7 @@ test("config: diskCache hydrates stale snapshot when /v1/models throws", async (
test("config: cached rawEnrichment from earlier provider hook is reused (no refetch)", async () => { test("config: cached rawEnrichment from earlier provider hook is reused (no refetch)", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-shared", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1202,9 +1219,10 @@ test("config: cached rawEnrichment from earlier provider hook is reused (no refe
await configHook(input); await configHook(input);
assert.equal(enrichmentFetcher.callCount(), 1, "config reused cached enrichment"); assert.equal(enrichmentFetcher.callCount(), 1, "config reused cached enrichment");
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6"); ];
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
}); });
// ───────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────
@@ -1215,7 +1233,7 @@ test("config: cached rawEnrichment from earlier provider hook is reused (no refe
test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-model names", async () => { test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-model names", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]); const fetcher = stubModelsFetcher([MODEL_CLAUDE, MODEL_GEMINI]);
const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]); const combosFetcher = stubCombosFetcher([COMBO_CLAUDE_TIER]);
@@ -1250,18 +1268,25 @@ test("config: providerTag (default-on) prepends '<provider> - ' to enriched raw-
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.ok(entry); assert.ok(entry);
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6"); assert.equal(
assert.equal(entry.models["omniroute/gemini-3-flash"].name, "Gemini-cli - Gemini 3 Flash"); entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
assert.equal(
entry.models["opencode-omniroute/gemini-3-flash"].name,
"Gemini-cli - Gemini 3 Flash"
);
// Combos stay untouched — `Combo: ` prefix already conveys multi-upstream. // Combos stay untouched — `Combo: ` prefix already conveys multi-upstream.
assert.equal(entry.models["omniroute/claude-tier"].name, "Claude Tier"); assert.equal(entry.models["opencode-omniroute/claude-tier"].name, "Claude Tier");
}); });
test("config: providerTag=false suppresses the suffix", async () => { test("config: providerTag=false suppresses the suffix", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1279,10 +1304,11 @@ test("config: providerTag=false suppresses the suffix", async () => {
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
];
assert.equal( assert.equal(
entry.models["omniroute/claude-sonnet-4-6"].name, entry.models["opencode-omniroute/claude-sonnet-4-6"].name,
"Claude Sonnet 4.6", "Claude Sonnet 4.6",
"enriched name kept, provider tag suppressed" "enriched name kept, provider tag suppressed"
); );
@@ -1290,7 +1316,7 @@ test("config: providerTag=false suppresses the suffix", async () => {
test("config: providerTag falls back to UPPER(alias) when providerDisplayName missing", async () => { test("config: providerTag falls back to UPPER(alias) when providerDisplayName missing", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1311,14 +1337,15 @@ test("config: providerTag falls back to UPPER(alias) when providerDisplayName mi
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6"); ];
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "CC - Claude Sonnet 4.6");
}); });
test("config: providerTag skipped entirely when neither providerDisplayName nor providerAlias set", async () => { test("config: providerTag skipped entirely when neither providerDisplayName nor providerAlias set", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1337,14 +1364,15 @@ test("config: providerTag skipped entirely when neither providerDisplayName nor
const input = makeInput(); const input = makeInput();
await hook(input); await hook(input);
const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entry = (input as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
assert.equal(entry.models["omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6"); ];
assert.equal(entry.models["opencode-omniroute/claude-sonnet-4-6"].name, "Claude Sonnet 4.6");
}); });
test("config: providerTag is idempotent — second hook call doesn't double-suffix", async () => { test("config: providerTag is idempotent — second hook call doesn't double-suffix", async () => {
const readAuthJson = stubReadAuthJson({ const readAuthJson = stubReadAuthJson({
omniroute: { type: "api", key: "sk-test", baseURL: "https://or.example/v1" }, "opencode-omniroute": { type: "api", key: "sk-test", baseURL: "https://or.example/v1" },
}); });
const fetcher = stubModelsFetcher([MODEL_CLAUDE]); const fetcher = stubModelsFetcher([MODEL_CLAUDE]);
const combosFetcher = stubCombosFetcher([]); const combosFetcher = stubCombosFetcher([]);
@@ -1363,16 +1391,24 @@ test("config: providerTag is idempotent — second hook call doesn't double-suff
const inputA = makeInput(); const inputA = makeInput();
await hook(inputA); await hook(inputA);
const entryA = (inputA as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entryA = (inputA as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
assert.equal(entryA.models["omniroute/claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6"); ];
assert.equal(
entryA.models["opencode-omniroute/claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
// Second invocation (cache hit) — name must still be single-suffixed. // Second invocation (cache hit) — name must still be single-suffixed.
const inputB = makeInput(); const inputB = makeInput();
await hook(inputB); await hook(inputB);
const entryB = (inputB as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider const entryB = (inputB as { provider: Record<string, OmniRouteStaticProviderEntry> }).provider[
.omniroute; "opencode-omniroute"
assert.equal(entryB.models["omniroute/claude-sonnet-4-6"].name, "Claude - Claude Sonnet 4.6"); ];
assert.equal(
entryB.models["opencode-omniroute/claude-sonnet-4-6"].name,
"Claude - Claude Sonnet 4.6"
);
}); });
// ──────────────────────────────────────────────────────────────────────────── // ────────────────────────────────────────────────────────────────────────────
@@ -1424,7 +1460,7 @@ test("buildStaticProviderEntry: nested combo-ref context is the bottleneck acros
); );
// Pre-fix: Parent would advertise 200_000 (only raw-big counted). // Pre-fix: Parent would advertise 200_000 (only raw-big counted).
// Post-fix: Parent should advertise 8_000 (TinyCombo bottleneck). // Post-fix: Parent should advertise 8_000 (TinyCombo bottleneck).
const parent = block.models["omniroute/parent"]; const parent = block.models["opencode-omniroute/parent"];
assert.ok(parent, "Parent combo must be in the static catalog"); assert.ok(parent, "Parent combo must be in the static catalog");
assert.equal(parent.limit?.context, 8_000); assert.equal(parent.limit?.context, 8_000);
}); });

View File

@@ -376,7 +376,7 @@ test("provider hook: enrichment fetcher called when features.enrichment !== fals
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk") as never });
assert.equal(called, 1, "enrichment fetcher called once"); assert.equal(called, 1, "enrichment fetcher called once");
const m = out["omniroute/claude-sonnet-4-6"]; const m = out["opencode-omniroute/claude-sonnet-4-6"];
assert.equal(m.name, "Claude Sonnet 4.6", "enrichment name overlay applied"); assert.equal(m.name, "Claude Sonnet 4.6", "enrichment name overlay applied");
assert.equal(m.cost.input, 3, "enrichment pricing applied"); assert.equal(m.cost.input, 3, "enrichment pricing applied");
assert.equal(m.cost.output, 15); assert.equal(m.cost.output, 15);
@@ -401,7 +401,11 @@ test("provider hook: enrichment fetcher NOT called when features.enrichment:fals
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk") as never });
assert.equal(called, 0, "enrichment fetcher NOT called when gated off"); assert.equal(called, 0, "enrichment fetcher NOT called when gated off");
assert.equal(out["omniroute/claude-sonnet-4-6"].name, "claude-sonnet-4-6", "raw id preserved"); assert.equal(
out["opencode-omniroute/claude-sonnet-4-6"].name,
"claude-sonnet-4-6",
"raw id preserved"
);
}); });
test("provider hook: compression metadata fetcher NOT called by default (opt-in)", async () => { test("provider hook: compression metadata fetcher NOT called by default (opt-in)", async () => {
@@ -459,7 +463,7 @@ test("provider hook: compression metadata fetcher called when opted in", async (
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk") as never });
assert.equal(called, 1, "compression metadata fetcher called"); assert.equal(called, 1, "compression metadata fetcher called");
const combo = out["omniroute/claude-primary"]; const combo = out["opencode-omniroute/claude-primary"];
assert.ok(combo, "combo entry present"); assert.ok(combo, "combo entry present");
assert.match( assert.match(
combo.name, combo.name,
@@ -473,7 +477,7 @@ test("provider hook: compression metadata fetcher called when opted in", async (
// ───────────────────────────────────────────────────────────────────────── // ─────────────────────────────────────────────────────────────────────────
const stubAuthJson = (apiKey: string) => async () => ({ const stubAuthJson = (apiKey: string) => async () => ({
omniroute: { type: "api" as const, key: apiKey }, "opencode-omniroute": { type: "api" as const, key: apiKey },
}); });
test("config hook: MCP auto-emit OFF by default (no mcp entry)", async () => { test("config hook: MCP auto-emit OFF by default (no mcp entry)", async () => {
@@ -488,7 +492,7 @@ test("config hook: MCP auto-emit OFF by default (no mcp entry)", async () => {
); );
const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {}; const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {};
await hook(input as never); await hook(input as never);
assert.ok(input.provider?.omniroute, "provider block written"); assert.ok(input.provider?.["opencode-omniroute"], "provider block written");
assert.equal(input.mcp, undefined, "no mcp block written"); assert.equal(input.mcp, undefined, "no mcp block written");
}); });
@@ -508,7 +512,7 @@ test("config hook: features.mcpAutoEmit:true writes mcp entry with provider apiK
); );
const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {}; const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {};
await hook(input as never); await hook(input as never);
const entry = input.mcp?.omniroute as const entry = input.mcp?.["opencode-omniroute"] as
| { type: string; url: string; enabled: boolean; headers: Record<string, string> } | { type: string; url: string; enabled: boolean; headers: Record<string, string> }
| undefined; | undefined;
assert.ok(entry, "mcp entry written"); assert.ok(entry, "mcp entry written");
@@ -538,7 +542,7 @@ test("config hook: features.mcpToken overrides provider apiKey in mcp Bearer", a
); );
const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {}; const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {};
await hook(input as never); await hook(input as never);
const entry = input.mcp?.omniroute as { headers: Record<string, string> }; const entry = input.mcp?.["opencode-omniroute"] as { headers: Record<string, string> };
assert.equal( assert.equal(
entry.headers.Authorization, entry.headers.Authorization,
"Bearer sk-mcp-narrower", "Bearer sk-mcp-narrower",
@@ -561,11 +565,11 @@ test("config hook: existing operator mcp.<providerId> wins (no overwrite)", asyn
} }
); );
const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = { const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {
mcp: { omniroute: { type: "custom-user-entry", url: "https://manual.example/mcp" } }, mcp: { "opencode-omniroute": { type: "custom-user-entry", url: "https://manual.example/mcp" } },
}; };
await hook(input as never); await hook(input as never);
assert.deepEqual( assert.deepEqual(
input.mcp?.omniroute, input.mcp?.["opencode-omniroute"],
{ type: "custom-user-entry", url: "https://manual.example/mcp" }, { type: "custom-user-entry", url: "https://manual.example/mcp" },
"operator override preserved" "operator override preserved"
); );
@@ -580,7 +584,7 @@ test("config hook: features.mcpAutoEmit:true with /v1 in baseURL → strips corr
}, },
{ {
readAuthJson: async () => ({ readAuthJson: async () => ({
"omniroute-preprod": { type: "api" as const, key: "sk-preprod" }, "opencode-omniroute-preprod": { type: "api" as const, key: "sk-preprod" },
}), }),
fetcher: async () => SAMPLE_RAW, fetcher: async () => SAMPLE_RAW,
combosFetcher: async () => [], combosFetcher: async () => [],
@@ -589,7 +593,7 @@ test("config hook: features.mcpAutoEmit:true with /v1 in baseURL → strips corr
); );
const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {}; const input: { provider?: Record<string, unknown>; mcp?: Record<string, unknown> } = {};
await hook(input as never); await hook(input as never);
const entry = input.mcp?.["omniroute-preprod"] as { url: string }; const entry = input.mcp?.["opencode-omniroute-preprod"] as { url: string };
assert.equal( assert.equal(
entry.url, entry.url,
"https://or-preprod.example.com/api/mcp/stream", "https://or-preprod.example.com/api/mcp/stream",

View File

@@ -38,8 +38,8 @@ test("multi-instance: two plugin invocations bind to their own providerId", asyn
baseURL: "https://b.example/v1", baseURL: "https://b.example/v1",
}); });
assert.equal(a.auth?.provider, "omniroute-prod"); assert.equal(a.auth?.provider, "opencode-omniroute-prod");
assert.equal(b.auth?.provider, "omniroute-preprod"); assert.equal(b.auth?.provider, "opencode-omniroute-preprod");
}); });
test("multi-instance: hook objects + nested arrays are independent references", async () => { test("multi-instance: hook objects + nested arrays are independent references", async () => {
@@ -70,8 +70,8 @@ test("multi-instance: identical opts twice still yield independent objects", asy
assert.notEqual(first.auth, second.auth); assert.notEqual(first.auth, second.auth);
assert.notEqual(first.auth?.methods, second.auth?.methods); assert.notEqual(first.auth?.methods, second.auth?.methods);
// Same provider id is fine — what matters is no shared mutable state. // Same provider id is fine — what matters is no shared mutable state.
assert.equal(first.auth?.provider, "twin"); assert.equal(first.auth?.provider, "opencode-twin");
assert.equal(second.auth?.provider, "twin"); assert.equal(second.auth?.provider, "opencode-twin");
}); });
test("multi-instance: mutating instance A's auth.methods does not affect instance B", async () => { test("multi-instance: mutating instance A's auth.methods does not affect instance B", async () => {
@@ -132,5 +132,5 @@ test("multi-instance: invalid opts on one instance does not poison the other", a
providerId: "recovered", providerId: "recovered",
baseURL: "https://ok.example/v1", baseURL: "https://ok.example/v1",
}); });
assert.equal(ok.auth?.provider, "recovered"); assert.equal(ok.auth?.provider, "opencode-recovered");
}); });

View File

@@ -75,7 +75,7 @@ const apiAuth = (key: string, baseURL?: string): unknown =>
test("createOmniRouteProviderHook: default providerId is 'omniroute'", () => { test("createOmniRouteProviderHook: default providerId is 'omniroute'", () => {
const hook = createOmniRouteProviderHook(undefined, { combosFetcher: async () => [] }); const hook = createOmniRouteProviderHook(undefined, { combosFetcher: async () => [] });
assert.equal(hook.id, "omniroute"); assert.equal(hook.id, "opencode-omniroute");
}); });
test("createOmniRouteProviderHook: custom providerId binds to hook.id (multi-instance)", () => { test("createOmniRouteProviderHook: custom providerId binds to hook.id (multi-instance)", () => {
@@ -87,8 +87,8 @@ test("createOmniRouteProviderHook: custom providerId binds to hook.id (multi-ins
{ providerId: "omniroute-local" }, { providerId: "omniroute-local" },
{ combosFetcher: async () => [] } { combosFetcher: async () => [] }
); );
assert.equal(a.id, "omniroute-preprod"); assert.equal(a.id, "opencode-omniroute-preprod");
assert.equal(b.id, "omniroute-local"); assert.equal(b.id, "opencode-omniroute-local");
}); });
test("models: extracts apiKey from ctx.auth (type=api) and calls fetcher with it", async () => { test("models: extracts apiKey from ctx.auth (type=api) and calls fetcher with it", async () => {
@@ -101,7 +101,7 @@ test("models: extracts apiKey from ctx.auth (type=api) and calls fetcher with it
assert.equal(fetcher.callCount(), 1); assert.equal(fetcher.callCount(), 1);
assert.deepEqual(fetcher.callsBy()[0], ["https://or.example.com/v1", "sk-abc"]); assert.deepEqual(fetcher.callsBy()[0], ["https://or.example.com/v1", "sk-abc"]);
assert.equal(Object.keys(out).length, 3); assert.equal(Object.keys(out).length, 3);
assert.ok(out["omniroute/claude-primary"]); assert.ok(out["opencode-omniroute/claude-primary"]);
}); });
test("models: returns {} when ctx.auth is null/undefined/wrong-type/empty-key", async () => { test("models: returns {} when ctx.auth is null/undefined/wrong-type/empty-key", async () => {
@@ -152,13 +152,13 @@ test("models: maps a sample /v1/models entry to ModelV2 (sanity)", async () => {
{ fetcher, combosFetcher: async () => [] } { fetcher, combosFetcher: async () => [] }
); );
const out = await hook.models!({} as never, { auth: apiAuth("sk-abc") as never }); const out = await hook.models!({} as never, { auth: apiAuth("sk-abc") as never });
const claude = out["omniroute/claude-primary"]; const claude = out["opencode-omniroute/claude-primary"];
assert.ok(claude, "claude-primary present"); assert.ok(claude, "claude-primary present");
// `mapRawModelToModelV2` stamps the provider prefix on the id so OC's // `mapRawModelToModelV2` stamps the provider prefix on the id so OC's
// static-catalog reader resolves `(providerID, modelID)` from the key. // static-catalog reader resolves `(providerID, modelID)` from the key.
assert.equal(claude.id, "omniroute/claude-primary"); assert.equal(claude.id, "opencode-omniroute/claude-primary");
assert.equal(claude.name, "claude-primary"); assert.equal(claude.name, "claude-primary");
assert.equal(claude.providerID, "omniroute"); assert.equal(claude.providerID, "opencode-omniroute");
assert.equal(claude.api.id, "openai-compatible"); assert.equal(claude.api.id, "openai-compatible");
assert.equal(claude.api.url, "https://or.example.com/v1"); assert.equal(claude.api.url, "https://or.example.com/v1");
assert.equal(claude.api.npm, "@ai-sdk/openai-compatible"); assert.equal(claude.api.npm, "@ai-sdk/openai-compatible");

View File

@@ -26,7 +26,7 @@ test("scaffold: default export is v1 plugin shape { id, server: OmniRoutePlugin
test("resolveOmniRoutePluginOptions: defaults", () => { test("resolveOmniRoutePluginOptions: defaults", () => {
const r = resolveOmniRoutePluginOptions(); const r = resolveOmniRoutePluginOptions();
assert.equal(r.providerId, "omniroute"); assert.equal(r.providerId, "opencode-omniroute");
assert.equal(r.displayName, "OmniRoute"); assert.equal(r.displayName, "OmniRoute");
assert.equal(r.modelCacheTtl, 300_000); assert.equal(r.modelCacheTtl, 300_000);
assert.equal(r.baseURL, undefined); assert.equal(r.baseURL, undefined);
@@ -34,8 +34,8 @@ test("resolveOmniRoutePluginOptions: defaults", () => {
test("resolveOmniRoutePluginOptions: custom providerId derives displayName", () => { test("resolveOmniRoutePluginOptions: custom providerId derives displayName", () => {
const r = resolveOmniRoutePluginOptions({ providerId: "omniroute-preprod" }); const r = resolveOmniRoutePluginOptions({ providerId: "omniroute-preprod" });
assert.equal(r.providerId, "omniroute-preprod"); assert.equal(r.providerId, "opencode-omniroute-preprod");
assert.equal(r.displayName, "OmniRoute (omniroute-preprod)"); assert.equal(r.displayName, "OmniRoute (opencode-omniroute-preprod)");
}); });
test("resolveOmniRoutePluginOptions: explicit displayName wins", () => { test("resolveOmniRoutePluginOptions: explicit displayName wins", () => {