agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
196 lines • 7.61 kB
JavaScript
;
/**
* search/facets — the `checked` items one call's search covered.
*
* Pattern: one pure composer per list; this one is `checked`.
* Role: semantics/ layer, pure.
* Emits: N/A.
*
* A facet enters `checked` from exactly these places, and nowhere else:
*
* • the declared subject (`from: 'tool'`, `facet: 'subject'`) — what the
* search looked for;
* • the declared source (`from: 'tool'`) — or, when a `source` argument is
* in effect, that argument: a sent value REPLACES the declared source and
* keeps the declared words only when it equals the declared default;
* • a declared fixed qualifier (`from: 'fixed'`);
* • a value the call sent (`from: 'call'`), or a declared default the call
* left out (`from: 'default'`, the value or `any`);
* • an argument the host filled from the run's identity (`from: 'host'`,
* its value by reference);
* • the result's own `period.queried`, when no period argument is in effect
* (`from: 'result'`).
*
* `cap`, `page`, `inert` and credential-filled arguments name no ground and
* compose no item; an unsent argument composes a gap instead (`gaps.ts`).
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.composeChecked = exports.valueText = void 0;
const binding_js_1 = require("./binding.js");
const span_js_1 = require("./span.js");
const valueWords_js_1 = require("./valueWords.js");
/**
* A value as composed text — a string as written, a number or boolean as
* printed, anything else as its JSON. Never spread into the item.
*/
function valueText(value) {
if (typeof value === 'string')
return value;
if (typeof value === 'number' || typeof value === 'boolean' || value === null) {
return String(value);
}
try {
return JSON.stringify(value) ?? String(value);
}
catch {
return String(value);
}
}
exports.valueText = valueText;
/** Deep equality for the plain values a default may be. */
function sameValue(a, b) {
if (Array.isArray(a) && Array.isArray(b)) {
return a.length === b.length && a.every((v, i) => sameValue(v, b[i]));
}
return Object.is(a, b);
}
/** The facet each ground-naming role composes — a query's population is what matched it. */
const ROLE_FACET = {
population: 'population',
filter: 'filter',
period: 'period',
query: 'population',
match: 'population',
};
/** The source item, or `undefined` when the source in effect is not known. */
function sourceItem(input) {
const { search } = input;
const declared = { what: search.source.what, facet: 'source', said: search.source.said };
const entry = input.readings.find(([a]) => a.role === 'source');
if (entry === undefined)
return { ...declared, from: 'tool' };
const [arg, reading] = entry;
const isDefault = (v) => arg.whenOmitted !== undefined && sameValue(v, arg.whenOmitted);
switch (reading.kind) {
case 'sent': {
if (isDefault(reading.value)) {
return { ...declared, argument: arg.name, value: reading.value, from: 'call', quote: true };
}
const words = (0, valueWords_js_1.valueWords)(arg.said, valueText(reading.value));
return {
what: `${arg.name}: ${valueText(reading.value)}`,
facet: 'source',
argument: arg.name,
value: reading.value,
from: 'call',
quote: true,
...(words !== undefined && { said: [...words] }),
};
}
case 'default':
return { ...declared, argument: arg.name, value: reading.value, from: 'default' };
case 'any':
return { ...declared, from: 'tool' };
case 'host':
return hostItem(arg.name, reading.identity);
default:
// unsent (a gap) or credential-filled (names no ground)
return undefined;
}
}
function hostItem(argument, identity) {
return {
what: `${argument}: the caller's ${identity}`,
facet: 'filter',
argument,
value: { identity },
from: 'host',
};
}
/** The items one non-source argument composes — none, one, or one per list value. */
function argumentItems(arg, reading, input) {
if (reading.kind === 'host')
return [hostItem(arg.name, reading.identity)];
if (!Object.hasOwn(ROLE_FACET, arg.role))
return [];
const facet = ROLE_FACET[arg.role];
if (reading.kind === 'any') {
return [{ what: `${arg.name}: any`, facet, argument: arg.name, any: true, from: 'default' }];
}
if (reading.kind !== 'sent' && reading.kind !== 'default')
return [];
const from = reading.kind === 'sent' ? 'call' : 'default';
const values = arg.role === 'population' && Array.isArray(reading.value) ? reading.value : [reading.value];
return values.map((value) => {
const words = arg.role === 'filter' ? (0, valueWords_js_1.valueWords)(arg.said, valueText(value)) : undefined;
const resolved = arg.role === 'population' ? (0, binding_js_1.resolvedFor)(arg.name, value, input.resolved) : undefined;
const span = arg.role === 'period'
? (0, span_js_1.relativeSpan)(arg.name, value, input.period, input.dispatchedAt)
: undefined;
return {
what: `${arg.name}: ${valueText(value)}`,
facet,
argument: arg.name,
value,
from,
...((from === 'call' || resolved !== undefined) && { quote: true }),
...(resolved !== undefined && { resolved }),
...(words !== undefined && { said: [...words] }),
...(span !== undefined && { span }),
};
});
}
/** The period facet the RESULT declared, when no period argument is in effect. */
function resultPeriodItem(input) {
const period = input.resultPeriod;
if (period === undefined)
return undefined;
if (input.readings.some(([a]) => a.role === 'period'))
return undefined;
const span = (0, span_js_1.resultSpan)(period);
return {
what: `period: ${period.queried.from} to ${period.queried.to}`,
facet: 'period',
from: 'result',
quote: true,
...(span !== undefined && { span }),
};
}
/**
* The `checked` items, in a fixed order: the source, the subject, the fixed qualifiers,
* then each argument in declaration order, then a result-declared period.
*/
function composeChecked(input) {
const items = [];
const source = sourceItem(input);
if (source !== undefined)
items.push(source);
// What the search looked for — the declaration's words; the statement names it.
items.push({
what: input.search.subject.what,
facet: 'subject',
from: 'tool',
said: [...input.search.subject.said],
});
for (const q of input.search.fixed ?? []) {
items.push({
what: `${q.name}: ${valueText(q.value)}`,
facet: 'filter',
argument: q.name,
value: q.value,
from: 'fixed',
said: [...q.said],
});
}
for (const [arg, reading] of input.readings) {
if (arg.role === 'source')
continue;
items.push(...argumentItems(arg, reading, input));
}
const fromResult = resultPeriodItem(input);
if (fromResult !== undefined)
items.push(fromResult);
return items;
}
exports.composeChecked = composeChecked;
//# sourceMappingURL=facets.js.map