/** * WHICH MODEL WAS ON THE OTHER END, resolved from evidence rather than hope. * * THE DEFECT THIS REPLACES. `McpEvidenceRecorder.analyticsAttribution` used to * read the model from `process.env.TOKEN_OPTIMIZER_MODEL` and nothing else -- * a variable this package never sets, never documents and no client exports. * So every analytics row recorded `model: null`, and a live ledger read: * * (unattributed) 5,440,720 tokens 3,884 ops not priced * claude-sonnet 27,134 tokens 3 ops not priced * * 3,884 of 3,895 verified-savings operations had no model, which meant 98.9% of * every token we could prove we saved could not be priced, and the dollar * figure the product reports was computed from five rows. The pricing catalog * was never the problem; the attribution was never wired. * * WHY NOT INFER IT. The tempting fix is to guess -- the client's name implies a * vendor, the vendor has a flagship, call it that. That would put a fabricated * model id underneath a priced figure inside a provenance-gated report, which * is the one thing every other measurement in this package refuses to do. A * guessed model is worse than no model: `(unattributed)` is visibly missing, * whereas a wrong model prices silently and wrongly. * * SO IT IS READ FROM THE CLIENT'S OWN LOG. Both agents we have a plugin for * already write the exact model id to disk for every turn -- Claude Code as * `message.model` on each assistant record in its session transcript, Codex as * `payload.model` in its rollout's session meta. Those are exact catalog ids * (`claude-opus-5`, not "opus"), written by the client itself, and they are * already on the machine. Reading them is observation, not inference. * * PRIVACY. The transcript is conversation content. This module reads a bounded * window of it, extracts one field, and returns a model id. No other field is * read, nothing is retained past the call, and nothing is logged -- the only * value that leaves is a model id or null. The window is parsed as JSON lines * and the id is taken from a parsed record, never matched out of the raw text: * a transcript routinely CONTAINS the string `"model": "..."` inside quoted * tool output and pasted code, and a regex over the buffer would happily * attribute a saving to a model id someone mentioned in a message. * * COST. Resolution is a tail read, not a scan: the newest records are at the * end of the file, so the window starts at 64KB and grows only if that window * held no assistant turn. Measured on a 199MB live transcript: 1ms at 64KB, * 1ms at 1MB, 14ms at 8MB, and the answer came from the first window. The * result is then cached for a few seconds so a burst of tool calls reads once. */ /** * WHERE A MODEL ID CAME FROM, recorded alongside it. * * A priced figure is only as good as its model id, so a reviewer has to be able * to tell a value the operator declared from one we observed in a log from one * we never found. Without this the three are indistinguishable in the ledger * and the first question asked of any surprising dollar figure -- "where did * that model come from?" -- has no answer. */ export declare const MODEL_SOURCES: Readonly<{ /** The operator set TOKEN_OPTIMIZER_MODEL. A stated answer beats an observed one. */ readonly Declared: "declared"; /** Read from `message.model` on the newest assistant turn of the session transcript. */ readonly ClaudeCodeTranscript: "claude-code-transcript"; /** Read from `payload.model` in the session meta of the Codex rollout. */ readonly CodexRollout: "codex-rollout"; /** No source produced one. The row stays unattributed, and says why. */ readonly None: "none"; }>; export type ModelSource = (typeof MODEL_SOURCES)[keyof typeof MODEL_SOURCES]; export interface ModelAttribution { readonly model: string | null; readonly source: ModelSource; } /** * Resolve the model behind the current session, uncached. * * ORDERED BY HOW DIRECTLY THE ANSWER WAS STATED, and it stops at the first * source that produces one. A declared value wins because an operator who sets * it has overridden us on purpose -- including the case where they are routing * a client through a model its own log would not name. * * Both logs are then consulted regardless of which client connected, because * the client name is not a reliable discriminator (a wrapper, a proxy or a * relaunch can all present something else) and a transcript named after this * process's own session id is proof enough on its own. */ export declare function resolveModelUncached(): Promise; /** * Resolve the model behind the current session. * * NEVER THROWS, because this sits on the path of every recorded tool result and * an attribution failure must cost a model id, not the measurement it was going * to label. */ export declare function resolveModel(now?: number): Promise; /** Drop the cached attribution. For tests, and for a deliberate re-read. */ export declare function resetModelAttributionCache(): void; //# sourceMappingURL=model-attribution.d.ts.map