UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

79 lines (78 loc) • 4.66 kB
/** * findings/answerText — the ONE reader of the reserved key in an answer's * TEXT (9.114.2). * * Pattern: an incremental scanner — a character state machine with no scope * and no I/O, fed chunk by chunk (`reservedMemberFilter`) or whole * (`withoutReservedMembers`), with the same bytes out either way. * Role: core/ layer leaf. `stages/callLLM.ts` feeds it the provider's * stream under the arm, so `agentfootprint.stream.token` never * carries the model's notes; `peel.ts · peelAnswerFindings` feeds * it the whole answer, so the answer the run returns is exactly the * text the stream showed. * * THE RULE * Every JSON object written in the text — the whole answer, one in a code * block, one in the prose, one in a list — loses its own `_findings` member: * the key, the value and the separator that joined it to its neighbours, * and nothing else. A `_findings` inside another object's VALUE is data and * stays. Each removed value is handed back (`removed`), as written, for the * peel to read as the answer's declaration. * * - An object left with nothing in it goes whole — its line too — when it * stood on a line of its own outside any list: the whole answer, a * paragraph of its own, a code block of its own. A code block left empty * goes with its fences. The removed line's indentation and the whole * blank lines after it go; the next line keeps its own indentation. No * blank lines are left at either end of the text. The whole answer, left with nothing, is `{}` (a JSON answer stays * JSON). Anywhere else an emptied object stays as `{}`, so the JSON or * code around it keeps its shape. A string inside a list is read as a * string, so a `]` or `{` in it neither ends the list nor opens an object. * - An answer that held nothing but notes — a code block of them, or * several objects of them — is left empty; only an answer that was one * bare JSON object becomes `{}`. * - Text that stops being JSON part way (`{see below}`, a stray quote or a * raw line break inside a `_findings` value) is given back exactly as it * was written; nothing is removed from it except a `_findings` member * that was already complete. An object that held nothing but such * complete notes when the text stopped being JSON (a model one closing * brace short) ends there, and goes as an emptied object does. Keys and * values are read by JSON's own grammar (`JsonValue`), so the first * character JSON cannot take decides it — not a count of brackets, which * prose may never balance. * - Text that ends inside a `_findings` value that is JSON so far (a * cut-off stream) keeps it hidden and reports it `complete: false`. * - Nothing removed ⇒ the text comes back byte for byte, whatever the * chunking. What is held back while undecided is bounded by a line: * whitespace, a key (a raw line break ends one — JSON allows none), `{` * until its first key is read, a separator, a code fence line, the rest * of an emptied object's line. The one exception is a `_findings` value: * it is never shown while it is JSON so far, and one that stops being * JSON is held until the character that breaks it and then given back as * written — so that hold can span as many lines as the notes do. */ /** One `_findings` member the scanner took out of the text. */ export interface RemovedMember { /** Which object in the text held it, counted from 0 in text order. */ readonly object: number; /** The member's value exactly as written — cut short when the text ended inside it. */ readonly valueText: string; /** False when the text ended before the value did. */ readonly complete: boolean; } /** The scanner, fed piece by piece. */ export interface ReservedMemberFilter { /** Feed the next piece of text; returns what can be shown now. */ push(chunk: string): string; /** No more text: returns what was held back. After it, every call returns `''`. */ end(): string; /** Every member removed so far, in text order. */ readonly removed: readonly RemovedMember[]; } /** A fresh scanner for one text (one provider call's content). */ export declare function reservedMemberFilter(key?: string): ReservedMemberFilter; /** The whole-text form: the text without its reserved members, and what was removed. */ export declare function withoutReservedMembers(text: string, key?: string): { readonly text: string; readonly removed: readonly RemovedMember[]; };