Files
omniai-web/src/utils/modelPricing.ts
T
stringadmin d28889fd0c
Web Quality / verify (push) Has been cancelled
Use server prices for text billing estimates
2026-06-10 14:12:55 +08:00

105 lines
2.7 KiB
TypeScript

import type { PublicModelPrice } from "../api/publicPricingClient";
import type { TextTokenCreditRate } from "./taskLifecycle";
const TOKENS_PER_MILLION = 1_000_000;
const BACKEND_TOKEN_PRICE_UNIT = 1_000;
const CREDITS_PER_CNY = 100;
const MILLS_PER_CNY = 1_000;
const CREDITS_PER_MILL = CREDITS_PER_CNY / MILLS_PER_CNY;
function isUsablePrice(value: number | null | undefined): value is number {
return typeof value === "number" && Number.isFinite(value) && value >= 0;
}
function normalizeModelKey(value: string): string {
return value.trim().toLowerCase();
}
function compactModelKey(value: string): string {
return normalizeModelKey(value).replace(/[^a-z0-9]+/g, "");
}
function addCandidate(
candidates: PublicModelPrice[],
seen: Set<string>,
price: PublicModelPrice,
): void {
const key = normalizeModelKey(price.modelKey);
if (seen.has(key)) return;
seen.add(key);
candidates.push(price);
}
export function millsPerThousandTokensToCreditsPerMillion(
priceMills: number,
): number {
if (!isUsablePrice(priceMills)) return 0;
return (
priceMills *
(TOKENS_PER_MILLION / BACKEND_TOKEN_PRICE_UNIT) *
CREDITS_PER_MILL
);
}
export function modelPriceToTextTokenCreditRate(
price: PublicModelPrice,
): TextTokenCreditRate | null {
if (
!isUsablePrice(price.inputPriceMills) ||
!isUsablePrice(price.outputPriceMills)
)
return null;
return {
inputCreditsPerMillion: millsPerThousandTokensToCreditsPerMillion(
price.inputPriceMills,
),
outputCreditsPerMillion: millsPerThousandTokensToCreditsPerMillion(
price.outputPriceMills,
),
source: "server",
modelKey: price.modelKey,
};
}
export function resolveTextTokenCreditRate(
prices: PublicModelPrice[],
modelKey: string | null | undefined,
): TextTokenCreditRate | null {
const normalizedTarget = normalizeModelKey(modelKey || "");
if (!normalizedTarget) return null;
const compactTarget = compactModelKey(normalizedTarget);
const candidates: PublicModelPrice[] = [];
const seen = new Set<string>();
for (const price of prices) {
if (normalizeModelKey(price.modelKey) === normalizedTarget) {
addCandidate(candidates, seen, price);
}
}
for (const price of prices) {
if (compactModelKey(price.modelKey) === compactTarget) {
addCandidate(candidates, seen, price);
}
}
for (const price of prices) {
const compactPriceKey = compactModelKey(price.modelKey);
if (
compactPriceKey.includes(compactTarget) ||
compactTarget.includes(compactPriceKey)
) {
addCandidate(candidates, seen, price);
}
}
for (const price of candidates) {
const rate = modelPriceToTextTokenCreditRate(price);
if (rate) return rate;
}
return null;
}