Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions @omniroute/opencode-plugin/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -240,10 +240,18 @@ export function resolveOmniRoutePluginOptions(
opts?: OmniRoutePluginOptions
): Required<Pick<OmniRoutePluginOptions, "providerId" | "displayName" | "modelCacheTtl">> &
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 =
opts?.displayName ??
(providerId === OMNIROUTE_PROVIDER_KEY ? "OmniRoute" : `OmniRoute (${providerId})`);
(providerId === `opencode-${OMNIROUTE_PROVIDER_KEY}`
? "OmniRoute"
: `OmniRoute (${providerId})`);
const modelCacheTtl =
typeof opts?.modelCacheTtl === "number" && opts.modelCacheTtl > 0
? opts.modelCacheTtl
Expand Down
6 changes: 3 additions & 3 deletions @omniroute/opencode-plugin/tests/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,12 @@ import { createOmniRouteAuthHook } from "../src/index.js";

test("createOmniRouteAuthHook: default providerId is 'omniroute'", () => {
const hook = createOmniRouteAuthHook();
assert.equal(hook.provider, "omniroute");
assert.equal(hook.provider, "opencode-omniroute");
});

test("createOmniRouteAuthHook: custom providerId binds to hook.provider (multi-instance)", () => {
const hook = createOmniRouteAuthHook({ providerId: "omniroute-preprod" });
assert.equal(hook.provider, "omniroute-preprod");
assert.equal(hook.provider, "opencode-omniroute-preprod");
});

test("createOmniRouteAuthHook: methods[0] is type 'api' with label including displayName", () => {
Expand All @@ -30,7 +30,7 @@ test("createOmniRouteAuthHook: methods[0] is type 'api' with label including dis
assert.equal(m.label, "OmniRoute API Key");

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", () => {
Expand Down
40 changes: 20 additions & 20 deletions @omniroute/opencode-plugin/tests/combos.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -447,14 +447,14 @@ test("models() returns combo entries merged into the map", async () => {

// 3 raw models + 1 combo = 4 entries
assert.equal(Object.keys(out).length, 4);
assert.ok(out["omniroute/claude-primary"]);
assert.ok(out["omniroute/claude-secondary"]);
assert.ok(out["omniroute/gemini-3-flash"]);
assert.ok(out["omniroute/claude-tier"]);
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["omniroute/claude-tier"];
const combo = out["opencode-omniroute/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)
assert.equal(combo.limit.context, 100_000);
assert.equal(combo.capabilities.reasoning, false);
Expand All @@ -478,11 +478,11 @@ test("models(): combo with unknown member ids degrades to all-false LCD posture"
{ fetcher: modelsFetcher, combosFetcher }
);
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).
assert.equal(out["omniroute/phantom-combo"].capabilities.toolcall, false);
assert.equal(out["omniroute/phantom-combo"].capabilities.reasoning, false);
assert.equal(out["omniroute/phantom-combo"].limit.context, 0);
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 () => {
Expand All @@ -505,8 +505,8 @@ test("models(): hidden combos are excluded from the map", async () => {
{ fetcher: modelsFetcher, combosFetcher }
);
const out = await hook.models!({} as never, { auth: apiAuth("sk-z") as never });
assert.ok(out["omniroute/visible"]);
assert.ok(!out["omniroute/hidden"], "hidden combo must be omitted");
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 () => {
Expand All @@ -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.
assert.ok(out["omniroute/claude-primary"], "combo surfaces under prefixed key");
assert.equal(out["omniroute/claude-primary"].name, "claude-primary");
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) => {
Expand Down Expand Up @@ -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 });
// First combo gets the bare slug; second gets disambiguated.
assert.ok(out["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"], "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 () => {
Expand All @@ -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.
assert.equal(Object.keys(out).length, 2);
assert.ok(out["omniroute/claude-primary"]);
assert.ok(out["omniroute/claude-secondary"]);
assert.ok(out["opencode-omniroute/claude-primary"]);
assert.ok(out["opencode-omniroute/claude-secondary"]);

// Soft-fail warning surfaced.
const softFail = warnings.find((w) => {
Expand All @@ -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 });
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["omniroute/claude-tier"]);
assert.ok(second["opencode-omniroute/claude-tier"]);
});

test("models(): combos refetched after TTL expiry (same key as models)", async () => {
Expand Down Expand Up @@ -701,7 +701,7 @@ test("models(): nested combo-ref context is the min of nested + raw members", as
{ fetcher: modelsFetcher, combosFetcher }
);
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.equal(
masterLight.limit.context,
Expand Down
Loading
Loading