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 • 6.26 kB
TypeScript
/**
* 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>;
//# sourceMappingURL=adapter.d.ts.map