/** * Turn usage extraction (cost view spec): pi agent_end events carry every * message; assistant messages embed provider usage. One derived audit event * per (wake, provider/model) so the service can aggregate tokens and cost. */ export interface TurnUsage { provider: string model: string input: number output: number cacheRead: number cacheWrite: number /** assistant responses aggregated into this row (one model call each) */ responses: number } interface UsageLike { input?: unknown output?: unknown cacheRead?: unknown cacheWrite?: unknown } interface AssistantLike { role?: unknown model?: unknown provider?: unknown usage?: unknown } const num = (v: unknown): number => (typeof v === 'number' && Number.isFinite(v) ? v : 0) export function sumAssistantUsage(messages: unknown): TurnUsage[] { if (!Array.isArray(messages)) return [] const byKey = new Map() for (const message of messages) { const m = message as AssistantLike if (m?.role !== 'assistant') continue if (typeof m.usage !== 'object' || m.usage === null) continue const u = m.usage as UsageLike const model = typeof m.model === 'string' ? m.model : 'unknown' const provider = typeof m.provider === 'string' ? m.provider : 'unknown' const key = `${provider}/${model}` const agg = byKey.get(key) ?? { provider, model, input: 0, output: 0, cacheRead: 0, cacheWrite: 0, responses: 0, } agg.input += num(u.input) agg.output += num(u.output) agg.cacheRead += num(u.cacheRead) agg.cacheWrite += num(u.cacheWrite) agg.responses += 1 byKey.set(key, agg) } return [...byKey.values()] }