fix(api): list self-aliased providers in canonical models catalog mode (#12381)

GET /v1/models with MODELS_CATALOG_PREFIX_MODE=canonical dropped every chat row of a provider whose registry alias is undefined (antigravity) or equal to its own id (agy, most built-ins). Each emission loop pushes alias/model only when includeAlias, and canonicalProviderId/model only when the ids differ — for a self-aliased provider both are the same string, so neither fired. #11918 fixed the class for custom nodes but not built-ins, and not the static loop. The alias row is now treated as the canonical row whenever the ids coincide, across the static, synced, custom and alias-backed loops; the canonical branch's !== alias guard is untouched, so dual and alias output cannot double up. Docs that described the omission as intended are corrected.

Validated in a combined worktree with all 25 PRs of this batch boarded together: typecheck:core clean, 443/443 node-runner tests plus 14/14 vitest across every test file the batch touches, and check-changelog-integrity, check:cycles (418 files), check:provider-consistency (272 REGISTRY entries, 355 canonical providers), check:docs-counts, check:docs-sync (42 locales) and check-file-size all green.

Thanks @pacocartones.
This commit is contained in:
Paco Cartones
2026-09-02 08:15:24 +02:00
committed by GitHub
parent 0ec7504024
commit 990aeca1db
7 changed files with 250 additions and 12 deletions

View File

@@ -1089,7 +1089,14 @@ async function buildUnifiedModelsResponseCore(
);
const thinkingCapabilities =
Object.keys(thinkingFields).length > 0 ? { capabilities: thinkingFields } : {};
if (includeAlias) {
// #12058: a self-aliased provider (registry `alias` undefined or equal to its
// own id — antigravity, agy, most built-ins) has a single id form, so its
// alias row IS its canonical row. Emit it in canonical mode too; the
// canonical branch below still skips it (`canonicalProviderId !== alias`),
// so dual mode cannot double up. Same class as #11832 (custom nodes,
// PR #11918), which only widened the synced/custom/alias-backed loops.
const selfAliased = canonicalProviderId === alias;
if (includeAlias || selfAliased) {
models.push({
id: aliasId,
object: "model",
@@ -1181,6 +1188,8 @@ async function buildUnifiedModelsResponseCore(
const prefix = providerIdToPrefix[providerId];
const alias = prefix || providerIdToAlias[providerId] || providerId;
const canonicalProviderId = resolveCanonicalProviderId(alias, providerId);
// #12058: see the static loop — the alias row is the only row here.
const selfAliased = canonicalProviderId === alias;
const parentProviderType = nodeIdToProviderType[providerId];
if (
@@ -1280,7 +1289,7 @@ async function buildUnifiedModelsResponseCore(
continue;
}
if (includeAlias || Boolean(prefix)) {
if (includeAlias || Boolean(prefix) || selfAliased) {
models.push({
id: aliasId,
object: "model",
@@ -1628,6 +1637,8 @@ async function buildUnifiedModelsResponseCore(
const prefix = providerIdToPrefix[providerId];
const alias = prefix || providerIdToAlias[providerId] || providerId;
const canonicalProviderId = resolveCanonicalProviderId(alias, providerId);
// #12058: see the static loop — the alias row is the only row here.
const selfAliased = canonicalProviderId === alias;
// Only include if provider is active — check alias, canonical ID, raw providerId,
// or the parent provider type (for compatible providers whose node ID is a UUID)
@@ -1733,7 +1744,7 @@ async function buildUnifiedModelsResponseCore(
? getCustomVisionCapabilityFields(model, aliasId, modelId)
: null;
if (includeAlias || Boolean(prefix)) {
if (includeAlias || Boolean(prefix) || selfAliased) {
models.push({
id: aliasId,
object: "model",
@@ -1852,7 +1863,9 @@ async function buildUnifiedModelsResponseCore(
const visionFields =
getVisionCapabilityFields(aliasId) || getVisionCapabilityFields(modelId);
if (includeAlias || Boolean(nodePrefix)) {
// #12058: see the static loop — the alias row is the only row here.
const selfAliased = canonicalProviderId === alias;
if (includeAlias || Boolean(nodePrefix) || selfAliased) {
models.push({
id: aliasId,
object: "model",