mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-17 04:12:17 +03:00
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.
75 lines
3.5 KiB
TypeScript
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());
|
|
}
|