UNPKG

agentfootprint

Version:

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

119 lines (118 loc) • 6.22 kB
/** * 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. */ import type { ProtocolAdapter, RunTranslation } from '../hosting/protocolAdapter.js'; import type { SseFraming } from '../hosting/stream.js'; import { type AgUiEncoderModule } from './framing.js'; import { type AgUiFrontendTool } from './frontendTools.js'; import { type AgUiRequestReader, type AgUiValidator } from './request.js'; import { type AgUiSerializerOptions } from './serializer.js'; import type { AgUiEvent, AgUiRunInput, AgUiSuspension } from './types.js'; /** What a translation is opened with — one run's ids, and the pause it continues. */ export interface AgUiTranslationRun { /** The conversation the request named (already format-checked). */ readonly threadId: string; /** The run id the request named (already format-checked). */ readonly runId: string; /** The paused run this one continues — its translation's `suspension()`, as the server kept it. */ readonly resume?: AgUiSuspension; } /** * The translation strategy: opens ONE run's translation — the run's served * events → AG-UI events. Default: `agUiSerializer`. Called once per request; * a translation it returns is fed only that request's run. One that pauses * hands out an `AgUiSuspension` from `suspension()`, or the core cannot match * the next request's answer to the pause and refuses it. */ export type AgUiTranslationStrategy = (run: AgUiTranslationRun) => RunTranslation<AgUiEvent>; /** Options for {@link agUi}. */ export interface AgUiOptions { /** * The tools a page may run, allow-listed by NAME with the app's own contract * (description, input schema) — never the page's. A request that declares a * tool not listed here is refused by name; one listed here and declared by * the page is handed to the agent factory (`StreamRunContext.tools`) for that * request. Needs a host that keeps the paused run (`threads` + `sessions`). */ readonly frontendTools?: Readonly<Record<string, AgUiFrontendTool>>; /** * The page's `state` and `context` reach the agent only through these: each * gets the value and the bound thread, and returns what the agent is given * (`StreamRunContext.accepted`) — or throws a `WireRequestRefusal`. Without * one, a request carrying a non-empty value is refused by name. */ readonly accept?: { readonly state?: AgUiValidator; readonly context?: AgUiValidator; }; /** * How a request body becomes the turn it asks for ({@link AgUiRequestReader}). * Default: the body is an AG-UI `RunAgentInput`. A gateway that sends its own * body passes a reader that maps it. The reader only MAPS: what it produces is * checked by the same core as a `RunAgentInput` — shape and ids, no system or * developer message, frontend tools by `frontendTools`, `state`/`context` by * `accept`, an answer only for the pause the server keeps. */ readonly reader?: AgUiRequestReader; /** * How one run's served events become AG-UI events ({@link AgUiTranslationStrategy}). * Default: `agUiSerializer` with `custom` and `reasoning`. Refused together * with either, which tune the default only. */ readonly translation?: AgUiTranslationStrategy; /** The CUSTOM allow-list — see {@link AgUiSerializerOptions.custom}. */ readonly custom?: AgUiSerializerOptions['custom']; /** Write the model's reasoning — see {@link AgUiSerializerOptions.reasoning}. */ readonly reasoning?: boolean; /** * How each AG-UI event is written ({@link SseFraming}). Default: `agUiSse()`, * the official `@ag-ui/encoder`'s frames. A gateway that expects its own * envelope passes a framing that writes it; the frame's number in its stream * arrives as `frame(event, { sequence })`, counted by the host per stream — * keep no counter in the framing. Refused together with `encoder`, which * feeds the default only. */ readonly framing?: SseFraming<AgUiEvent>; /** `@ag-ui/encoder`, imported by you — for a bundle this package cannot load it in. */ readonly encoder?: AgUiEncoderModule; } /** * 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. */ export declare function agUi(options?: AgUiOptions): ProtocolAdapter<AgUiRunInput, AgUiEvent>;