Files
OmniRoute/src/lib/usage/providerLimits/quotaNormalize.ts
Bob.Hou ebdbd2c67d feat(models): live account catalog for Claude, Codex, Copilot, AGY (#12866)
Validado numa worktree combinada com as 16 PRs desta leva sobre `release/v3.8.51`: typecheck:core limpo, check-file-size e check-changelog-integrity OK, complexity 2788/3218 e cognitive 1261/1437, ESLint 0 erros nos 152 arquivos alterados, 771 testes unitários focados, 49 de integração e a suíte vitest:ui completa (2149) verdes.

O ponto que sustenta a PR é o `models.dev` virar overlay de preço em vez de fonte de catálogo. Um catálogo estático que sobrevive à conta já ter listado ids mais novos é o tipo de defeito que só aparece quando o modelo novo é justamente o que se quer usar.

Nota de integração: `activeSyncedCatalog.ts` colidiu com o #12934 (união dos `customModels` do picker no catálogo de despacho). Como você extraiu o bloco original para `loadConnectionCatalog`, os dois se compõem: a união dos irmãos agy/antigravity primeiro, o `unionCustomModels` por cima. Revalidei com `custom-models-live-catalog-12597`, `live-model-catalog-reconciliation-8926`, `sync-models-degraded-cached-catalog-9683`, `models-dev-catalog-read-gate`, `discovery-class`, `reactive-model-sync` e `l1-oauth-autosync-default` juntos — 48/48 — mais typecheck:core limpo.

Sobre o `autoSync` padrão em Claude/Codex/Copilot com scheduler de 6h: passei isso pelo dono antes de mergear e a decisão foi manter como está.
2026-09-07 09:02:59 -03:00

73 lines
2.6 KiB
TypeScript

import {
isUserCallableAntigravityModelId,
toClientAntigravityModelId,
} from "@omniroute/open-sse/config/antigravityModelAliases.ts";
import { isDiscoverableAgyModelId } from "@omniroute/open-sse/config/agyModels.ts";
type JsonRecord = Record<string, unknown>;
export function isRecord(value: unknown): value is JsonRecord {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
export function isUsageQuotaKeyAllowed(provider: string, quotaKey: string): boolean {
if (quotaKey === "credits" || quotaKey === "models") return true;
if (provider === "antigravity") return isUserCallableAntigravityModelId(quotaKey);
if (provider === "agy") return isDiscoverableAgyModelId(quotaKey);
return true;
}
export function normalizeUsageQuotaKey(provider: string, quotaKey: string): string | null {
if (quotaKey === "credits" || quotaKey === "models") return quotaKey;
if (provider === "antigravity" || provider === "agy") {
const clientKey = toClientAntigravityModelId(quotaKey);
return isUsageQuotaKeyAllowed(provider, clientKey) ? clientKey : null;
}
return isUsageQuotaKeyAllowed(provider, quotaKey) ? quotaKey : null;
}
export function normalizeUsageQuotasForProvider(
provider: string,
quotas: JsonRecord | null | undefined
): JsonRecord | null {
if (!isRecord(quotas)) return quotas ?? null;
const normalized: JsonRecord = {};
let changed = false;
for (const [quotaKey, quota] of Object.entries(quotas)) {
const normalizedKey = normalizeUsageQuotaKey(provider, quotaKey);
if (!normalizedKey) {
changed = true;
continue;
}
const existing = normalized[normalizedKey];
if (existing && isRecord(existing) && isRecord(quota)) {
const existingSource = String(existing.quotaSource ?? "");
const nextSource = String(quota.quotaSource ?? "");
const sourceRank: Record<string, number> = {
fetchAvailableModels: 0,
localUsageHistory: 1,
retrieveUserQuota: 2,
};
if ((sourceRank[existingSource] ?? 0) > (sourceRank[nextSource] ?? 0)) {
continue;
}
}
normalized[normalizedKey] = quota as JsonRecord;
if (normalizedKey !== quotaKey) changed = true;
}
return changed ? normalized : quotas;
}
export function sanitizeUsageQuotasForProvider(provider: string, usage: JsonRecord): JsonRecord {
if (provider !== "antigravity" && provider !== "agy") return usage;
if (!isRecord(usage.quotas)) return usage;
const sanitizedQuotas = normalizeUsageQuotasForProvider(provider, usage.quotas);
return sanitizedQuotas === usage.quotas ? usage : { ...usage, quotas: sanitizedQuotas };
}