mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 12:22:34 +03:00
Validado numa worktree combinada com a onda de roteamento/free-tier desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-api-typecheck OK (288), check-file-size OK após rebaseline, 84/85 nos testes focados. Duas cópias da mesma ordem de provider com um "keep in sync" implícito é dívida que cobra juros a cada provider novo. Uma definição com re-export nos dois lados resolve a classe. O `xao/*` ordenando depois de todo provider conhecido em vez de junto do `xai-oauth` é um sintoma concreto de que a duplicação já estava divergindo. **Integração:** `scripts/quality/run-all-gates.mjs` conflitou com o `check:pricing-freshness` que entrou pelo #12792 na mesma onda. Aditivo — os dois gates coexistem.
79 lines
3.3 KiB
TypeScript
79 lines
3.3 KiB
TypeScript
/**
|
|
* catalogOrder.ts — canonical provider-grouped ordering for GET /v1/models.
|
|
*
|
|
* The catalog is assembled by many independent push loops (auto-combos, named
|
|
* combos, static registry, codex-native, synced, OpenRouter, specialty registries,
|
|
* custom, alias-backed, connection-fallback). The same provider appears in several
|
|
* loops with other providers interleaved, so its models land in multiple separated
|
|
* blocks. This module applies ONE stable, provider-grouped sort at serialization.
|
|
*
|
|
* Group key = owned_by (the public owner identity), NOT the model-id prefix.
|
|
* Built-in providers use their canonical id as owned_by; compatible nodes use the
|
|
* configured node prefix. A single routable public prefix can differ from its owner:
|
|
* no-auth OpenCode publishes `oc/<model>` while retaining owned_by "opencode".
|
|
* Grouping by the prefix would split one provider's models; grouping by owned_by
|
|
* keeps them contiguous.
|
|
*
|
|
* Order: combo block (owned_by === "combo") pinned first, preserving #4164; then
|
|
* providers in registry precedence (OAUTH -> NOAUTH -> APIKEY canonical keys); then
|
|
* unknown providers by locale-independent code-unit order. Within a group the input
|
|
* order is preserved (stable), keeping combo sort_order, connection priority, custom
|
|
* append-order, and equal-id audio twins.
|
|
*
|
|
* Reorders rows only. Identity, alias mapping, Combo/bare compatibility (#6940/#8530),
|
|
* effort-variant scoping, and Claude-mirror gating are untouched. Pure; no DB/IO.
|
|
*/
|
|
|
|
import { COMBO_GROUP, groupSortPriority } from "@/shared/constants/canonicalProviderOrder";
|
|
|
|
/**
|
|
* Locale-independent code-unit comparator. UTF-16 code units put uppercase A-Z
|
|
* (0x41-0x5A) before lowercase a-z (0x61-0x7A); byte-deterministic, no ICU.
|
|
*/
|
|
function codeUnitCompare(a: string, b: string): number {
|
|
return a < b ? -1 : a > b ? 1 : 0;
|
|
}
|
|
|
|
/**
|
|
* Grouping key for a catalog row: "combo" for combo-owned rows, else owned_by
|
|
* (canonical identity). Rows with no usable owned_by fall back to the id-prefix;
|
|
* that path is defensive only — every published row carries owned_by.
|
|
*/
|
|
function modelGroupKey(model: Record<string, unknown>): string {
|
|
const ownedBy = typeof model.owned_by === "string" ? model.owned_by : "";
|
|
if (ownedBy === "combo") return COMBO_GROUP;
|
|
if (ownedBy) return ownedBy;
|
|
|
|
const id = typeof model.id === "string" ? model.id : "";
|
|
const slash = id.indexOf("/");
|
|
return slash > 0 ? id.slice(0, slash) : id;
|
|
}
|
|
|
|
/**
|
|
* Stable provider-grouped sort. Does not mutate the input. Deterministic for a
|
|
* given input array.
|
|
*/
|
|
export function sortCatalogModelsProviderGrouped<T extends Record<string, unknown>>(
|
|
models: T[]
|
|
): T[] {
|
|
if (!Array.isArray(models) || models.length < 2) return models;
|
|
|
|
const annotated = models.map((model, index) => {
|
|
const groupKey = modelGroupKey(model);
|
|
return { model, groupKey, priority: groupSortPriority(groupKey), index };
|
|
});
|
|
|
|
annotated.sort((a, b) => {
|
|
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
// Unknown groups (both Infinity): code-unit order by key.
|
|
if (a.priority === Infinity && b.priority === Infinity) {
|
|
const cmp = codeUnitCompare(a.groupKey, b.groupKey);
|
|
if (cmp !== 0) return cmp;
|
|
}
|
|
// Stable: preserve input order within a group.
|
|
return a.index - b.index;
|
|
});
|
|
|
|
return annotated.map((entry) => entry.model);
|
|
}
|