agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
114 lines • 4.72 kB
JavaScript
;
/**
* Shared helpers for slot subflow builders.
*
* Pattern: utility module.
* Role: Tiny pure functions the slot builders share — hash, truncate,
* breakdown. Kept co-located to avoid cross-package import churn.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.formatOverflowWarning = exports.slotOverflow = exports.composeSlot = exports.breakdown = exports.truncate = exports.fnv1a = void 0;
/**
* Non-cryptographic stable hash — sufficient for InjectionRecord dedup.
*
* The implementation moved to `lib/fnv1a.ts` in 8.8.0 so the memory
* layer's retrieval record can hash the same way without importing
* `core/` (which imports `memory/` — the cycle is why a second copy was
* the alternative). Re-exported here under the same name: every existing
* importer of `helpers.fnv1a` is untouched, and there is still exactly
* one implementation.
*/
var fnv1a_js_1 = require("../../lib/fnv1a.js");
Object.defineProperty(exports, "fnv1a", { enumerable: true, get: function () { return fnv1a_js_1.fnv1a; } });
/** Truncate with ellipsis for contentSummary fields. */
function truncate(s, n) {
if (s.length <= n)
return s;
return s.slice(0, n - 1) + '…';
}
exports.truncate = truncate;
/** Aggregate injection chars/count per source — payload for SlotComposition. */
function breakdown(injections) {
const out = {};
for (const r of injections) {
const chars = r.rawContent?.length ?? r.contentSummary.length;
const existing = out[r.source];
if (existing) {
out[r.source] = {
chars: existing.chars + chars,
count: existing.count + 1,
};
}
else {
out[r.source] = { chars, count: 1 };
}
}
return out;
}
exports.breakdown = breakdown;
/**
* Build a SlotComposition summary record from injections + budget cap.
* Drop tracking is opt-in — pass `dropped` when the slot actually
* evicted anything during composition.
*/
function composeSlot(slot, iteration, injections, budgetCap, orderingStrategy, dropped) {
const used = injections.reduce((sum, r) => sum + (r.rawContent?.length ?? r.contentSummary.length), 0);
return {
slot,
iteration,
budget: { cap: budgetCap, used, headroomChars: Math.max(0, budgetCap - used) },
sourceBreakdown: breakdown(injections),
...(orderingStrategy !== undefined && { orderingStrategy }),
droppedCount: dropped?.count ?? 0,
droppedSummaries: dropped?.summaries ?? [],
};
}
exports.composeSlot = composeSlot;
/**
* Overflow detector for slot compositions.
*
* Returns a `BudgetPressureRecord` when `used > cap`, otherwise `null`.
* `cap <= 0` is the "no budget" sentinel (see buildMessageApiChart's
* `{ cap: 0, used: 0 }` composition) — it never overflows.
*
* `planAction: 'none'` is the honest answer: the built-in slots evict and
* truncate NOTHING, so the full content went to the LLM and this record is
* the loud signal that the slot's budget is not being respected.
*
* Units are CHARS, and the record says so: `unit` / `cap` / `projected`, added
* in 8.14.0 and the only spelling since 9.0.0 removed the `*Tokens` names that
* asserted a unit this channel does not use. A window strategy fills the same
* fields with `unit: 'tokens'`, and one subscriber gets both.
*/
function slotOverflow(composition) {
const { cap, used } = composition.budget;
if (cap <= 0 || used <= cap)
return null;
return {
slot: composition.slot,
overflowBy: used - cap,
planAction: 'none',
unit: 'chars',
cap,
projected: used,
};
}
exports.slotOverflow = slotOverflow;
/**
* Human-facing warning text for a slot overflow. Says what actually
* happened (nothing was truncated) and what to do about it — the typed
* `context.budget_pressure` event carries the machine-readable truth.
*
* The unit is read off the record rather than written into the sentence, so
* the words and the event can never disagree about what was counted.
*/
function formatOverflowWarning(opts) {
const { pressure, itemCount, itemNoun, contentNoun, remedy } = opts;
const unit = pressure.unit ?? 'chars';
return (`[agentfootprint] ${pressure.slot} slot over budget: ` +
`${pressure.projected}/${pressure.cap} ${unit} (+${pressure.overflowBy}), ` +
`${itemCount} ${itemNoun}(s). Nothing was truncated — the full ${contentNoun} were sent ` +
`to the LLM — but the slot budget signal is unreliable. ${remedy}`);
}
exports.formatOverflowWarning = formatOverflowWarning;
//# sourceMappingURL=helpers.js.map