agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
161 lines • 7.21 kB
JavaScript
;
/**
* locales — Message Catalog Pattern.
*
* The Block D piece. agentfootprint emits user-facing prose at two
* audience levels:
*
* - **Commentary** — third-person prose for the bottom panel of any
* viewer (Lens, CLI tail, log files). "The agent dispatched the
* refund tool, which returned successfully."
* - **Thinking** — first-person status for chat-bubble UIs.
* "Looking up your order…"
*
* v2.4 shipped these as `defaultCommentaryTemplates` and
* `defaultStatusTemplates` (flat `Record<string, string>` maps with
* `{{var}}` substitution). The names worked but the framing was
* generic — "templates" collides with TypeScript / templating-engine
* terminology, and there was no first-class place to ship locale
* packs.
*
* **Block D formalizes this as the Message Catalog Pattern:**
*
* - `defaultCommentaryMessages` / `defaultThinkingMessages` —
* canonical English bundles. Aliases of the v2.4 names; symbol
* identity preserved (`defaultCommentaryMessages ===
* defaultCommentaryTemplates`).
* - `composeMessages(defaults, overrides)` — spread overrides on
* top of defaults; missing keys fall back to the default bundle.
* - `validateMessages(catalog, requiredKeys)` — assert every
* required key is present (and non-empty). Catches drift between
* a consumer's locale pack and the framework's required key set.
*
* The natural consumer pattern is to ship locale packs alongside the
* agent code:
*
* import { defaultCommentaryMessages, composeMessages } from 'agentfootprint/observe';
* import { esCommentaryMessages } from './locales/es.js';
*
* const merged = composeMessages(defaultCommentaryMessages, esCommentaryMessages);
*
* const agent = Agent.create({...})
* .commentaryTemplates(merged)
* .build();
*
* Pattern: i18n locale resolution (Resource Bundle, Fowler 2002) +
* plain object merge for overrides. No catalog inheritance
* chain — overrides win OR fall back to defaults.
*
* @example Locale pack drop-in
* const esThinking = composeMessages(defaultThinkingMessages, {
* idle: '{{appName}} está pensando…',
* tool: 'Trabajando en `{{toolName}}`…',
* });
* const agent = Agent.create({...}).thinkingTemplates(esThinking).build();
*
* @example Validate a locale pack against the framework's required keys
* import { defaultCommentaryMessages, composeMessages, validateMessages } from 'agentfootprint/observe';
*
* const myCatalog = composeMessages(defaultCommentaryMessages, customOverrides);
* validateMessages(myCatalog, Object.keys(defaultCommentaryMessages));
* // throws on first missing OR empty key — fail-fast at boot
*
* Not an import path of its own since 9.0.0. This is the implementation barrel
* behind `agentfootprint/observe`, which re-exports every name here — same
* symbols, one door. Import from the door.
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.validateMessages = exports.composeMessages = exports.defaultTimeAskMessages = exports.defaultStatusTemplates = exports.defaultThinkingMessages = exports.defaultCommentaryMessages = void 0;
const commentaryTemplates_js_1 = require("../recorders/observability/commentary/commentaryTemplates.js");
const statusTemplates_js_1 = require("../recorders/observability/status/statusTemplates.js");
Object.defineProperty(exports, "defaultStatusTemplates", { enumerable: true, get: function () { return statusTemplates_js_1.defaultStatusTemplates; } });
/**
* Canonical English commentary bundle. Alias of v2.4's
* `defaultCommentaryTemplates` — same symbol, friendlier framing.
*
* Keys mirror agentfootprint event types; values may contain
* `{{var}}` placeholders for runtime substitution.
*/
exports.defaultCommentaryMessages = commentaryTemplates_js_1.defaultCommentaryTemplates;
/**
* Canonical English thinking bundle. Alias of v2.4's
* `defaultStatusTemplates`.
*
* Keys mirror agentfootprint event types; values may contain
* `{{var}}` placeholders for runtime substitution.
*/
exports.defaultThinkingMessages = statusTemplates_js_1.defaultStatusTemplates;
/**
* The time ask's sentences (the time layer, step T4) — a refused time
* answer's reason, the ask's questions, the label on a reading to confirm.
* Override keys through `.time({ messages })`.
*/
var timeAsk_js_1 = require("./timeAsk.js");
Object.defineProperty(exports, "defaultTimeAskMessages", { enumerable: true, get: function () { return timeAsk_js_1.defaultTimeAskMessages; } });
/**
* Spread `overrides` on top of `defaults` so every key in `defaults`
* has a value (the override or the original). The result is a fresh
* object — neither input is mutated.
*
* Missing override keys fall back to the default; extra override
* keys are preserved (forward-compat for consumer-defined keys).
*
* @example
* const merged = composeMessages(defaultCommentaryMessages, {
* 'stream.llm_start.iter1': 'My custom thinking line',
* });
*/
function composeMessages(defaults, overrides = {}) {
return Object.freeze({ ...defaults, ...overrides });
}
exports.composeMessages = composeMessages;
/**
* Assert that every key in `requiredKeys` is present in `catalog`.
* Throws an Error listing every missing key — batched so consumers
* fix all at once instead of error-by-error.
*
* Useful at boot to catch drift between a consumer's locale pack and
* the framework's required key set.
*
* Empty-string values are VALID by default — the framework's default
* catalogs use `''` to signal "render nothing for this event."
* Pass `{ forbidEmpty: true }` to also reject empty values.
*
* @param catalog The (composed) message catalog to validate.
* @param requiredKeys The keys consumers must define. Typically
* `Object.keys(defaultCommentaryMessages)` or
* `Object.keys(defaultThinkingMessages)`.
* @param opts Optional `{ label, forbidEmpty }` (or a bare
* string label for back-compat with simple use).
*
* @throws Error when any required key is missing (or empty under
* `forbidEmpty`).
*/
function validateMessages(catalog, requiredKeys, opts = {}) {
const resolved = typeof opts === 'string' ? { label: opts } : opts;
const label = resolved.label ?? 'message catalog';
const forbidEmpty = resolved.forbidEmpty ?? false;
const missing = [];
const empty = [];
for (const key of requiredKeys) {
const value = catalog[key];
if (value === undefined) {
missing.push(key);
}
else if (forbidEmpty && value.length === 0) {
empty.push(key);
}
}
if (missing.length === 0 && empty.length === 0)
return;
const parts = [];
if (missing.length > 0) {
parts.push(`missing keys: ${missing.join(', ')}`);
}
if (empty.length > 0) {
parts.push(`empty values: ${empty.join(', ')}`);
}
throw new Error(`validateMessages(${label}): ${parts.join('; ')}.`);
}
exports.validateMessages = validateMessages;
//# sourceMappingURL=index.js.map