import type { PricingCatalogue } from './pricing.js'; import type { UsageRecord } from './usage.js'; /** * The retry bill of truncation. * * ## The half of the cost the truncation line could not see * * `truncatedOutputUsd` prices the answers cut off at `max_tokens` — paid in * full, and the attempt bought nothing. The sentence next to it has always * said "frequently retried, billed again", and that second half was an * assertion rather than a measurement: nothing counted the retries. * * This does. A truncated answer followed **within a couple of minutes by * another call in the same conversation** is the shape a retry has — the * user or the harness asked again, usually with a raised ceiling — and both * sides of that pair are in the log. The first call's full price is money * that bought a cut-off answer; the follow-up is the same question billed a * second time, at conversation prices, with the history re-sent. * * ## What it compares, and why only that * * Each call is checked against the one immediately before it in the same * session — the bounded-memory design `repeats.ts` uses, for the same * reasons — and only when the previous call was truncated and the gap is * non-negative and inside the window. Two minutes by default, wider than the * repeat window: a human rephrasing after a cut-off answer takes longer than * a harness retrying a timeout. * * ## What it refuses to conclude * * It cannot see content, so it cannot tell a retry from a user changing the * subject right after a truncated answer. The pair is a pattern, stated as * one. A single pair is not reported — one retry is an anecdote — and the * per-slice denominator (`truncatedCalls` that *could* be checked) travels * with the count, so "3 of 40" and "3 of 3" read as differently as they are. */ /** Truncated answers followed by another call in the same conversation. */ export interface TruncationRetry { label: string; model: string; modelName: string; /** Truncated calls that were followed up inside the window. */ retried: number; /** Truncated calls in this slice that carried a session and a clock. */ truncatedCalls: number; /** The full price of the truncated attempts that were followed up. */ wastedUsd: number; /** The full price of the follow-up calls. */ retryUsd: number; /** The window the follow-up had to fall inside, in milliseconds. */ withinMs: number; } export interface TruncationRetryOptions { catalogue: PricingCatalogue; on?: Date; /** * How close the follow-up has to be. Two minutes by default — a human * rephrasing after a cut-off answer takes longer than a harness retrying, * and past a couple of minutes the next call is a next question. */ withinMs?: number; /** Slices below this many retried pairs are dropped. Default 2. */ minRetried?: number; } export interface TruncationRetryTracker { add(record: UsageRecord): void; finish(): TruncationRetry[]; } /** An accumulator, fed in the pass a profile already makes. */ export declare function createTruncationRetryTracker(options: TruncationRetryOptions): TruncationRetryTracker; /** The same measurement over a list of records, for a caller holding one. */ export declare function truncationRetries(records: readonly UsageRecord[], options: TruncationRetryOptions): TruncationRetry[]; //# sourceMappingURL=truncation-retry.d.ts.map