Estimate session costs in sessions list

This commit is contained in:
Tyler Yust
2026-03-12 01:50:15 -07:00
parent ee2563a38b
commit 067af13502
3 changed files with 75 additions and 0 deletions

View File

@@ -836,6 +836,46 @@ describe("listSessionsFromStore search", () => {
expect(missing?.totalTokens).toBeUndefined();
expect(missing?.totalTokensFresh).toBe(false);
});
test("includes estimated session cost when model pricing is configured", () => {
const cfg = {
session: { mainKey: "main" },
agents: { list: [{ id: "main", default: true }] },
models: {
providers: {
openai: {
models: [
{
id: "gpt-5.4",
label: "GPT 5.4",
baseUrl: "https://api.openai.com/v1",
cost: { input: 1.25, output: 10, cacheRead: 0.125, cacheWrite: 0.5 },
},
],
},
},
},
} as unknown as OpenClawConfig;
const result = listSessionsFromStore({
cfg,
storePath: "/tmp/sessions.json",
store: {
"agent:main:main": {
sessionId: "sess-main",
updatedAt: Date.now(),
modelProvider: "openai",
model: "gpt-5.4",
inputTokens: 2_000,
outputTokens: 500,
cacheRead: 1_000,
cacheWrite: 200,
} as SessionEntry,
},
opts: {},
});
expect(result.sessions[0]?.estimatedCostUsd).toBeCloseTo(0.007725, 8);
});
});
describe("listSessionsFromStore subagent metadata", () => {

View File

@@ -44,6 +44,7 @@ import {
resolveAvatarMime,
} from "../shared/avatar-policy.js";
import { normalizeSessionDeliveryFields } from "../utils/delivery-context.js";
import { estimateUsageCost, resolveModelCostConfig } from "../utils/usage-format.js";
import { readSessionTitleFieldsFromTranscript } from "./session-utils.fs.js";
import type {
GatewayAgentRow,
@@ -213,6 +214,32 @@ function resolveSessionRuntimeMs(
return Math.max(0, (run.endedAt ?? now) - run.startedAt);
}
function resolveEstimatedSessionCostUsd(params: {
cfg: OpenClawConfig;
provider?: string;
model?: string;
entry?: SessionEntry;
}): number | undefined {
const cost = resolveModelCostConfig({
provider: params.provider,
model: params.model,
config: params.cfg,
});
if (!cost) {
return undefined;
}
const estimated = estimateUsageCost({
usage: {
input: params.entry?.inputTokens,
output: params.entry?.outputTokens,
cacheRead: params.entry?.cacheRead,
cacheWrite: params.entry?.cacheWrite,
},
cost,
});
return typeof estimated === "number" && Number.isFinite(estimated) ? estimated : undefined;
}
function resolveChildSessionKeys(controllerSessionKey: string): string[] | undefined {
const childSessions = Array.from(
new Set(
@@ -960,6 +987,12 @@ export function listSessionsFromStore(params: {
const model = resolvedModel.model ?? DEFAULT_MODEL;
const subagentRun = getSubagentRunByChildSessionKey(key);
const childSessions = resolveChildSessionKeys(key);
const estimatedCostUsd = resolveEstimatedSessionCostUsd({
cfg,
provider: modelProvider,
model,
entry,
});
return {
key,
spawnedBy: entry?.spawnedBy,
@@ -987,6 +1020,7 @@ export function listSessionsFromStore(params: {
outputTokens: entry?.outputTokens,
totalTokens: total,
totalTokensFresh,
estimatedCostUsd,
status: resolveSessionRunStatus(subagentRun),
startedAt: subagentRun?.startedAt,
endedAt: subagentRun?.endedAt,

View File

@@ -43,6 +43,7 @@ export type GatewaySessionRow = {
outputTokens?: number;
totalTokens?: number;
totalTokensFresh?: boolean;
estimatedCostUsd?: number;
status?: SessionRunStatus;
startedAt?: number;
endedAt?: number;