fix(api): include noAuth providers in /v1/models catalog (#2798) (#2814)

Integrated into release/v3.8.6.
This commit is contained in:
Diego Rodrigues de Sa e Souza
2026-05-29 00:57:36 -03:00
committed by GitHub
parent 25db3dee59
commit 18dae1ab95
3 changed files with 40 additions and 6 deletions

View File

@@ -14,6 +14,7 @@
### 🔧 Bug Fixes
- **api:** include noAuth providers (opencode, etc.) in `/v1/models` active aliases so their models surface without a DB connection row (#2798)
- **opencode-go:** route Qwen3.x via Claude messages format and repair `fixMissingToolResponses` helper for Claude-shape upstreams (#2791 — thanks @jeferssonlemes)
- **validation:** register missing validation helper checks for web-cookie providers (`claude-web`, `gemini-web`, `copilot-web`, `t3-web`) (#2793 — thanks @oyi77)
- **docker:** check and warn if `/app/data` is not writable in the Docker entrypoint script to fail fast with helpful host instructions (#2795 — thanks @hartmark)

View File

@@ -1,5 +1,5 @@
import { PROVIDER_MODELS, PROVIDER_ID_TO_ALIAS } from "@/shared/constants/models";
import { AI_PROVIDERS } from "@/shared/constants/providers";
import { AI_PROVIDERS, NOAUTH_PROVIDERS } from "@/shared/constants/providers";
import {
getProviderConnections,
getCombos,
@@ -345,6 +345,13 @@ export async function getUnifiedModelsResponse(
registerConnectionKey(conn.provider, conn);
}
// noAuth providers never create DB connection rows, so they are always active.
// Add their IDs and aliases unconditionally so the catalog gate does not filter them. (#2798)
for (const p of Object.values(NOAUTH_PROVIDERS)) {
activeAliases.add(p.id);
if ("alias" in p && typeof p.alias === "string") activeAliases.add(p.alias);
}
const getConnectionsForProvider = (...keys: Array<string | null | undefined>) => {
const seen = new Set<string>();
const collected: typeof connections = [];
@@ -362,6 +369,11 @@ export async function getUnifiedModelsResponse(
const providerSupportsModel = (providerKey: string, modelId: string) => {
const providerId = aliasToProviderId[providerKey] || providerKey;
const alias = providerIdToAlias[providerId] || providerKey;
// noAuth providers have no connection rows — treat every model as eligible. (#2798)
const isNoAuth = Object.values(NOAUTH_PROVIDERS).some(
(p) => p.id === providerId || p.id === providerKey || ("alias" in p && p.alias === alias)
);
if (isNoAuth) return true;
return hasEligibleConnectionForModel(
getConnectionsForProvider(providerKey, providerId, alias),
modelId

View File

@@ -236,11 +236,13 @@ test("v1 models catalog keeps only visible combos when no providers are active",
const body = (await response.json()) as any;
assert.equal(response.status, 200);
assert.deepEqual(
body.data.map((item) => item.id),
[visible.name]
);
assert.equal(body.data[0].context_length, 32000);
// The visible combo must be present (noAuth provider models may also appear — that is correct
// behavior after the fix for Issue #2798, so we check membership rather than exact equality).
const ids = body.data.map((item) => item.id);
assert.ok(ids.includes(visible.name), "visible combo must appear");
const visibleCombo = body.data.find((item) => item.id === visible.name);
assert.ok(visibleCombo, "visible combo entry must exist");
assert.equal(visibleCombo.context_length, 32000);
assert.equal(
body.data.some((item) => item.id === hidden.name),
false
@@ -1337,3 +1339,22 @@ test("v1 models catalog prefers manual combo context_length over auto-calculated
assert.ok(comboModel);
assert.equal(comboModel.context_length, 64000, "manual context_length should override auto-calc");
});
// Regression test for Issue #2798: noAuth providers (opencode/oc) have no DB connection rows
// but their models must still appear in /v1/models.
test("v1 models catalog includes noAuth provider models when no DB connections exist (#2798)", async () => {
// No connections seeded — empty DB, simulating a fresh install with no credentials added.
const response = await v1ModelsCatalog.getUnifiedModelsResponse(
new Request("http://localhost/api/v1/models")
);
const body = (await response.json()) as any;
const ids: string[] = body.data.map((item: any) => item.id);
assert.equal(response.status, 200);
// opencode (noAuth) models must surface even with zero connection rows.
// The registry defines models under alias "oc" (e.g. "oc/big-pickle").
assert.ok(
ids.some((id) => id.startsWith("oc/") || id.startsWith("opencode/")),
`Expected at least one oc/* or opencode/* model in /v1/models but got none. IDs sample: ${ids.slice(0, 10).join(", ")}`
);
});