UNPKG

agentfootprint

Version:

The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.

126 lines • 6.18 kB
"use strict"; /** * adapter — AG-UI as a protocol adapter: what `streamHandler` takes to serve * runs to any AG-UI frontend — or to a gateway that speaks AG-UI in its own * request body and its own envelope. * * Three strategies, each replaceable on its own, around one CORE that is not: * - the READER — a request body → the turn it asks for (`reader`; default: * the body is a `RunAgentInput`). Mapping only; * - the TRANSLATION — one run's served events → AG-UI events (`translation`; * default `agUiSerializer`, tuned by `custom` and `reasoning`), one per * request; * - the FRAMING — AG-UI events → bytes (`framing`; default `agUiSse`, * `encoder` for bundles). The host numbers each stream's frames and hands * the number to the framing, so a gateway envelope's sequence is per run. * - the CORE (`request.ts`), whichever reader ran: the request's shape and * ids checked as plain JSON, a system/developer message refused, frontend * tools only by the app's allow-list, `state`/`context` only through the * app's validators (`accept`), an answer only for the pause the server * keeps. No strategy can reach past it. * * Holds no state between requests: its options are read once and frozen, and * every request gets its own translation and its own frontend tools. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.agUi = void 0; const framing_js_1 = require("./framing.js"); const frontendTools_js_1 = require("./frontendTools.js"); const request_js_1 = require("./request.js"); const serializer_js_1 = require("./serializer.js"); /** * The AG-UI protocol adapter. Hand it to a host: * * @example * ```ts * import { memorySessions, streamHandler } from 'agentfootprint/hosting'; * import { agUi } from 'agentfootprint/ag-ui'; * * export const POST = streamHandler({ * agent: ({ tools }) => Agent.create({ provider, model }).tools([...mine, ...tools]).build(), * adapter: agUi(), * threads: { bind: ({ conversation, headers }) => bindToSignedInUser(conversation, headers) }, * sessions: memorySessions(), * }); * ``` * * @throws TypeError at construction for a frontend tool whose name or contract * is not usable, and a validator that is not a function. */ function agUi(options = {}) { const frontendTools = (0, frontendTools_js_1.frontendToolBuilders)(options.frontendTools); const accept = Object.freeze({ ...options.accept }); for (const [name, validator] of Object.entries(accept)) { if (validator !== undefined && typeof validator !== 'function') throw new TypeError(`agUi(): accept.${name} must be a function.`); } const rules = Object.freeze({ frontendTools, accept }); const reader = strategy(options.reader, 'reader') ?? request_js_1.runAgentInputReader; const translate = translationOf(options); const framing = framingOf(options); return Object.freeze({ name: 'ag-ui', readRequest: ({ body, headers }) => (0, request_js_1.readAgUiRequest)(reader, { body, headers }), conversation: (input) => input.threadId, plan: (input, held, facts) => (0, request_js_1.planAgUiTurn)(input, held, facts, rules), awaitsCaller: frontendTools_js_1.isFrontendToolPause, open: (input, resumed) => { const resume = resumed === undefined ? undefined : (0, request_js_1.readAgUiSuspension)(resumed); return translate({ threadId: input.threadId, runId: input.runId, ...(resume !== undefined && { resume }), }); }, framing, }); } exports.agUi = agUi; /** A strategy option: absent, or a function — anything else refused by name. */ function strategy(value, name) { if (value !== undefined && typeof value !== 'function') throw new TypeError(`agUi(): ${name} must be a function.`); return value; } /** The translation strategy — the app's, or `agUiSerializer` tuned by `custom` and `reasoning`. */ function translationOf(options) { const { custom, reasoning } = options; const translation = strategy(options.translation, 'translation'); if (translation !== undefined) { if (custom !== undefined || reasoning !== undefined) throw new TypeError('agUi(): `translation` was given with `custom` or `reasoning`, which tune the default ' + 'translation only — your translation decides both itself. Pass one or the other.'); // One translation, one run: a strategy that hands one back twice would // write one run's open messages and ids into another run's stream. const opened = new WeakSet(); return (run) => { const opening = translation(run); if (typeof opening !== 'object' || opening === null || opened.has(opening)) throw new TypeError('agUi(): `translation` must return a NEW translation for every run — one it returned ' + "before would carry another run's state into this one."); opened.add(opening); return opening; }; } return (run) => (0, serializer_js_1.agUiSerializer)({ ...run, ...(custom !== undefined && { custom }), ...(reasoning !== undefined && { reasoning }), }); } /** The framing strategy — the app's, or `agUiSse` (with the `encoder` the app imported). */ function framingOf(options) { const { framing, encoder } = options; if (framing === undefined) return () => (0, framing_js_1.agUiSse)(encoder === undefined ? {} : { encoder }); if (encoder !== undefined) throw new TypeError('agUi(): `framing` was given with `encoder`, which feeds the default framing only — ' + 'your framing writes its own frames. Pass one or the other.'); if (typeof framing !== 'object' || framing === null || typeof framing.frame !== 'function' || typeof framing.contentType !== 'string') throw new TypeError('agUi(): `framing` must be an SseFraming — { contentType, frame }.'); return () => framing; } //# sourceMappingURL=adapter.js.map