/**
 * cacheRecorder() — the cache layer's meter.
 *
 * Subscribes to:
 *   - `FlowRecorder.onDecision` — CacheGate routing decisions
 *     (apply-markers / no-markers + the rule that fired + evidence from
 *     `decide()`).
 *   - `agentfootprint.stream.llm_end` — reads that event's `usage` (the PORT
 *     shape: `{ input, output, cacheRead?, cacheWrite? }`) and asks the
 *     agent's `CacheStrategy.extractMetrics` what is known about it.
 *
 * Produces: a per-turn report via `recorder.report()` — token tallies, hit
 * rate and dollar estimates, each carried as a `Claim` so an UNMEASURED turn
 * can never render as a zero one.
 *
 * ── Why every number here is a Claim (9.59.0) ─────────────────────────
 * The shipped 9.58.0 meter reported `hitRate: 0` for a 20-call turn that hit
 * cache on every call. Two faults, and the second is why the first went
 * unnoticed for so long: (1) the strategies parsed RAW WIRE field names off a
 * value that carries the PORT shape, so every field read `undefined`; (2) the
 * report typed its aggregates as bare `number`, so "nobody measured" and
 * "measured, and it was zero" rendered identically. Fixing (1) alone would
 * have left a meter that still cannot say "unmeasured" — hence `Claim<T>`,
 * plus `measuredIterations` / `unmeasuredIterations` so a rate computed from
 * 3 of 20 calls states its own denominator.
 *
 * Read the number through `isKnown(report.hitRate)` (or `describeClaim` for a
 * one-line render). There is deliberately no door that hands you a bare
 * number without your having branched.
 *
 * ── And an unknown carries ITS OWN reason (9.59.1) ────────────────────
 * 9.59.0 enforced that law per call and then broke it in `report()`, which
 * hardcoded "the provider reported no cache fields" for the whole turn no
 * matter what the rows said. With no strategy passed and a provider that DID
 * report cache fields, the row said "nothing read the usage" and the summary
 * blamed the provider — pointing the reader away from their actual mistake.
 * The summary now carries the rows' own reasons, and says so plainly when the
 * rows disagree rather than picking one.
 *
 * ── What this recorder does NOT do ────────────────────────────────────
 * It emits no events. (Earlier prose here promised per-iteration
 * `agentfootprint.cache.applied` / `agentfootprint.cache.metrics` events;
 * neither name has ever existed in the event registry and no `typedEmit` has
 * ever been in this file. The prose was the bug.)
 *
 * It does not write `scope.recentHitRate` back into agent state either, so
 * CacheGate's hit-rate-floor rule never fires on its own — the loop is
 * severed at both ends (the key is seeded `undefined` and written by
 * nothing). Recorders do not write to chart scope, so closing that loop needs
 * an agent-side accessor convention; it is separable work from measuring the
 * number, which is what this file now does.
 */
import type { CombinedRecorder } from 'footprintjs';
import type { CacheMetrics, CacheStrategy } from './types.js';
import type { PricingTable } from '../provider-contract/types.js';
import { type Claim } from '../lib/claim/claim.js';
/** One LLM call's row on the record. */
export interface PerIterEntry {
    readonly iteration: number;
    readonly branch: 'apply-markers' | 'no-markers';
    readonly rule?: string;
    /**
     * What the strategy could say about this call's cache traffic. `known` =
     * the provider reported counts; `unknown` = nothing was measured;
     * `not-applicable` = this adapter cannot report cache usage at all.
     */
    readonly metrics: Claim<CacheMetrics>;
    /** Dollar estimates, `unknown` on any call whose metrics were not known. */
    readonly dollarsSpent: Claim<number>;
    readonly dollarsSavedVsNoCache: Claim<number>;
}
/**
 * The turn's tally. Every quantity derived from provider usage is a
 * {@link Claim}: unmeasured is a first-class answer, never a zero.
 */
export interface CacheReportSummary {
    /** LLM calls seen. Always known — the recorder counted them itself. */
    readonly totalIterations: number;
    readonly applyMarkersIterations: number;
    readonly noMarkersIterations: number;
    /**
     * Calls whose cache traffic the provider actually reported. The DENOMINATOR
     * every claim below is computed over — a rate from 3 of 20 calls is not the
     * turn's rate, and this is how a reader can tell.
     */
    readonly measuredIterations: number;
    /** Calls that reported nothing (no usage, no cache fields, or an adapter that cannot). */
    readonly unmeasuredIterations: number;
    readonly cacheReadTokensTotal: Claim<number>;
    readonly cacheWriteTokensTotal: Claim<number>;
    readonly freshInputTokensTotal: Claim<number>;
    /** cacheRead / (cacheRead + cacheWrite + fresh), over MEASURED calls only. */
    readonly hitRate: Claim<number>;
    readonly estimatedDollarsSpent: Claim<number>;
    readonly estimatedDollarsSavedVsNoCache: Claim<number>;
    readonly perIter: readonly PerIterEntry[];
}
export interface CacheRecorderOptions {
    /**
     * The agent's CacheStrategy — the thing that reads the port usage. Without
     * one every row's metrics are `unknown` (with that as the stated reason),
     * which is the honest answer: nothing read the usage.
     */
    readonly strategy?: CacheStrategy;
    /**
     * PricingTable for dollar estimates. Falls back to token-count-only
     * reporting when omitted. Looks up `'input'` / `'cacheRead'` /
     * `'cacheWrite'` token kinds (PricingTable already supports these
     * as of v2.5).
     */
    readonly pricing?: PricingTable;
    /**
     * Model id for pricing lookup. Defaults to a placeholder; set to
     * the actual model the agent is using for accurate dollar math.
     */
    readonly model?: string;
}
export interface CacheRecorderHandle extends CombinedRecorder {
    /**
     * Build a per-turn report. Call after `agent.run()` completes.
     * Returns a frozen snapshot — recorder keeps accumulating but the
     * report you held is stable.
     */
    report(): CacheReportSummary;
    /**
     * Reset accumulated state. Call between turns if you want
     * per-turn rather than per-session reporting.
     */
    reset(): void;
}
export declare function cacheRecorder(options?: CacheRecorderOptions): CacheRecorderHandle;
