mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-18 04:42:30 +03:00
fix(providers): single source for provider order, resolve xao rank (#12790)
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.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import { Input } from "@/shared/components";
|
||||
import { getProviderConnectionFamilyIds } from "@/shared/constants/providers";
|
||||
import { providerText, type ProviderMessageTranslator } from "../../providerPageHelpers";
|
||||
|
||||
import {
|
||||
@@ -54,7 +55,7 @@ export default function QuotaScrapingFields({
|
||||
);
|
||||
}
|
||||
|
||||
if (provider === "alibaba" || provider === "alibaba-cn") {
|
||||
if (getProviderConnectionFamilyIds("alibaba").includes(provider)) {
|
||||
return (
|
||||
<div className="flex flex-col gap-3 rounded-lg border border-border/50 bg-surface/20 p-4">
|
||||
<Input
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
* this file instead; the component re-exports it for existing callers.
|
||||
*/
|
||||
|
||||
import { getProviderConnectionFamilyIds } from "@/shared/constants/providers";
|
||||
|
||||
/** Providers whose quota lives behind the Qwen/Model Studio console gateway (#9603). */
|
||||
export const QWEN_TOKEN_PLAN_PROVIDERS = new Set(["qwen-cloud-token-plan", "bailian-coding-plan"]);
|
||||
|
||||
@@ -34,7 +36,7 @@ export function assignQuotaScrapingProviderData(
|
||||
if (provider === "ollama-cloud" && values.ollamaCloudUsageCookie.trim()) {
|
||||
target.ollamaCloudUsageCookie = values.ollamaCloudUsageCookie.trim();
|
||||
} else if (
|
||||
(provider === "alibaba" || provider === "alibaba-cn") &&
|
||||
getProviderConnectionFamilyIds("alibaba").includes(provider) &&
|
||||
values.alibabaConsoleCookie.trim()
|
||||
) {
|
||||
target.alibabaConsoleCookie = values.alibabaConsoleCookie.trim();
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getModelsByProviderId } from "@omniroute/open-sse/config/providerModels.ts";
|
||||
import { getProviderConnectionFamilyIds } from "@/shared/constants/providers";
|
||||
import { safePercentage } from "@/shared/utils/formatting";
|
||||
|
||||
const GLM_QUOTA_ORDER: Record<string, number> = { session: 0, weekly: 1, mcp_monthly: 2 };
|
||||
@@ -10,7 +11,7 @@ const CODEX_QUOTA_ORDER: Record<string, number> = {
|
||||
banked_reset_credits: 4,
|
||||
};
|
||||
const GLM_FAMILY_PROVIDERS = ["glm", "glm-cn", "glmt", "opencode-go"];
|
||||
const KIMI_CODING_PROVIDERS = ["kimi-coding", "kimi-coding-apikey"];
|
||||
const KIMI_CODING_PROVIDERS: readonly string[] = getProviderConnectionFamilyIds("kimi-coding");
|
||||
|
||||
/**
|
||||
* Providers whose quotas already get a deterministic fixed-window order below
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import {
|
||||
getProviderConnectionFamilyIds,
|
||||
isClaudeCodeCompatibleProvider,
|
||||
isAnthropicCompatibleProvider,
|
||||
isOpenAICompatibleProvider,
|
||||
@@ -2343,7 +2344,7 @@ export async function GET(
|
||||
|
||||
const data = await response.json();
|
||||
let pageModels = config.parseResponse(data);
|
||||
if (provider === "alibaba" || provider === "alibaba-cn") {
|
||||
if (getProviderConnectionFamilyIds("alibaba").includes(provider)) {
|
||||
const { parseAlibabaModelStudioModelsForConnection } =
|
||||
await import("./discovery/providerModelsConfig.ts");
|
||||
pageModels = parseAlibabaModelStudioModelsForConnection(
|
||||
@@ -2372,7 +2373,7 @@ export async function GET(
|
||||
);
|
||||
}
|
||||
|
||||
if (provider === "alibaba" || provider === "alibaba-cn") {
|
||||
if (getProviderConnectionFamilyIds("alibaba").includes(provider)) {
|
||||
const { shouldUseLiveAlibabaFreeModelDiscovery } =
|
||||
await import("@omniroute/open-sse/services/alibabaFreeTier.ts");
|
||||
const { scheduleAlibabaFreeTierProbeRefresh } =
|
||||
|
||||
@@ -24,14 +24,7 @@
|
||||
* effort-variant scoping, and Claude-mirror gating are untouched. Pure; no DB/IO.
|
||||
*/
|
||||
|
||||
import { OAUTH_PROVIDERS, NOAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/providers";
|
||||
|
||||
/** Canonical provider precedence, keyed by provider id (not alias). Built once. */
|
||||
const CANONICAL_PROVIDER_ORDER: readonly string[] = [
|
||||
...Object.keys(OAUTH_PROVIDERS),
|
||||
...Object.keys(NOAUTH_PROVIDERS),
|
||||
...Object.keys(APIKEY_PROVIDERS),
|
||||
];
|
||||
import { COMBO_GROUP, groupSortPriority } from "@/shared/constants/canonicalProviderOrder";
|
||||
|
||||
/**
|
||||
* Locale-independent code-unit comparator. UTF-16 code units put uppercase A-Z
|
||||
@@ -41,9 +34,6 @@ function codeUnitCompare(a: string, b: string): number {
|
||||
return a < b ? -1 : a > b ? 1 : 0;
|
||||
}
|
||||
|
||||
/** Combo bucket key, distinct from any real provider id. */
|
||||
const COMBO_GROUP = " combo";
|
||||
|
||||
/**
|
||||
* 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;
|
||||
@@ -59,16 +49,6 @@ function modelGroupKey(model: Record<string, unknown>): string {
|
||||
return slash > 0 ? id.slice(0, slash) : id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sort priority for a group key: combo first, then registry precedence, then
|
||||
* unknown groups (rank Infinity, ordered among themselves by code unit).
|
||||
*/
|
||||
function groupSortPriority(groupKey: string): number {
|
||||
if (groupKey === COMBO_GROUP) return -1;
|
||||
const idx = CANONICAL_PROVIDER_ORDER.indexOf(groupKey);
|
||||
return idx >= 0 ? idx : Infinity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Stable provider-grouped sort. Does not mutate the input. Deterministic for a
|
||||
* given input array.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// src/lib/combos/comboSort.ts
|
||||
import { OAUTH_PROVIDERS, NOAUTH_PROVIDERS, APIKEY_PROVIDERS } from "@/shared/constants/providers";
|
||||
import { CANONICAL_PROVIDER_ORDER, providerRank } from "@/shared/constants/canonicalProviderOrder";
|
||||
import type { ComboStep } from "@/lib/combos/steps";
|
||||
|
||||
export type { ComboStep };
|
||||
@@ -17,13 +17,8 @@ export function isValidSortMethod(raw: unknown): raw is SortMethod {
|
||||
return VALID_SORT_METHODS.has(raw as string);
|
||||
}
|
||||
|
||||
// Mirrors CANONICAL_PROVIDER_ORDER from src/app/api/v1/models/catalogOrder.ts — keep in sync.
|
||||
// Both are derived from OAUTH+NOAUTH+APIKEY keys; drift would silently diverge catalog vs combo order.
|
||||
export const PROVIDER_ORDER: readonly string[] = [
|
||||
...Object.keys(OAUTH_PROVIDERS),
|
||||
...Object.keys(NOAUTH_PROVIDERS),
|
||||
...Object.keys(APIKEY_PROVIDERS),
|
||||
];
|
||||
// Single source: re-exported from canonicalProviderOrder (was a duplicated derivation).
|
||||
export { CANONICAL_PROVIDER_ORDER as PROVIDER_ORDER };
|
||||
|
||||
const REFERENCE_SENTINEL = " combo-ref"; // sorts after any real provider id
|
||||
|
||||
@@ -40,12 +35,6 @@ function nameKey(step: ComboStep): string {
|
||||
return step.comboName; // combo-ref
|
||||
}
|
||||
|
||||
/** Stable index for provider ordering; unknown providers go after the known list. */
|
||||
function providerRank(providerId: string): number {
|
||||
const idx = PROVIDER_ORDER.indexOf(providerId);
|
||||
return idx === -1 ? PROVIDER_ORDER.length : idx;
|
||||
}
|
||||
|
||||
/** Synchronous sorts: manual (noop), provider, name. Stable. */
|
||||
export function sortComboStepsSync(
|
||||
steps: ComboStep[],
|
||||
|
||||
48
src/shared/constants/canonicalProviderOrder.ts
Normal file
48
src/shared/constants/canonicalProviderOrder.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import { OAUTH_PROVIDERS } from "@/shared/constants/providers/oauth";
|
||||
import { NOAUTH_PROVIDERS } from "@/shared/constants/providers/noauth";
|
||||
import { APIKEY_PROVIDERS } from "@/shared/constants/providers/apikey";
|
||||
|
||||
/** Registry-canonical provider precedence: OAUTH -> NOAUTH -> APIKEY (canonical keys, not aliases). */
|
||||
export const CANONICAL_PROVIDER_ORDER: readonly string[] = [
|
||||
...Object.keys(OAUTH_PROVIDERS),
|
||||
...Object.keys(NOAUTH_PROVIDERS),
|
||||
...Object.keys(APIKEY_PROVIDERS),
|
||||
];
|
||||
|
||||
// Single load-time pass (never resolve per sorted item: getProviderByAlias loops
|
||||
// ~10 sections x providers, which costs more than the indexOf it replaces on ~1k catalog rows).
|
||||
function buildRankResolved(): ReadonlyMap<string, number> {
|
||||
const rank = new Map<string, number>();
|
||||
CANONICAL_PROVIDER_ORDER.forEach((id, i) => rank.set(id, i));
|
||||
// Canonical keys win over homonym aliases (m-a: ~17 aliases like minimax-cn
|
||||
// are also canonical keys — an alias must never steal a canonical slot).
|
||||
const canonicals = new Set<string>(CANONICAL_PROVIDER_ORDER);
|
||||
for (const def of Object.values({
|
||||
...OAUTH_PROVIDERS,
|
||||
...NOAUTH_PROVIDERS,
|
||||
...APIKEY_PROVIDERS,
|
||||
})) {
|
||||
const a = (def as { id: string; alias?: string }).alias;
|
||||
if (typeof a === "string" && a.length > 0 && !canonicals.has(a) && rank.has(def.id)) {
|
||||
rank.set(a, rank.get(def.id) as number);
|
||||
}
|
||||
}
|
||||
return rank;
|
||||
}
|
||||
|
||||
const RANK_RESOLVED: ReadonlyMap<string, number> = buildRankResolved();
|
||||
|
||||
/** Finite rank (combos): alias resolved, unknown -> length (existing comboSort contract). */
|
||||
export function providerRank(providerId: string): number {
|
||||
return RANK_RESOLVED.get(providerId) ?? CANONICAL_PROVIDER_ORDER.length;
|
||||
}
|
||||
|
||||
/** Combo bucket key, distinct from any real provider id. Single definition (was also in catalogOrder.ts:45). */
|
||||
export const COMBO_GROUP = " combo";
|
||||
|
||||
/** Catalog priority: combo first, then registry precedence, unknown -> Infinity (code-unit branch stays live). */
|
||||
export function groupSortPriority(groupKey: string): number {
|
||||
if (groupKey === COMBO_GROUP) return -1;
|
||||
const r = RANK_RESOLVED.get(groupKey);
|
||||
return r ?? Infinity;
|
||||
}
|
||||
Reference in New Issue
Block a user