import type { PricingCatalogue } from './pricing.js'; import type { UsageRecord } from './usage.js'; /** * What one conversation costs. * * ## The question a total cannot answer * * "Support cost $4,000 last month" does not say whether that is forty thousand * cheap conversations or four hundred expensive ones, and every decision made * on top of it needs the answer: what to charge per seat, where to put a quota, * whether one runaway agent loop is eating the budget. The bill has the data — * the log groups by `session` already — and nothing was reporting it. * * ## Median and p95, not mean * * A mean conversation cost is the total divided by the session count, which is * the total again wearing a hat: one 400-turn agent loop drags it up and hides * the ordinary case. The **median** is the conversation in the middle — what a * typical one costs — and the **p95** is the one a quota has to survive. The * gap between them is the finding: `$0.02 median, $1.80 p95` is a workload with * a tail worth hunting; `$0.40 median, $0.55 p95` is a workload that is simply * expensive, and no amount of tail-hunting will fix it. * * Every figure is **exact** — the provider's own billed counts, summed per * conversation at each model's published rates. No counterfactual, no estimate. * * ## What it refuses to claim * * A conversation that started before this log or continues after it is counted * only for the turns recorded here, so its cost is a floor. That is stated * rather than corrected: guessing at unseen turns would be exactly the kind of * invention this package exists to end. Session keys group turns and never * leave this module, as everywhere the field is touched. */ export interface SessionCostShape { label: string; model: string; modelName: string; /** Conversations measured. Never which ones. */ sessions: number; calls: number; /** What those conversations cost in total — exact, billed. */ totalUsd: number; /** The conversation in the middle. */ medianUsd: number; /** The conversation a quota has to survive: 95th percentile, by nearest rank. */ p95Usd: number; /** The single most expensive conversation in the slice. */ maxUsd: number; /** Turns in the median conversation, for scale. */ medianTurns: number; } export interface SessionCostOptions { catalogue: PricingCatalogue; on?: Date; /** * Slices with fewer conversations than this are dropped: a median over three * sessions is not a median, it is one of the three, and a p95 over them is * the maximum wearing a percentile's name. Default 5. */ minSessions?: number; } export interface SessionCostTracker { add(record: UsageRecord): void; finish(): SessionCostShape[]; } /** Every billed dollar of one call, at its own model's rates. */ export declare function costOf(record: UsageRecord, catalogue: PricingCatalogue, on: Date): number | null; export declare function createSessionCostTracker(options: SessionCostOptions): SessionCostTracker; /** The same measurement over a list, for a caller holding one already. */ export declare function sessionCostShapes(records: readonly UsageRecord[], options: SessionCostOptions): SessionCostShape[]; //# sourceMappingURL=session-cost.d.ts.map