diff --git a/src/lib/combos/builderOptions.ts b/src/lib/combos/builderOptions.ts index 7761ca106e..0fe687b7cd 100644 --- a/src/lib/combos/builderOptions.ts +++ b/src/lib/combos/builderOptions.ts @@ -556,6 +556,17 @@ function buildModelOptions( return modelMap; } +function rewriteQualifiedModelPrefix( + modelMap: Map, + providerId: string, + routingPrefix: string +): void { + if (routingPrefix === providerId) return; + for (const option of modelMap.values()) { + option.qualifiedModel = `${routingPrefix}/${option.id}`; + } +} + /** * #6957: some providers' own catalogs assign the identical display `name` to * several distinct model ids (e.g. Mistral's "codestral-latest" alias renders @@ -684,6 +695,12 @@ export async function getComboBuilderOptions(): Promise" 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}`; - } - } + rewriteQualifiedModelPrefix(modelMap, providerId, routingPrefix); const displayName = (providerEntryName(providerId) || getProviderDisplayName(providerId, null) || diff --git a/tests/unit/combo-builder-opencode-prefix.test.ts b/tests/unit/combo-builder-opencode-prefix.test.ts index efb874657a..8ce510e8be 100644 --- a/tests/unit/combo-builder-opencode-prefix.test.ts +++ b/tests/unit/combo-builder-opencode-prefix.test.ts @@ -22,6 +22,7 @@ const TEST_DATA_DIR = fs.mkdtempSync(path.join(os.tmpdir(), "omniroute-combo-pre process.env.DATA_DIR = TEST_DATA_DIR; const core = await import("../../src/lib/db/core.ts"); +const providersDb = await import("../../src/lib/db/providers.ts"); const { getComboBuilderOptions } = await import("../../src/lib/combos/builderOptions.ts"); const { parseModel } = await import("../../open-sse/services/model.ts"); @@ -52,6 +53,29 @@ test("#2901 no-auth OpenCode combo models use the oc/ prefix (not opencode/)", a } }); +test("#2901 configured OpenCode connections also use the oc/ prefix", async () => { + await providersDb.createProviderConnection({ + provider: "opencode", + authType: "apikey", + name: "OpenCode Free test connection", + apiKey: "test-key", + }); + + const payload = await getComboBuilderOptions(); + const opencode = payload.providers.find( + (provider) => provider.providerId === "opencode" && provider.connectionCount > 0 + ); + assert.ok(opencode, "configured OpenCode provider must appear in the combo builder"); + + const bigPickle = opencode.models.find((model) => model.id === "big-pickle"); + assert.ok(bigPickle, "big-pickle must be listed under the configured opencode provider"); + assert.equal( + bigPickle.qualifiedModel, + "oc/big-pickle", + "configured opencode combo entries must use the 'oc/' routing alias" + ); +}); + 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");