Files
OmniRoute/open-sse/services/providerCostData.ts
Dizzle d88fc2bce7 fix(routing): table pricing with catalog fallback for off-table models, pooled latency bootstrap, fresh tier cache (#12792)
Um modelo grátis fora da tabela herdando $5/$15 por milhão e afundando no roteamento cost-aware é o defeito mais caro desta onda: silencioso, e inverte exatamente a decisão que o operador quer.

Parar de chutar 1500ms de latência para modelo desconhecido e usar a mediana observada do pool — com contador de quantas vezes o chute dispara — é trocar heurística por medição do jeito certo. O contador é o que permite saber se valeu.

Revalidei após reconstruir a branch sobre o tip: **33/33** nas suítes da PR, typecheck:core limpo, `check-api-typecheck` OK (289).

**Duas integrações:**

1. `computeSnapshotWeights` conflitou com o #12794 (health via breaker + quality), já mergeado. Os dois compõem e ambos ficaram: o seu termo de `reliability` — que era a única chave que o caminho de snapshot ainda ignorava — mais o health observado e o quality do #12794.
2. `scripts/quality/run-all-gates.mjs` conflitou com o `check:provider-order-sync` do #12790. Aditivo, os dois gates coexistem.

**Nota de dívida:** o `virtualFactory.ts` cruzou o teto de 1200 linhas pela primeira vez (1187 → 1207) somando esta onda. Congelei em vez de dividir e registrei os dois candidatos a extração na justificativa — `computeSnapshotWeights` (~85 linhas) e o grupo de elegibilidade de credencial (~70). Qualquer um dos dois volta o arquivo para baixo do cap.
2026-09-10 09:20:18 -03:00

75 lines
3.5 KiB
TypeScript

import { getPricingForModel as getDefaultPricingForModel } from "@/shared/constants/pricing";
import { isFreeModel } from "@/shared/utils/freeModels";
import type { TierConfig } from "./tierTypes";
export interface ModelPricing {
inputCostPer1M: number;
outputCostPer1M: number;
isFree: boolean;
freeQuotaLimit?: number;
}
export const KNOWN_MODEL_PRICING: Record<string, ModelPricing> = {
"gpt-4o": { inputCostPer1M: 2.5, outputCostPer1M: 10.0, isFree: false },
"gpt-4o-mini": { inputCostPer1M: 0.15, outputCostPer1M: 0.6, isFree: false },
"claude-fable-5-1": { inputCostPer1M: 10.0, outputCostPer1M: 50.0, isFree: false },
"claude-fable-5": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false },
"claude-opus-5": { inputCostPer1M: 5.0, outputCostPer1M: 25.0, isFree: false },
"claude-opus-4-8": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false },
"claude-opus-4-7": { inputCostPer1M: 15.0, outputCostPer1M: 75.0, isFree: false },
"claude-sonnet-4-6": { inputCostPer1M: 3.0, outputCostPer1M: 15.0, isFree: false },
"claude-sonnet-5": { inputCostPer1M: 3.0, outputCostPer1M: 15.0, isFree: false },
"claude-haiku-4-5": { inputCostPer1M: 0.8, outputCostPer1M: 4.0, isFree: false },
"gemini-2.5-flash": { inputCostPer1M: 0.15, outputCostPer1M: 0.6, isFree: false },
"gemini-2.5-pro": { inputCostPer1M: 1.25, outputCostPer1M: 5.0, isFree: false },
"deepseek-chat": { inputCostPer1M: 0.27, outputCostPer1M: 1.1, isFree: false },
"deepseek-reasoner": { inputCostPer1M: 0.55, outputCostPer1M: 2.19, isFree: false },
"glm-4.7": { inputCostPer1M: 0.6, outputCostPer1M: 0.6, isFree: false },
"glm-5.1": { inputCostPer1M: 0.5, outputCostPer1M: 0.5, isFree: false },
"minimax-m2.1": { inputCostPer1M: 0.2, outputCostPer1M: 0.2, isFree: false },
"grok-4-fast": { inputCostPer1M: 0.2, outputCostPer1M: 0.5, isFree: false },
"kimi-k2-thinking": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
"qwen3-coder-plus": { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true },
"longcat-2.0": {
inputCostPer1M: 0.75,
outputCostPer1M: 2.95,
isFree: true,
freeQuotaLimit: 10000000,
},
};
export function getModelPricing(provider: string, model: string): ModelPricing {
const normalized = String(model || "")
.split("/")
.pop()!
.toLowerCase();
const providerHit = KNOWN_MODEL_PRICING[`${provider}/${normalized}`.toLowerCase()];
if (providerHit) return providerHit;
const defaultPricing = getDefaultPricingForModel(provider, model);
if (defaultPricing) {
const inputCostPer1M = Number(defaultPricing.input);
const outputCostPer1M = Number(defaultPricing.output);
if (Number.isFinite(inputCostPer1M) && Number.isFinite(outputCostPer1M)) {
return {
inputCostPer1M,
outputCostPer1M,
isFree: inputCostPer1M === 0 && outputCostPer1M === 0,
};
}
}
const genericHit = KNOWN_MODEL_PRICING[normalized];
if (genericHit) return genericHit;
if (isFreeModel(provider, { id: normalized }))
return { inputCostPer1M: 0, outputCostPer1M: 0, isFree: true };
return { inputCostPer1M: 5.0, outputCostPer1M: 15.0, isFree: false };
}
/** Input cost per 1M tokens a virtual auto-combo candidate is scored at. */
export function resolveVirtualCost(providerId: string, modelId: string): number {
return getModelPricing(providerId, modelId).inputCostPer1M;
}
export function isExplicitlyFree(provider: string, config: TierConfig): boolean {
return config.freeProviders.includes(provider.toLowerCase());
}