agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
91 lines • 4.8 kB
JavaScript
import { throwIfAborted, untilAborted } from '../lib/abort.js';
import { copyJson, ownData } from '../lib/boundedJson.js';
import { DETAIL_LIMITS, readDetailArgs } from '../artifacts/detailRequest.js';
import { ArtifactDetailError, unavailableDetail } from '../artifacts/detailError.js';
import { projectArtifactDetail } from '../artifacts/detailView.js';
import { defineTool } from './tools.js';
/**
* Open bounded JSON sections through a host-authenticated reference.
*
* The host owns scope, run, source, kind, integrity and domain checks in `admit`.
* This helper never reads a store automatically or grants claim authority. Every
* call re-admits, then detaches/freezes at most 1 MiB of ordinary JSON (depth 100).
* Paths have at most 16 segments and 2048 UTF-16 characters; pages contain at most
* 20 children. Whole responses are bounded to 12 KiB UTF-8, including at most
* 4 KiB host context; inline values are at most 8 KiB. Large scalars are omitted
* whole. Bounds do not preempt synchronous host code or arbitrary Proxy traps.
*
* Callback failures use fixed refusal text. Cancellation stops waiting and late
* results cannot be served, but does not claim the underlying work stopped.
*/
export function artifactDetailTool(options) {
const configured = ownData(options, ['name', 'admit', 'context']);
if (typeof configured.name !== 'string' ||
typeof configured.admit !== 'function' ||
(configured.context !== undefined && typeof configured.context !== 'function'))
throw new Error('Invalid artifact detail configuration.');
const name = configured.name;
const admit = configured.admit;
const context = configured.context;
return defineTool({
name,
description: 'Open a bounded section of an authorized stored JSON proof. Use the complete issued ref and JSON pointer (empty path opens its root). Small sections return actual values; larger containers return child actions and exact shown/total/complete/nextOffset. Large scalars are omitted whole. Reading a section grants no additional claim authority.',
inputSchema: {
type: 'object',
additionalProperties: false,
required: ['ref', 'path'],
properties: {
ref: {
type: 'string',
description: 'Complete issued artifact reference: art_ followed by 22 base62 characters, for example art_0123456789ABCDEFGHIJKL. Never a file, URL or storage scope.',
},
path: {
type: 'string',
maxLength: DETAIL_LIMITS.path,
description: 'Safe JSON pointer; escape ~ as ~0 and / as ~1. Empty string opens the root; at most 16 segments.',
},
offset: {
type: 'integer',
minimum: 0,
maximum: DETAIL_LIMITS.offset,
description: 'Direct-child offset from nextOffset; default zero. Scalars accept only zero and are returned whole or explicitly omitted.',
},
limit: {
type: 'integer',
minimum: 1,
maximum: DETAIL_LIMITS.page,
description: 'Direct children per page, from 1 to 20; default 20. Follow actual child actions or nextOffset; shown/total/complete describe the page, not proof completeness.',
},
},
},
async execute(value, ctx) {
const args = readDetailArgs(value);
let root;
let domain;
try {
throwIfAborted(ctx.signal);
const supplied = await untilAborted(Promise.resolve().then(() => {
throwIfAborted(ctx.signal);
return admit(args.ref, ctx);
}), ctx.signal);
throwIfAborted(ctx.signal);
root = copyJson(supplied, DETAIL_LIMITS.inputBytes, true);
throwIfAborted(ctx.signal);
const presented = context?.(root, args.path);
domain =
presented === undefined
? undefined
: ownData(copyJson(presented, DETAIL_LIMITS.contextBytes, true));
throwIfAborted(ctx.signal);
}
catch {
if (ctx.signal?.aborted)
throw new ArtifactDetailError('ERR_ARTIFACT_DETAIL_CANCELLED');
return unavailableDetail();
}
// Host failures are sanitized above; the pure view owns its fixed request refusals.
return projectArtifactDetail(root, args, name, domain);
},
});
}
//# sourceMappingURL=artifactDetailTool.js.map