import type { PricingCatalogue } from './pricing.js'; import type { UsageRecord } from './usage.js'; /** * Cache writes made by conversations that never came back. * * ## The waste the aggregate hides * * A cache write is a bet: pay 1.25x input now (2x at the 1-hour TTL) so the * *next* call reads the prefix at 0.1x. A conversation that ends after its * first turn never places that next call — its write bought reuse that its own * conversation never made. On a workload with many short sessions this is a * steady leak, and it hides inside healthy-looking totals: the long sessions' * reads pay for the cache overall, so `cacheEconomics` reports `paid-off` * while every one-turn drive-by pays the premium for nothing. * * ## The caveat that keeps the figure honest * * The provider's cache is keyed by prefix content, not by conversation. A * one-turn session's write **can** be read back by a different session that * sends the same prefix within the TTL — a shared system prompt does exactly * that — and a usage log cannot see whose write a read hit. So the figure * reported here is a **ceiling, named as one**: these writes paid off only if * another conversation shared the prefix in time, and the log cannot say * whether one did. * * There is one case where the ceiling collapses into a fact, and the caller * can detect it from the slice it already has: when the slice recorded **zero * cache reads**, nothing read those writes — within the session, across * sessions, at all. The row deliberately does not decide this itself; the * breakdown holding the slice's reads belongs to the caller, and deriving it * twice is how two figures drift. * * Session keys group turns and never leave this module, like everywhere else * the field is touched. */ export interface SingleTurnCacheWrites { label: string; model: string; modelName: string; /** Conversations seen in this slice — with a session key, priced model. */ sessions: number; /** Median turns per conversation, for scale: 1-turn sessions in a sea of 40-turn ones read differently than in a sea of 2s. */ medianTurns: number; /** Conversations that ended after exactly one recorded turn. */ singleTurnSessions: number; /** Cache-write tokens those one-turn conversations paid for. */ singleTurnWriteTokens: number; /** * What those writes cost, at the same rates the bill used — the 5-minute * rate for writes whose TTL the log did not state, so like the bill it is * a floor when `assumedTtlTokens` is non-zero. A **ceiling on the waste** * (another conversation may have read the prefix; the log cannot see it) * built on a **floor of a price** — both directions named, neither guessed. */ singleTurnWriteUsd: number; /** The part of `singleTurnWriteTokens` whose TTL the log did not record. */ assumedTtlTokens: number; } export interface SessionLedgerOptions { catalogue: PricingCatalogue; /** Date the prices are read at, so a promotional rate resolves the same way. */ on?: Date; } export interface SessionLedgerTracker { add(record: UsageRecord): void; finish(): SingleTurnCacheWrites[]; } /** * An accumulator, like the TTL-fit tracker and for the same reason: one pass * over a log measured in megabytes, holding one small tally per conversation. * No timestamp needed — "came back" is a fact about turn count, not the clock, * so this measures logs the TTL-fit cannot. */ export declare function createSessionLedgerTracker(options: SessionLedgerOptions): SessionLedgerTracker; /** The same measurement over a list, for a caller holding one already. */ export declare function singleTurnCacheWrites(records: readonly UsageRecord[], options: SessionLedgerOptions): SingleTurnCacheWrites[]; //# sourceMappingURL=session-ledger.d.ts.map