Files
OmniRoute/src/app/api/v1/models/catalogSyncedCoverage.ts
Diego Rodrigues de Sa e Souza 92e8960f77 feat(models): functional gateway mirrors + fix synced-substitution (#9217)
* fix(models): preserve static registry models not covered by synced discovery

* feat(models): add functional gateway mirror synthesizer

* feat(models): add functional gateway mirror gate predicate

* feat(models): add functional gateway mirror DB gate

* feat(models): wire functional gateway mirrors into /v1/models

* refactor(models): extract synced-coverage helper to pure leaf (file-size gate)

* fix(db): re-export functional gateway mirrors gate from localDb (db-rules)

* fix(i18n): translate functional gateway mirror flag for Vietnamese (locale completeness)

---------

Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
2026-08-02 20:35:58 -03:00

62 lines
2.6 KiB
TypeScript

/**
* catalogSyncedCoverage.ts — synced-model coverage for the /v1/models static loop.
*
* The synced discovery list (from a provider's `modelsUrl`) REPLACES the static
* registry models in the catalog (see #7694 reasoning). But it must only replace
* models the synced list actually covers. Before this helper, any provider with
* ANY synced model dropped ALL its static models — so static models the upstream
* discovery does not list (e.g. command-code's `deepseek/deepseek-v4-flash`) were
* silently removed from the catalog even though the gateway routes them (200 OK).
*
* Pure module (no DB import) so it is unit-testable and keeps catalog.ts lean.
*/
export interface SyncedModelRow {
id?: unknown;
[key: string]: unknown;
}
/**
* Decide whether a static registry model should be suppressed because a provider's
* synced model list covers it.
*
* `syncedModelIds` are the display-model ids already normalized by the synced loop
* (`displayModelId`), i.e. without the provider alias prefix. Match the static
* model id exactly.
*/
export function shouldSuppressStaticModelBySyncedCoverage(opts: {
providerHasSynced: boolean;
staticModelId: string;
syncedModelIds: string[];
}): boolean {
if (!opts.providerHasSynced) return false;
if (opts.syncedModelIds.length === 0) return false;
return opts.syncedModelIds.includes(opts.staticModelId);
}
/**
* Build a Map of canonical provider id -> set of synced display-model ids, so the
* static loop can decide which static models a provider's synced discovery list
* actually covers (and which static models it must preserve). Keyed by canonical
* provider id because the static loop addresses providers that way.
*/
export function buildSyncedModelIdsByCanonicalProvider(
syncedModelsByProvider: Record<string, SyncedModelRow[]>,
resolveCanonicalProviderId: (aliasOrId: string, fallbackProviderId?: string) => string,
providerIdToPrefix: Record<string, string>,
providerIdToAlias: Record<string, string>
): Map<string, Set<string>> {
const map = new Map<string, Set<string>>();
for (const [providerId, syncedModels] of Object.entries(syncedModelsByProvider)) {
if (!Array.isArray(syncedModels)) continue;
const alias = providerIdToPrefix[providerId] || providerIdToAlias[providerId] || providerId;
const canonicalId = resolveCanonicalProviderId(alias, providerId);
const set = map.get(canonicalId) || new Set<string>();
for (const sm of syncedModels) {
if (typeof sm?.id === "string" && sm.id.length > 0) set.add(sm.id);
}
map.set(canonicalId, set);
}
return map;
}