/** * How an elision is written into the text the model reads. * * THE WHOLE COMPETITIVE ARGUMENT IS IN THIS FILE, so it is worth being explicit * about what is being avoided. * * HeadRoom emits `<>` and, to make that * redeemable, appends to every compressed request a `## Compressed Context * Available` system message plus a `headroom_retrieve` tool definition * (`headroom/ccr/tool_injection.py:143`). Roughly 200 tokens of preamble per * request, and around 8,000 across a forty-turn session -- context spent in * order to save context. * * A marker here costs nothing extra. It is a sentence naming what went and a * path the agent can already read, so: * * - no system message, no tool definition, no hash, nothing injected; * - a model that does not need the content spends nothing; * - a model that does need it uses `Read`, which it already knows; * - and if our bookkeeping is gone the path still resolves to the real file, * where their marker resolves to `[unresolved: entry not found]`. */ import type { Elision, EngineContext, Stamp } from './types.js'; export declare const STAMP_CHARS = 9; export declare function stampFor(text: string): string; /** * The context an engine should work from: the caller's, with a stamp. * * EVERY ENGINE ENTRY POINT GOES THROUGH THIS, so no caller can get output it * has no way to decode. An engine run with a bare context used to emit markers * and leave the caller to guess; it now mints one from the content and hands it * back on the result, which is the only shape under which `rehydrate` can be * called correctly by someone who did not build the context. * * `null` IS A CHOICE AND IS KEPT. A caller who passes it means `do not stamp`, * and gets markers nothing will honour -- which is what the published arm of * the comparator wants, where the output is scored as text and never decoded. * Only `undefined` means `I did not think about this`. */ export declare function withStamp(ctx: EngineContext, text: string): EngineContext & { readonly stamp: Stamp; }; /** * NULL IS NOT "ANY". A decoder handed no stamp honours no marker: every * marker-shaped line is then content, and content is returned verbatim. That is * the safe direction -- the cost of being wrong is a line that reads like a * marker surviving into the output, against a block denied and an attacker's * path quoted back at the caller. */ export type { Stamp }; /** Does this marker body carry the stamp we are expecting? */ export declare function stamped(body: string, stamp: Stamp): boolean; /** * ONE SHAPE, so a model learns it once. * * Square brackets and a leading ellipsis, because that is how humans have * written elision in quoted text for a century and models have read a great * deal of it. Deliberately NOT an angle-bracket sigil: those read as markup and * invite the model to treat them as a protocol it must satisfy. */ export declare function marker(elision: Pick, stamp?: Stamp): string; /** * Is this line a marker THIS decoder wrote? * * The one definition of that question, because every decoder in this directory * has to ask it and they must all answer it the same way. A line in the marker * envelope carrying the stamp is ours: expand it, or refuse with the reason. * Anything else is content, and content is returned as it arrived. * * THE STAMP SITS BEFORE THE ARROW, not at the very end: `recoverAt` is a path * and a path has no business carrying our authenticator. So the suffix is cut * off before the body is checked, which also means a marker whose stamp is in * the PATH -- `[... 400 lines -> /x ~abc]` -- is not ours, which is correct: * nothing we emit puts it there. */ export declare function isStamped(line: string, stamp: Stamp): boolean; /** * The stamp suffix a decoder pattern has to allow for, as regex source. * * EVERY DECODER PATTERN IN THIS DIRECTORY ANCHORS ON `]`, so the stamp this * encoder appends sits between the last thing those patterns match and that * bracket. They each need the same fragment spliced in, and they need it to * match NOTHING when there is no stamp -- a decoder handed none honours no * marker, which is what makes planted content inert. * * The tag is narrowed to the alphabet `stampFor` draws from, so a caller * cannot smuggle a quantifier or a group into a decoder pattern through it. */ export declare function stampPattern(stamp: Stamp): string; /** * Refuses a stamp that is neither a string nor `null`, by name. * * A DECODER TAKES ITS STAMP AS A SECOND ARGUMENT, which makes every decoder * entry point accidentally shaped like an `Array.prototype.map` callback -- * `texts.map(decode)` then hands it the INDEX, and a number reaches a regex * builder as `stamp.replace is not a function` from four frames down. That * happened at four call sites while this was being written. Naming the mistake * where it is made costs one comparison per pattern built. * * It throws rather than coercing, because every quiet reading of a number is * wrong: as a stamp it honours no marker and the output silently keeps lines * that should have expanded, and as `null` it says the caller meant `do not * decode` when the caller meant nothing of the kind. */ export declare function assertStamp(stamp: Stamp): void; /** * The path out of a lossy marker, or null when the line is not one. * * `rehydrate` rebuilds the input from the output ALONE, so a `-> path` marker * is something it can never expand: the path is the whole point of it. That is * a completely different fact from "a marker family nobody registered", and a * caller which cannot tell the two apart files by-design behaviour on a defect * queue -- which is exactly what the head-to-head harness was doing, reporting * six refusals that were the design working. * * The parser lives here because this file owns the envelope both forms share. */ export declare function pathAddressed(line: string, stamp?: Stamp): string | null; /** * A marker that was recognised and is recoverable, just not from here. * * Carries the path so a caller can score the content as retrieved-in-one-read * rather than lost. */ export declare class PathAddressedError extends Error { readonly recoverAt: string; constructor(recoverAt: string); } /** An inline elision, written where the content used to be. */ export declare function inlineMarker(removed: string, recoverAt: string | null, stamp?: Stamp): string; /** * A short, plural-correct count phrase. * * Small thing, but "1 duplicate lines" reads as a bug in the tool and invites a * model to distrust the number beside it. */ export declare function count(n: number, singular: string, plural?: string): string; /** `path:start-end`, or `path:line` when the span is one line. */ export declare function span(path: string, start: number, end: number): string; /** * An ascending index list, as gaps rather than absolutes. * * `positions=[...]` on a log template held one absolute line number per * occurrence, and on a dense template that is most of what the template costs: * on the `raw-build-log` fixture the four lists were 7,897 of the block's * 46,064 characters, 17%, to say something the reader almost never reads * digit by digit. The positions are ascending and usually near-consecutive, so * the gap between them is a one-digit number where the absolute is four, and a * run of equal gaps folds. Same 1,800 line numbers, 3,347 characters. * * The form is `first,gap,gap*repeat,...` where every gap is the step from the * previous position. Gaps are >= 1, so `*` can only ever mean a repeat count * and the grammar stays unambiguous. */ export declare function encodeGaps(positions: readonly number[]): string; /** The inverse of {@link encodeGaps}. */ export declare function decodeGaps(encoded: string): number[]; //# sourceMappingURL=annotate.d.ts.map