agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
92 lines (91 loc) • 4.57 kB
TypeScript
/**
* servedJson — a served tool result's JSON, read by the JSON grammar when the
* whole text is NOT one JSON value.
*
* Pattern: two pure readers over the JSON grammar (no imports, no state, no
* clock): `leadingJsonValues` (the complete values a text OPENS
* with, and the text after them) and `jsonPrefixOf` (the leaves of
* JSON text cut short, token by token, and the whole words of a
* string the cut falls inside).
* Role: core/ layer, `evidence/`. Asked by `evidenceIndex.ts` ·
* `readResult` — the ONE reading of a result that the evidence index
* and the inputs layer's declared-sources check
* (`resultCarries.ts` · `resultReader`) share.
* Emits: N/A.
*
* ## Why a served result is not always one JSON value
*
* The dispatch loop serves a tool's value as JSON and joins framework notes
* AFTER it — a stepped skill's step banner, an effect note, the repeated-call
* note (`stages/toolCalls.ts`); an MCP text result joins its content blocks
* with a newline (`mcp/toolResult.ts` · `readTextResult`); and a capped
* result's `head` is the first characters of the tool's JSON, cut mid-value
* (`toolResultCap.ts`). `JSON.parse` refuses all of these, and the text
* fallback tokenises `{"id":4417,"up":true}` into `id`, `:4417`, `up`,
* `:true` — so every number and boolean the tool returned read as ABSENT: the
* evidence gate's false flag on an answer that quoted one, and the
* declared-sources check's false `not-in-result` on a value the model took
* from one (then a needless ask). So the tool's JSON is read by the JSON
* grammar as far as it goes, and only what follows it is text.
*
* The extent of a value is found the way `findings/unsettled.ts` ·
* `leadingObjectOf` finds the served envelope — strings and their escapes
* skipped, brackets counted — and each span is parsed once, guarded.
*/
/**
* The complete JSON values `text` OPENS with — an object or an array, then
* any further ones separated by whitespace (the blocks of an MCP text result)
* — and the text after the last of them (`rest`, which starts at its first
* non-space character; `''` when nothing follows). `values` is empty when the
* text does not open with a complete, parseable value.
*
* @example
* ```ts
* leadingJsonValues('{"id":4417}\n{"id":2210}\n\n[identical call: …]');
* // { values: [{ id: 4417 }, { id: 2210 }], rest: '[identical call: …]' }
* ```
*/
export declare function leadingJsonValues(text: string): {
readonly values: readonly unknown[];
readonly rest: string;
};
/** What JSON text CUT SHORT reads as (`jsonPrefixOf`). */
export interface JsonPrefix {
/**
* Every complete string (a key or a value) and every complete number and
* boolean, in order, each as the string the parsed walk would index
* (`String(4417)`, `'true'`); `null` is not a leaf.
*/
readonly leaves: readonly string[];
/**
* When the text ends inside a string: its WHOLE words — the string up to the
* last space before the cut, unescaped (`"msg":"disk full on host-7 and mo`
* → `disk full on host-7 and`). Text, never a leaf: it is not the whole
* string. Absent when no whole word is left.
*/
readonly words?: string;
}
/**
* JSON text CUT SHORT — an object or an array whose text ends before it
* closes (a capped result's `head`, a result a tool truncated itself) — read
* by the JSON grammar as far as it goes: its complete leaves, in order, and,
* when the text ends inside a string, that string's whole words (`words`).
* The token the text ends inside is never a leaf — it may be cut (`4417` cut
* at `44` is not the value `44`) — and the word a cut string ends in is never
* one of its words (`mo` may be `more`).
*
* `undefined` when the text is not JSON cut short: it does not open with `{`
* or `[`, a token is not a JSON token (`[INFO] …`, `{level=info`) — the one
* the text ends inside not even the beginning of one — a closing bracket does
* not match, or the value CLOSES: a complete value that did not parse is not
* JSON at all. Such text is read as text.
*
* @example
* ```ts
* jsonPrefixOf('{"hosts":[{"id":4417,"up":true},{"id":22');
* // { leaves: ['hosts', 'id', '4417', 'up', 'true', 'id'] }
* jsonPrefixOf('{"id":4417,"msg":"disk full on host-7 and mo');
* // { leaves: ['id', '4417', 'msg'], words: 'disk full on host-7 and' }
* ```
*/
export declare function jsonPrefixOf(text: string): JsonPrefix | undefined;