import type { LlmProvider } from '@trazum/core'; /** * Not asking the model the same question twice. * * The roadmap item this answers was "prompt caching for `--suggest`", meaning * the API feature: mark a stable prefix with `cache_control` and pay a tenth of * the price for it on every later call. **That cannot work here, and the reason * is a number rather than an opinion.** * * Prompt caching has a minimum cacheable prefix — 512 tokens on the newest * models, 1,024 on most, 4,096 on some — and a prefix shorter than the minimum * is *silently* not cached: no error, no warning, `cache_creation_input_tokens` * comes back zero. Trazum's suggest prompt is **291 tokens**. Marking it would * have looked like an optimisation, cost a line of code, changed nothing, and * been impossible to notice. `suggest-cache.test.js` measures it against the * published minima so that stays true, or stops being true loudly. * * The stable prefix is also the only thing that *could* be cached: the rest of * the request is the author's prompt, which is different every time. So there * is no arrangement of `cache_control` that helps. * * What does help is the observation behind the request — running `--suggest` * over a directory asks the same questions again on every run, and most of the * prompts have not changed since the last one. Answering those from disk is not * a 90% saving on the call, it is the whole call. On a re-run after editing two * files out of forty, thirty-eight requests do not happen. * * Three decisions worth arguing with: * * **The raw response is cached, not the parsed suggestions.** Everything * `suggestRewrites` does after the model answers — checking each `before` * appears byte for byte, refusing anything that touches protected content, * dropping overlaps — is deterministic and lives in the core. Caching the text * means a hit is re-validated by *today's* rules rather than replaying a * verdict reached by an older version. Same reasoning as recomputing token * counts on read instead of storing them. * * **It is opt-in.** A cache hit returns what the model said last time, and a * model is not a pure function — silently answering from a week-old response * would be a surprise, in a tool whose other model-touching features * (`--suggest`, `--apply-suggestions`, `--reorder`) all require asking twice. * * **The files are 0600 in a 0700 directory.** The cache holds prompt text, and * a prompt is the most sensitive thing this tool ever touches — it is somebody's * unreleased product behaviour. A world-readable cache in a shared home * directory would publish it to every account on the machine. */ /** * Bumped when anything that shapes the answer changes and is not already in the * key — the suggest system prompt, the response format, the checking rules. * A stale entry answers a question that is no longer the one being asked. * * Exported so the test can derive a key independently rather than comparing * `cacheKey` to itself. */ export declare const SCHEMA = 2; /** Seven days. Long enough for a working week, short enough that an alias that started pointing at a new model does not answer forever. */ export declare const DEFAULT_TTL_DAYS = 7; export interface CacheEntry { schema: number; /** When it was written, so the TTL can be applied by the reader. */ at: number; provider: string; model: string; /** The model's answer, before any checking. */ response: string; } /** * Where the cache lives. * * `XDG_CACHE_HOME` first, because a user who set it meant it. Not the project * directory: two checkouts of the same repository ask the same questions, and a * per-checkout cache answers neither of them from the other. */ export declare function cacheDir(env?: NodeJS.ProcessEnv): string; /** * The key: everything that changes the answer, and nothing that does not. * * `provider` and `model` are in here rather than only in the entry because two * models answer differently — a hit from the wrong one is not a hit. The system * prompt is in here rather than relying on `SCHEMA` alone, so a caller passing * their own system prompt gets their own entries without anybody remembering to * bump a constant. */ export declare function cacheKey(input: { provider: string; model: string; system: string; user: string; }): string; export declare function readEntry(dir: string, key: string, now: number, ttlDays: number): CacheEntry | null; export declare function writeEntry(dir: string, key: string, entry: CacheEntry): void; /** Delete every entry. Returns how many went. */ export declare function clearCache(dir: string): number; /** Entry count and total bytes, so `--clear-suggestion-cache` can say what it emptied. */ export declare function cacheStats(dir: string): { entries: number; bytes: number; }; export interface CachedProvider extends LlmProvider { /** How many calls this provider answered from disk. */ readonly hits: number; /** How many it had to make. */ readonly misses: number; } /** * Wraps a provider so identical questions are asked once. * * A wrapper rather than a change inside `suggestRewrites`, for two reasons: the * core stays free of `node:fs` (it is browser-safe, and a test asserts the * import graph), and every command that reaches for an LLM gets the cache by * passing through one function rather than by each remembering to. */ export declare function cachingProvider(inner: LlmProvider, options: { dir: string; ttlDays?: number; now?: () => number; }): CachedProvider; //# sourceMappingURL=suggest-cache.d.ts.map