mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-21 05:44:59 +00:00
134 lines
3.2 KiB
TypeScript
134 lines
3.2 KiB
TypeScript
import type { NormalizedUsage } from "../agents/usage.js";
|
|
import type { OpenClawConfig } from "../config/config.js";
|
|
|
|
export type ModelCostConfig = {
|
|
input: number;
|
|
output: number;
|
|
cacheRead: number;
|
|
cacheWrite: number;
|
|
};
|
|
|
|
export type UsageTotals = {
|
|
input?: number;
|
|
output?: number;
|
|
cacheRead?: number;
|
|
cacheWrite?: number;
|
|
total?: number;
|
|
};
|
|
|
|
export function formatTokenCount(value?: number): string {
|
|
if (value === undefined || !Number.isFinite(value)) {
|
|
return "0";
|
|
}
|
|
const safe = Math.max(0, value);
|
|
if (safe >= 1_000_000) {
|
|
return `${(safe / 1_000_000).toFixed(1)}m`;
|
|
}
|
|
if (safe >= 1_000) {
|
|
const precision = safe >= 10_000 ? 0 : 1;
|
|
const formattedThousands = (safe / 1_000).toFixed(precision);
|
|
if (Number(formattedThousands) >= 1_000) {
|
|
return `${(safe / 1_000_000).toFixed(1)}m`;
|
|
}
|
|
return `${formattedThousands}k`;
|
|
}
|
|
return String(Math.round(safe));
|
|
}
|
|
|
|
export function formatUsd(value?: number): string | undefined {
|
|
if (value === undefined || !Number.isFinite(value)) {
|
|
return undefined;
|
|
}
|
|
if (value >= 1) {
|
|
return `$${value.toFixed(2)}`;
|
|
}
|
|
if (value >= 0.01) {
|
|
return `$${value.toFixed(2)}`;
|
|
}
|
|
return `$${value.toFixed(4)}`;
|
|
}
|
|
|
|
const BUILTIN_MODEL_COSTS: Record<string, ModelCostConfig> = {
|
|
"anthropic/claude-opus-4-6": {
|
|
input: 15,
|
|
output: 75,
|
|
cacheRead: 1.5,
|
|
cacheWrite: 18.75,
|
|
},
|
|
"anthropic/claude-sonnet-4-6": {
|
|
input: 3,
|
|
output: 15,
|
|
cacheRead: 0.3,
|
|
cacheWrite: 3.75,
|
|
},
|
|
"anthropic/claude-haiku-4-5": {
|
|
input: 0.8,
|
|
output: 4,
|
|
cacheRead: 0.08,
|
|
cacheWrite: 1,
|
|
},
|
|
"openai-codex/gpt-5.4": {
|
|
input: 2,
|
|
output: 8,
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
},
|
|
"openai-codex/gpt-5.3-codex": {
|
|
input: 1,
|
|
output: 4,
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
},
|
|
"openai-codex/gpt-5.3-codex-spark": {
|
|
input: 0.5,
|
|
output: 2,
|
|
cacheRead: 0,
|
|
cacheWrite: 0,
|
|
},
|
|
};
|
|
|
|
export function resolveModelCostConfig(params: {
|
|
provider?: string;
|
|
model?: string;
|
|
config?: OpenClawConfig;
|
|
}): ModelCostConfig | undefined {
|
|
const provider = params.provider?.trim();
|
|
const model = params.model?.trim();
|
|
if (!provider || !model) {
|
|
return undefined;
|
|
}
|
|
const providers = params.config?.models?.providers ?? {};
|
|
const entry = providers[provider]?.models?.find((item) => item.id === model);
|
|
if (entry?.cost) {
|
|
return entry.cost;
|
|
}
|
|
return BUILTIN_MODEL_COSTS[`${provider.toLowerCase()}/${model.toLowerCase()}`];
|
|
}
|
|
|
|
const toNumber = (value: number | undefined): number =>
|
|
typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
|
|
export function estimateUsageCost(params: {
|
|
usage?: NormalizedUsage | UsageTotals | null;
|
|
cost?: ModelCostConfig;
|
|
}): number | undefined {
|
|
const usage = params.usage;
|
|
const cost = params.cost;
|
|
if (!usage || !cost) {
|
|
return undefined;
|
|
}
|
|
const input = toNumber(usage.input);
|
|
const output = toNumber(usage.output);
|
|
const cacheRead = toNumber(usage.cacheRead);
|
|
const cacheWrite = toNumber(usage.cacheWrite);
|
|
const total =
|
|
input * cost.input +
|
|
output * cost.output +
|
|
cacheRead * cost.cacheRead +
|
|
cacheWrite * cost.cacheWrite;
|
|
if (!Number.isFinite(total)) {
|
|
return undefined;
|
|
}
|
|
return total / 1_000_000;
|
|
}
|