UNPKG

agentfootprint

Version:

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

102 lines • 4.76 kB
"use strict"; /** * lib/storedPreview — how much of somebody's stored data an error may quote. * * A refusal about unreadable bytes has to show enough of them to be diagnosable * ("ah, that is our envelope stringified by something that was not JSON") and no * more, because the rest of those bytes are a conversation, a memory, or * whatever else a user said. Error messages travel: they land in logs, in * dashboards, in bug reports pasted into chat windows. So the quote is capped, * and the cap lives HERE rather than being re-decided by each adapter that needs * one — two copies of a redaction rule is one copy that will drift. * * Structured values are SUMMARISED rather than serialized: an array reports its * length and an object its keys, because `String(anObject)` and * `JSON.stringify(anArray)` are both ways of accidentally printing the payload * this file exists to keep out of the message. * * Two functions, because a cap is only safe when the first bytes are structure. * {@link previewStored} quotes a capped prefix — right for a value whose opening * fields are metadata. {@link describeStoredShape} quotes nothing at all — right * for a value whose content starts immediately. Choosing between them is the * caller's job because only the caller knows what its bytes begin with. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.describeStoredShape = exports.previewStored = exports.STORED_PREVIEW_LIMIT = void 0; /** * How much of a stored value a refusal may quote. Enough to recognise a mangled * encoding at a glance; not enough to leak what was being said. */ exports.STORED_PREVIEW_LIMIT = 64; /** * Describe what a store handed back WITHOUT quoting the whole thing. * * @example * previewStored('{format=conversation-v1, data={version=1, …}}') * // → '"{format=conversation-v1, data={version=1, …" (96 chars)' * previewStored({ id: 'm-1', value: 'her address is …' }) * // → 'an object with keys: id, value' */ function previewStored(value) { if (typeof value === 'string') { return value.length <= exports.STORED_PREVIEW_LIMIT ? `"${value}"` : `"${value.slice(0, exports.STORED_PREVIEW_LIMIT)}…" (${value.length} chars)`; } return describeStoredShape(value); } exports.previewStored = previewStored; /** * Describe a stored value **without quoting any of it** — its type, its size and * its shape, and nothing that came from a person. * * Use this instead of {@link previewStored} wherever the stored bytes carry * user content from their first characters, so that even a capped prefix would * leak. A `CheckpointEnvelope` opens with `format`, `data`, `savedAt` and can * afford to be quoted; a `MemoryEntry` opens with `id` and then `value`, so its * second field IS the thing somebody asked an agent to remember. The cap is a * policy about how MUCH may be shown; this is the answer when the honest amount * is none. * * It stays diagnosable, which is the whole job of the quote it replaces: a * length, whether it is JSON at all, and its opening character are enough to * recognise "an object stringified by something that was not JSON". * * @example * describeStoredShape('{id=a, value={text=her address is …}}') * // → "a 37-character string that is not JSON, starting '{'" */ function describeStoredShape(value) { if (typeof value === 'string') { let parsedShape = 'is not JSON'; try { const parsed = JSON.parse(value); parsedShape = parsed !== null && typeof parsed === 'object' ? 'parses as JSON' : `parses as JSON but is a bare ${parsed === null ? 'null' : typeof parsed}`; } catch { /* not JSON — the interesting case, and already the default */ } // The opening character only: '{' or '[' is what says "something // stringified an object", and one structural character is not content. const opener = value.length > 0 ? `, starting ${JSON.stringify(value[0])}` : ''; return `a ${value.length}-character string that ${parsedShape}${opener}`; } if (Array.isArray(value)) return `an array of ${value.length} item(s)`; if (value === null) return 'null'; if (value === undefined) return 'undefined'; if (typeof value === 'object') { const keys = Object.keys(value); return keys.length > 0 ? `an object with keys: ${keys.slice(0, 8).join(', ')}` : 'an object with no keys'; } return `${typeof value} ${String(value)}`; } exports.describeStoredShape = describeStoredShape; //# sourceMappingURL=storedPreview.js.map