/** * Usage accounting — the host's own numbers, read in one place. * * WHAT THIS IS, AND WHAT IT IS NOT * Four pure readers, and they TRIGGER NOTHING. `readUsedTokens` sums a usage block the * way the host sums it (`input + output + reasoning + cache.read + cache.write`), * `readModelKey` pulls the providerID+modelID that produced it, `lastUsageOf` walks a * transcript for the newest reading, and `percentOf` turns a numerator plus the * catalog's `limit.context` into the percent every threshold in this package compares * against. `cacheStatsOf` turns that same block's cache counters into a hit rate — and * answers NULL, never 0%, when the provider reported no cache at all. * * Until 1.7.0 these lived in `v2-compaction.ts`, whose layer submitted * `ctx.session.compact({sessionID})` on its own once a session crossed 75% of the * window. That AUTOMATIC compaction was removed in 1.7.1 — a plugin that summarizes a * live conversation by itself is a context-losing action the user did not ask for, and * the host owns when a session is summarized. The readers stayed because * `v2-prune.ts` still needs the same formula and the same denominator for ITS OWN * percent: one definition of "how full is this window", not two that can drift. * * WHY THE READERS LOOK THIS DEFENSIVE (evidence kept from the removed layer, because it * is the reason for every branch below): the live 2.0.23 request payload carries NO usage * on its messages (`measured source=no_usage messages=3`, run * r-20261005-235343-114738) — the numbers ride the host's `session.usage.updated` event. * So every location the host has actually used is accepted (`tokens` / * `metadata.tokens` / `info.tokens` / `usage`) and "found nothing" returns null rather * than 0: "we did not find them" is not "usage is zero", and the two must never collapse * into one number. The denominator comes from `ctx.model.list()` alone, so a model the * catalog does not describe has no percent — `percentOf` answers 0 instead of inventing * a window. */ import type { V2Context } from "./v2-types.js"; /** * The host's usage block, summed the way the host sums it. Returns null when the object * carries no numbers at all — "we did not find them" is not "usage is zero". */ export declare function readUsedTokens(message: unknown): number | null; /** * Cache accounting carried by ONE usage block. `present` records whether the block * had a `cache` object at all: providers that report no caching and providers that * report a 0-hit cache are different facts, and only the second one is a rate. */ export interface UsageCacheCounts { read: number; write: number; input: number; present: boolean; } /** The model that produced that usage — the key we need to find its context limit. */ export declare function readModelKey(message: unknown): { providerID: string; id: string; } | null; /** * Newest message that carries usage wins — that is what the host's meter calls * `usage.last`. `cache` rides along from the SAME block as `used`, so the hit rate * can never be computed against a different request than the token total. */ export declare function lastUsageOf(messages: unknown): { used: number; model: { providerID: string; id: string; } | null; cache: UsageCacheCounts; } | null; export declare function percentOf(used: number, limit: number): number; /** * One window reading: the newest usage, the catalog window that produced it, and * the percent between them. `model` is NON-null by construction — a reading * without a model has no denominator, and that case returns null instead of a * guessed 0%. `cache` is the cache accounting of the SAME usage block; its * `present` flag is what `cacheStatsOf` refuses to turn into a 0% hit rate. */ export interface UsagePercentReading { used: number; limit: number; percent: number; model: { providerID: string; id: string; }; cache: UsageCacheCounts; } export type UsagePercentReader = (sessionID: string, messages: unknown) => Promise; /** The cache numbers, plus the one ratio they support. */ export interface CacheStats { read: number; write: number; input: number; /** `read / (read + write + input)`, 0..1 — null when it cannot be computed. */ hitRate: number | null; } /** * Cache hit rate from one usage reading. * * NULL, never 0, in two cases — and the distinction is the whole point: * - the block carried no `cache` object: that provider does not report caching, * so "0% hit" would be a number we invented from a missing field; * - all three values are 0: the denominator is 0, and dividing is guessing. * A provider that really reported `cache: { read: 0, write: 0 }` with a non-zero * input IS a 0% rate and comes back as `0`. * * `read / (read + write + input)`: the denominator counts every input token once, * because a cache read and a cache write are both a way an input token was billed. */ export declare function cacheStatsOf(reading: UsagePercentReading | null | undefined): CacheStats; /** * Resolve "how full is this window" from the request plus the catalog — the same * denominator the desktop's usage meter reads (`ctx.model.list()` → `limit.context`), * which is what makes the percent we render mean the same percent. * * The catalog is read LAZILY and cached for the process (a refresh happens once per * unknown model key, not once per request), because `model.list` is an async host call * on the request path. Every uncertain branch returns NULL rather than a number: * no usage, no model key, no catalog entry → "we could not read it", which the caller * must render as "no entry", never as "0%". */ export declare function createUsagePercent(ctx: V2Context): UsagePercentReader; //# sourceMappingURL=v2-usage.d.ts.map