import type { PricingCatalogue } from './pricing.js'; import type { UsageRecord } from './usage.js'; /** * Does the cache TTL fit how fast the turns actually arrive? * * ## The mechanism nothing else can see * * A cache entry lives 5 minutes, or an hour at twice the write price. Whether * either is the right choice depends on one number the bill never shows: **how * long the workload waits between turns.** A support agent whose users answer in * nine minutes writes a 5-minute entry on every turn and reads it back on none of * them — every write expires unread, which from the bill is indistinguishable * from any other losing cache. `cacheEconomics` can say *that* money was lost; * only the clock can say *why*, and the why decides the fix: the 1-hour TTL, or * caching switched off. * * The opposite mistake is quieter and this is the only place it appears at all: * turns arriving seconds apart, written at the 1-hour rate. Those writes work — * the verdict above reads `paid-off` — and every one of them pays 2x input for * endurance the workload never uses. **Switching them to the 5-minute TTL is the * one exact saving in this module**: the same tokens at 1.25x instead of 2x, * which is the same-tokens-different-rate arithmetic `cacheEconomics` already * draws the counterfactual line at. * * ## What it measures, and how it stays honest * * The gap between consecutive turns **of the same conversation**, from the * recorded clock — sessions are what a cache entry actually serves, and gaps * between unrelated calls of a label say nothing about whether *this* * conversation's next turn found the entry alive. Timestamps are sorted within * each session before differencing, so the measurement is independent of the * order of the log — the property the conversation tracker had to learn the * hard way. * * The reported number is the **median** gap, named as such: a median survives * the overnight gap between a user's Tuesday and Wednesday in a way a mean does * not, and a verdict hung on a mean would flip on one lunch break. * * When the log did not record which TTL the writes used, the gap can sit where * the verdict depends on the answer — over 5 minutes and under an hour survives * one TTL and not the other. That is reported as `unsettled`, the same refusal * `cacheEconomics` makes for the same missing field, and never resolved in the * flattering direction. * * The session key is used to group turns and never leaves this module, like * everywhere else the field is touched. */ /** Cache-entry lifetimes, in milliseconds. Anthropic's two published TTLs. */ export declare const TTL_5M_MS: number; export declare const TTL_1H_MS: number; export type TtlFitVerdict = /** The median gap outlives the entry: writes expire before the next turn. */ 'expires-before-reuse' /** 1-hour writes on gaps inside the 5-minute window: paying 2x for nothing. */ | 'overlong-ttl' /** The TTL the log did not record decides the verdict, so nothing does. */ | 'unsettled' /** The entry outlives the gap at the TTL the writes actually used. */ | 'fits'; export interface CacheTtlFit { label: string; model: string; modelName: string; /** Conversations with at least two timestamped turns. Never which ones. */ sessions: number; /** Gaps measured across them. */ gaps: number; medianGapMs: number; /** Write tokens the log said were 5-minute entries. */ write5mTokens: number; /** Write tokens the log said were 1-hour entries. */ write1hTokens: number; /** Write tokens whose TTL the log did not record. */ assumedTtlTokens: number; verdict: TtlFitVerdict; /** * What the 1-hour writes would save at the 5-minute rate, when the gaps show * the hour is never needed. Exact — the same tokens at 1.25x instead of 2x, * at the model's own input rate — and zero for every other verdict. */ overpayUsd: number; } export interface TtlFitOptions { catalogue: PricingCatalogue; /** Date the prices are read at, so a promotional rate resolves the same way. */ on?: Date; } export interface TtlFitTracker { /** Feed one parsed record. */ add(record: UsageRecord): void; /** The finished measurement. */ finish(): CacheTtlFit[]; } /** * An accumulator, like the conversation tracker and for the same reason: a * usage log is measured in megabytes and `profileUsage` makes one pass. What * this holds is one number per timestamped call that belongs to a session, * which is the minimum the gaps can be computed from at all. */ export declare function createTtlFitTracker(options: TtlFitOptions): TtlFitTracker; /** The same measurement over a list, for a caller holding one already. */ export declare function cacheTtlFit(records: readonly UsageRecord[], options: TtlFitOptions): CacheTtlFit[]; //# sourceMappingURL=ttl-fit.d.ts.map