/**
* Lightweight Anthropic usage meter.
*
* Pure observability — no behaviour. Each agent's LLM wrapper calls
* `recordUsage(...)` once per successful `messages.create` response.
* The orchestrator / demo prints `usageSnapshot()` at the end of a
* run so we can see call counts, token totals, and rough cost per
* agent without depending on transcripts or external billing pulls.
*/
export interface UsageRecord {
readonly agent: string;
readonly model: string;
readonly inputTokens: number;
readonly outputTokens: number;
readonly at: string;
}
export interface UsageRollupRow {
readonly agent: string;
readonly model: string;
readonly calls: number;
readonly inputTokens: number;
readonly outputTokens: number;
}
const records: UsageRecord[] = [];
export function recordUsage(agent: string, model: string, inputTokens: number, outputTokens: number): void {
records.push({ agent, model, inputTokens, outputTokens, at: new Date().toISOString() });
}
export function resetUsage(): void {
records.length = 0;
}
export function usageSnapshot(): {
readonly records: readonly UsageRecord[];
readonly byAgent: readonly UsageRollupRow[];
readonly totals: { calls: number; inputTokens: number; outputTokens: number };
} {
const byAgent = new Map<string, UsageRollupRow>();
for (const r of records) {
const key = `${r.agent}|${r.model}`;
const prior = byAgent.get(key);
if (prior) {
byAgent.set(key, {
agent: r.agent,
model: r.model,
calls: prior.calls + 1,
inputTokens: prior.inputTokens + r.inputTokens,
outputTokens: prior.outputTokens + r.outputTokens,
});
} else {
byAgent.set(key, {
agent: r.agent,
model: r.model,
calls: 1,
inputTokens: r.inputTokens,
outputTokens: r.outputTokens,
});
}
}
let calls = 0, inputTokens = 0, outputTokens = 0;
for (const row of byAgent.values()) {
calls += row.calls;
inputTokens += row.inputTokens;
outputTokens += row.outputTokens;
}
return { records: [...records], byAgent: [...byAgent.values()], totals: { calls, inputTokens, outputTokens } };
}
/** Anthropic Haiku 4.5 public list prices (USD per 1M tokens) at time
* of writing — confirm at console.anthropic.com/settings/billing for
* the authoritative rate. Used only for rough cost reporting. */
export const HAIKU_4_5_PRICE_PER_M_INPUT = 1.0;
export const HAIKU_4_5_PRICE_PER_M_OUTPUT = 5.0;
export function estimateCostUsd(inputTokens: number, outputTokens: number, model: string): number {
if (model.startsWith('claude-haiku-4-5')) {
return (inputTokens / 1_000_000) * HAIKU_4_5_PRICE_PER_M_INPUT
+ (outputTokens / 1_000_000) * HAIKU_4_5_PRICE_PER_M_OUTPUT;
}
// Unknown model — return 0 to avoid faking a cost we don't know.
return 0;
}