Luigit
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()]
}