fix: stop guessing session model costs

This commit is contained in:
Tyler Yust 2026-03-12 17:34:12 -07:00
parent 6bc1f779df
commit de22f822e0
6 changed files with 25 additions and 58 deletions

View File

@ -83,10 +83,23 @@ describe("models-config", () => {
const modelPath = path.join(resolveOpenClawAgentDir(), "models.json");
const raw = await fs.readFile(modelPath, "utf8");
const parsed = JSON.parse(raw) as {
providers: Record<string, { baseUrl?: string }>;
providers: Record<
string,
{
baseUrl?: string;
models?: Array<{
id?: string;
cost?: { input?: number; output?: number; cacheRead?: number; cacheWrite?: number };
}>;
}
>;
};
expect(parsed.providers["custom-proxy"]?.baseUrl).toBe("http://localhost:4000/v1");
expect(parsed.providers["custom-proxy"]?.models?.[0]).toMatchObject({
id: "llama-3.1-8b",
cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
});
});
});

View File

@ -123,6 +123,7 @@ describe("sessions tools", () => {
status: "running",
startedAt: 100,
runtimeMs: 42,
estimatedCostUsd: 0.0042,
childSessions: ["agent:main:subagent:worker"],
},
{
@ -164,6 +165,7 @@ describe("sessions tools", () => {
status?: string;
startedAt?: number;
runtimeMs?: number;
estimatedCostUsd?: number;
childSessions?: string[];
messages?: Array<{ role?: string }>;
}>;
@ -178,6 +180,7 @@ describe("sessions tools", () => {
expect(group?.status).toBe("running");
expect(group?.startedAt).toBe(100);
expect(group?.runtimeMs).toBe(42);
expect(group?.estimatedCostUsd).toBe(0.0042);
expect(group?.childSessions).toEqual(["agent:main:subagent:worker"]);
const cronOnly = await tool.execute("call2", { kinds: ["cron"] });

View File

@ -58,6 +58,7 @@ export type SessionListRow = {
model?: string;
contextTokens?: number | null;
totalTokens?: number | null;
estimatedCostUsd?: number;
status?: SessionRunStatus;
startedAt?: number;
endedAt?: number;

View File

@ -203,6 +203,8 @@ export function createSessionsListTool(opts?: {
model: typeof entry.model === "string" ? entry.model : undefined,
contextTokens: typeof entry.contextTokens === "number" ? entry.contextTokens : undefined,
totalTokens: typeof entry.totalTokens === "number" ? entry.totalTokens : undefined,
estimatedCostUsd:
typeof entry.estimatedCostUsd === "number" ? entry.estimatedCostUsd : undefined,
status: typeof entry.status === "string" ? entry.status : undefined,
startedAt: typeof entry.startedAt === "number" ? entry.startedAt : undefined,
endedAt: typeof entry.endedAt === "number" ? entry.endedAt : undefined,

View File

@ -60,33 +60,23 @@ describe("usage-format", () => {
expect(total).toBeCloseTo(0.003);
});
it("falls back to built in pricing for common models", () => {
it("returns undefined when model pricing is not configured", () => {
expect(
resolveModelCostConfig({
provider: "anthropic",
model: "claude-sonnet-4-6",
}),
).toEqual({
input: 3,
output: 15,
cacheRead: 0.3,
cacheWrite: 3.75,
});
).toBeUndefined();
expect(
resolveModelCostConfig({
provider: "openai-codex",
model: "gpt-5.4",
}),
).toEqual({
input: 2,
output: 8,
cacheRead: 0,
cacheWrite: 0,
});
).toBeUndefined();
});
it("prefers configured pricing over built in defaults", () => {
it("uses configured pricing when present", () => {
const config = {
models: {
providers: {

View File

@ -48,45 +48,6 @@ export function formatUsd(value?: number): string | undefined {
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;
@ -99,10 +60,7 @@ export function resolveModelCostConfig(params: {
}
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()}`];
return entry?.cost;
}
const toNumber = (value: number | undefined): number =>