mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-15 16:20:44 +00:00
Serve usage.cost and sessions.usage from a durable transcript aggregate cache with guarded refreshes, cache-status UI localization, and regression coverage. Thanks @Marvinthebored.
196 lines
4.9 KiB
TypeScript
196 lines
4.9 KiB
TypeScript
import type { NormalizedUsage } from "../agents/usage.js";
|
|
import type {
|
|
SessionUsageTimePoint as SharedSessionUsageTimePoint,
|
|
SessionUsageTimeSeries as SharedSessionUsageTimeSeries,
|
|
} from "../shared/session-usage-timeseries-types.js";
|
|
|
|
export type CostBreakdown = {
|
|
total?: number;
|
|
input?: number;
|
|
output?: number;
|
|
cacheRead?: number;
|
|
cacheWrite?: number;
|
|
};
|
|
|
|
export type ParsedUsageEntry = {
|
|
usage: NormalizedUsage;
|
|
costTotal?: number;
|
|
costBreakdown?: CostBreakdown;
|
|
provider?: string;
|
|
model?: string;
|
|
timestamp?: Date;
|
|
};
|
|
|
|
export type ParsedTranscriptEntry = {
|
|
message: Record<string, unknown>;
|
|
role?: "user" | "assistant";
|
|
timestamp?: Date;
|
|
durationMs?: number;
|
|
usage?: NormalizedUsage;
|
|
costTotal?: number;
|
|
costBreakdown?: CostBreakdown;
|
|
provider?: string;
|
|
model?: string;
|
|
stopReason?: string;
|
|
toolNames: string[];
|
|
toolResultCounts: { total: number; errors: number };
|
|
};
|
|
|
|
export type CostUsageTotals = {
|
|
input: number;
|
|
output: number;
|
|
cacheRead: number;
|
|
cacheWrite: number;
|
|
totalTokens: number;
|
|
totalCost: number;
|
|
// Cost breakdown by token type (from actual API data when available)
|
|
inputCost: number;
|
|
outputCost: number;
|
|
cacheReadCost: number;
|
|
cacheWriteCost: number;
|
|
missingCostEntries: number;
|
|
};
|
|
|
|
type CostUsageDailyEntry = CostUsageTotals & {
|
|
date: string;
|
|
};
|
|
|
|
export type CostUsageSummary = {
|
|
updatedAt: number;
|
|
days: number;
|
|
daily: CostUsageDailyEntry[];
|
|
totals: CostUsageTotals;
|
|
cacheStatus?: {
|
|
status: "fresh" | "partial" | "stale" | "refreshing";
|
|
cachedFiles: number;
|
|
pendingFiles: number;
|
|
staleFiles: number;
|
|
refreshedAt?: number;
|
|
};
|
|
};
|
|
|
|
export type UsageCacheStatus = NonNullable<CostUsageSummary["cacheStatus"]>;
|
|
|
|
export type SessionDailyUsage = {
|
|
date: string; // YYYY-MM-DD
|
|
tokens: number;
|
|
cost: number;
|
|
};
|
|
|
|
export type SessionDailyMessageCounts = {
|
|
date: string; // YYYY-MM-DD
|
|
total: number;
|
|
user: number;
|
|
assistant: number;
|
|
toolCalls: number;
|
|
toolResults: number;
|
|
errors: number;
|
|
};
|
|
|
|
export type SessionUtcQuarterHourMessageCounts = {
|
|
date: string; // YYYY-MM-DD (UTC)
|
|
quarterIndex: number; // 0-95, UTC quarter-hour bucket (index = floor((utcH * 60 + utcM) / 15))
|
|
total: number;
|
|
user: number;
|
|
assistant: number;
|
|
toolCalls: number;
|
|
toolResults: number;
|
|
errors: number;
|
|
};
|
|
|
|
export type SessionUtcQuarterHourTokenUsage = {
|
|
date: string; // YYYY-MM-DD (UTC)
|
|
quarterIndex: number; // 0-95, UTC quarter-hour bucket (index = floor((utcH * 60 + utcM) / 15))
|
|
input: number;
|
|
output: number;
|
|
cacheRead: number;
|
|
cacheWrite: number;
|
|
// Uses the same token total basis as CostUsageTotals: usage.total when present,
|
|
// otherwise input + output + cacheRead + cacheWrite. This intentionally differs
|
|
// from legacy dailyBreakdown.tokens, which preserves its existing component-sum
|
|
// behavior until daily usage buckets are refactored separately.
|
|
totalTokens: number;
|
|
totalCost: number;
|
|
};
|
|
|
|
export type SessionLatencyStats = {
|
|
count: number;
|
|
avgMs: number;
|
|
p95Ms: number;
|
|
minMs: number;
|
|
maxMs: number;
|
|
};
|
|
|
|
export type SessionDailyLatency = SessionLatencyStats & {
|
|
date: string; // YYYY-MM-DD
|
|
};
|
|
|
|
export type SessionDailyModelUsage = {
|
|
date: string; // YYYY-MM-DD
|
|
provider?: string;
|
|
model?: string;
|
|
tokens: number;
|
|
cost: number;
|
|
count: number;
|
|
};
|
|
|
|
export type SessionMessageCounts = {
|
|
total: number;
|
|
user: number;
|
|
assistant: number;
|
|
toolCalls: number;
|
|
toolResults: number;
|
|
errors: number;
|
|
};
|
|
|
|
export type SessionToolUsage = {
|
|
totalCalls: number;
|
|
uniqueTools: number;
|
|
tools: Array<{ name: string; count: number }>;
|
|
};
|
|
|
|
export type SessionModelUsage = {
|
|
provider?: string;
|
|
model?: string;
|
|
count: number;
|
|
totals: CostUsageTotals;
|
|
};
|
|
|
|
export type SessionCostSummary = CostUsageTotals & {
|
|
sessionId?: string;
|
|
sessionFile?: string;
|
|
firstActivity?: number;
|
|
lastActivity?: number;
|
|
durationMs?: number;
|
|
activityDates?: string[]; // YYYY-MM-DD dates when session had activity
|
|
dailyBreakdown?: SessionDailyUsage[]; // Per-day token/cost breakdown
|
|
dailyMessageCounts?: SessionDailyMessageCounts[];
|
|
utcQuarterHourMessageCounts?: SessionUtcQuarterHourMessageCounts[]; // UTC quarter-hour buckets for precise hourly stats
|
|
utcQuarterHourTokenUsage?: SessionUtcQuarterHourTokenUsage[]; // UTC quarter-hour buckets for precise token mosaic stats
|
|
dailyLatency?: SessionDailyLatency[];
|
|
dailyModelUsage?: SessionDailyModelUsage[];
|
|
messageCounts?: SessionMessageCounts;
|
|
toolUsage?: SessionToolUsage;
|
|
modelUsage?: SessionModelUsage[];
|
|
latency?: SessionLatencyStats;
|
|
};
|
|
|
|
export type DiscoveredSession = {
|
|
sessionId: string;
|
|
sessionFile: string;
|
|
mtime: number;
|
|
firstUserMessage?: string;
|
|
};
|
|
|
|
export type SessionUsageTimePoint = SharedSessionUsageTimePoint;
|
|
|
|
export type SessionUsageTimeSeries = SharedSessionUsageTimeSeries;
|
|
|
|
export type SessionLogEntry = {
|
|
timestamp: number;
|
|
role: "user" | "assistant" | "tool" | "toolResult";
|
|
content: string;
|
|
tokens?: number;
|
|
cost?: number;
|
|
};
|