agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
88 lines • 4.39 kB
JavaScript
;
/**
* coverage/refusal — how a result helper refuses: the words every refusal
* starts with, and the one unknown-key check its declaration passes.
*
* Pattern: one voice, every door (`absent()`, `coverage()`,
* `describedResult()`), so they
* cannot disagree about what a refusal looks like or which keys a
* declaration may carry.
* Role: core/ layer, pure, a leaf — no imports.
* Emits: N/A.
*
* ## Why the prefix never names the helper
*
* These helpers run inside a tool's `execute`. A refusal is thrown there, the
* dispatch loop catches it, and its message becomes that call's error result
* — the text the MODEL reads in place of the data. Every refusal used to start
* with the helper's own name (`absent: …`, `coverage: …`, `semantic: …`), and
* a tool result that starts `absent:` reads like a finding. No provider
* adapter this library ships marks a tool result as an error on the wire
* (Anthropic's `is_error` is never set), so the words are all the model has:
* they say what the text IS first, in the same word for all three helpers,
* whatever the helper is called.
*
* ## Why an unknown key is refused, never dropped
*
* A declaration built in plain JavaScript, parsed from JSON or held in a
* widened variable escapes the type checker. `coverage: { checked,
* not_checked }` then minted the `checked` list and dropped the rest without
* a word: the declared limit vanished, and the answer read as if the tool had
* none. So every key a declaration carries is either one the helper reads or
* a refusal, and a refusal names the spelling meant when the key is that
* spelling with its case or separators changed (`not_checked` →
* `notChecked`; `measured_at` → `measuredAt` at `describedResult()`).
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.refuseUnknownKeys = exports.spellingMeant = exports.refusal = exports.REFUSED_PREFIX = void 0;
/**
* The first words of every refusal `absent()`, `coverage()`,
* `describedResult()` throw — and so of the error result a
* model reads when one of them refuses
* inside a tool's `execute`. Neutral on purpose: it says what the text is and
* never depends on which helper refused.
*/
exports.REFUSED_PREFIX = 'refused: ';
/** The error a result helper throws: the prefix, then what was refused. */
function refusal(message) {
return new Error(`${exports.REFUSED_PREFIX}${message}`);
}
exports.refusal = refusal;
/** A key with its case and separators folded away — `not_checked`,
* `notChecked` and `NotChecked` fold to the same string. */
const folded = (key) => key.replace(/[_-]/g, '').toLowerCase();
/**
* The known key `key` is a respelling of — the same letters with a
* different case or separators (a snake_case / camelCase slip) — or
* `undefined` when it is no such thing. Never a guess past the spelling: a
* key with a different word gets no suggestion.
*/
function spellingMeant(key, known) {
const want = folded(key);
return known.find((candidate) => candidate !== key && folded(candidate) === want);
}
exports.spellingMeant = spellingMeant;
/**
* Refuse the first own key of `value` that `known` does not list. `at` names
* the nested object the keys belong to (`'coverage'`), or is omitted for the
* declaration itself. The refusal names the key, the spelling meant when it is
* a slip of a known one, and the fields that object has.
*
* @example
* refuseUnknownKeys({ checked: ['a'], not_checked: ['b'] }, ['checked', 'notChecked', 'cannotCover'], 'coverage');
* // throws: refused: 'coverage.not_checked' is not a field this vocabulary has — did you
* // mean `notChecked`? The fields of `coverage` are: checked, notChecked, cannotCover.
*/
function refuseUnknownKeys(value, known, at) {
for (const key of Object.keys(value)) {
if (known.includes(key))
continue;
const meant = spellingMeant(key, known);
const path = at === undefined ? key : `${at}.${key}`;
throw refusal(`'${path}' is not a field this vocabulary has` +
(meant === undefined ? '.' : ` — did you mean \`${meant}\`?`) +
` The fields${at === undefined ? '' : ` of \`${at}\``} are: ${known.join(', ')}.`);
}
}
exports.refuseUnknownKeys = refuseUnknownKeys;
//# sourceMappingURL=refusal.js.map