From e63fbed64be8ea0776dbccb424e92569ad97fa6f Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sun, 31 May 2026 01:18:08 -0300 Subject: [PATCH] fix(combo): no-auth OpenCode combos use oc/ prefix, not opencode/ (#2901) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The no-auth OpenCode provider has id "opencode" and alias "oc". The combo builder built qualifiedModel from the provider id ("opencode/big-pickle"), but parseModel("opencode/...") resolves to the opencode-zen api-key tier via a manual ALIAS_TO_PROVIDER_ID override — not the no-auth "opencode" provider. "oc/" resolves correctly. Rewrite qualifiedModel to the provider alias for no-auth providers (only when the alias differs from the id), keeping providerId for getModelIsHidden. Aligned the route test that previously asserted the buggy "opencode/" prefix. Closes #2901 --- CHANGELOG.md | 5 ++ src/lib/combos/builderOptions.ts | 11 ++++ .../combo-builder-opencode-prefix.test.ts | 59 +++++++++++++++++++ .../unit/combo-builder-options-route.test.ts | 4 +- 4 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 tests/unit/combo-builder-opencode-prefix.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 4080cf0bdd..d77ee72ddc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -83,6 +83,11 @@ rejected with `[400] image_generation tool type is not supported`. It is now treated like `tool_search`: allowed past the tool-type validator and dropped silently from the tools array before forwarding to Chat Completions. (#2950) +- **combo/builder:** no-auth OpenCode Free combo entries now use the `oc/` routing + alias instead of the `opencode/` prefix. `parseModel("opencode/")` + resolves to the `opencode-zen` api-key tier (via a manual `ALIAS_TO_PROVIDER_ID` + override), so combos built with the bare provider id misrouted away from the + no-auth `opencode` provider; `oc/` resolves correctly. (#2901) ### ✨ New Features diff --git a/src/lib/combos/builderOptions.ts b/src/lib/combos/builderOptions.ts index 12d2add450..cbeea5306d 100644 --- a/src/lib/combos/builderOptions.ts +++ b/src/lib/combos/builderOptions.ts @@ -618,6 +618,17 @@ export async function getComboBuilderOptions(): Promise" misroutes to the opencode-zen api-key tier + // (manual ALIAS_TO_PROVIDER_ID override), while "oc/" resolves to the + // no-auth "opencode" provider. Rewrite qualifiedModel to the alias prefix. + const routingPrefix = noAuthProvider.alias || providerId; + if (routingPrefix !== providerId) { + for (const opt of modelMap.values()) { + opt.qualifiedModel = `${routingPrefix}/${opt.id}`; + } + } + const displayName = (providerEntryName(providerId) || getProviderDisplayName(providerId, null) || providerId) as string; diff --git a/tests/unit/combo-builder-opencode-prefix.test.ts b/tests/unit/combo-builder-opencode-prefix.test.ts new file mode 100644 index 0000000000..efb874657a --- /dev/null +++ b/tests/unit/combo-builder-opencode-prefix.test.ts @@ -0,0 +1,59 @@ +/** + * Issue #2901 — OpenCode Free combo entries use the `opencode/` prefix instead + * of `oc/`. + * + * The no-auth OpenCode provider has id "opencode" and alias "oc". The combo + * builder built `qualifiedModel` from the provider *id* (`opencode/big-pickle`), + * but `parseModel("opencode/...")` resolves to the **opencode-zen** provider + * (an api-key tier) via a manual ALIAS_TO_PROVIDER_ID override — not the no-auth + * "opencode" provider. The user-facing routing alias `oc/` resolves correctly + * (`oc/big-pickle` → provider "opencode"). + * + * This test drives the real builder against a fresh DB and asserts that no-auth + * OpenCode models carry the `oc/` prefix. + */ +import test from "node:test"; +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; + +const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-prefix-")); +process.env.DATA_DIR = TEST_DATA_DIR; + +const core = await import("../../src/lib/db/core.ts"); +const { getComboBuilderOptions } = await import("../../src/lib/combos/builderOptions.ts"); +const { parseModel } = await import("../../open-sse/services/model.ts"); + +test.after(() => { + core.resetDbInstance(); + fs.rmSync(TEST_DATA_DIR, { recursive: true, force: true }); +}); + +test("#2901 no-auth OpenCode combo models use the oc/ prefix (not opencode/)", async () => { + const payload = await getComboBuilderOptions(); + const opencode = payload.providers.find((p) => p.providerId === "opencode"); + assert.ok(opencode, "no-auth 'opencode' provider must appear in the combo builder"); + + const bigPickle = opencode.models.find((m) => m.id === "big-pickle"); + assert.ok(bigPickle, "big-pickle must be listed under the no-auth opencode provider"); + assert.equal( + bigPickle.qualifiedModel, + "oc/big-pickle", + "no-auth opencode/big-pickle combo entry must use the 'oc/' routing alias" + ); + + // Every model under the no-auth provider must use the alias prefix. + for (const m of opencode.models) { + assert.ok( + m.qualifiedModel.startsWith("oc/"), + `qualifiedModel '${m.qualifiedModel}' must start with 'oc/' (got id-prefix instead)` + ); + } +}); + +test("#2901 the oc/ prefix actually resolves back to the no-auth opencode provider", () => { + // Guards the premise: opencode/ misroutes to opencode-zen, oc/ is correct. + assert.equal(parseModel("oc/big-pickle").provider, "opencode"); + assert.equal(parseModel("opencode/big-pickle").provider, "opencode-zen"); +}); diff --git a/tests/unit/combo-builder-options-route.test.ts b/tests/unit/combo-builder-options-route.test.ts index c2e02b2d2b..704e4009f4 100644 --- a/tests/unit/combo-builder-options-route.test.ts +++ b/tests/unit/combo-builder-options-route.test.ts @@ -212,7 +212,9 @@ test("combo builder options route includes no-auth provider (opencode) even with opencode.models.some((m: any) => m.id === "big-pickle"), "big-pickle should be among opencode models" ); - assert.equal(opencode.models[0].qualifiedModel.startsWith("opencode/"), true); + // #2901: no-auth opencode routes under its alias "oc/" (the bare "opencode/" + // prefix misroutes to the opencode-zen api-key tier via ALIAS_TO_PROVIDER_ID). + assert.equal(opencode.models[0].qualifiedModel.startsWith("oc/"), true); assert.equal(opencode.source, "system", "opencode should have source=system"); });