repositories / will
will
owned by admin
src/agent/usage.ts
Raw/**
* 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<string, TurnUsage>()
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()]
}