agentfootprint
Version:
The explainable agent framework — backtrack a wrong answer to the exact context that caused it (evidence, not guesses). Built on footprintjs.
153 lines • 6.76 kB
TypeScript
/**
* stream — agent events → Server-Sent Events helpers.
*
* Pattern: Adapter (event stream → SSE wire format).
* Role: Outer ring. Subscribes to a `Runner`'s `EventDispatcher`
* and yields SSE-formatted strings. Drop into any HTTP
* framework that accepts an async iterable response body
* (Fetch Response, Express res.write, Hono streaming, etc.).
* Emits: N/A — observes only.
*
* Not an import path of its own since 9.0.0. This is the implementation barrel
* behind `agentfootprint/observe`, which re-exports every name here — same
* symbols, one door. Import from the door.
*/
import type { AgentfootprintEvent } from '../events/registry.js';
import type { RunnerBase } from '../core/RunnerBase.js';
/**
* Hand the runner this iterable's caller before calling `runner.run()`.
* Yields SSE-formatted strings until the run finishes (success, error,
* or pause). Each event becomes:
*
* event: <event name>
* data: <JSON payload>
* <blank line>
*
* @example
* // Express
* app.post('/agent', async (req, res) => {
* res.setHeader('content-type', 'text/event-stream');
* for await (const chunk of toSSE(agent)) {
* res.write(chunk);
* }
* res.end();
* // (in parallel: await agent.run(req.body))
* });
*/
export interface ToSSEOptions {
/**
* Filter predicate — return false to skip an event. Default: all events.
* Common: `event => event.type.startsWith('agentfootprint.stream.')`
* for a token-only feed. It sees each event as the run's RECORD serves it —
* under an agent's `redact`, the placeholder wherever the policy selected a
* value — in both formats.
*/
readonly filter?: (event: AgentfootprintEvent) => boolean;
/**
* Output shape:
* - 'full' (default) — each event is JSON-serialized verbatim: the run's
* RECORD, so under an agent's `redact` policy it is the served event
* (the placeholder wherever the policy selected a value).
* - 'text' — only `agentfootprint.stream.token.content` is yielded,
* in plain text form (no event/data prefix). Useful for piping
* directly into a chat UI. This is the REPLY to the person who asked —
* the caller's own answer, like `run()`'s return — so a redaction
* policy never masks it.
*/
readonly format?: 'full' | 'text';
/**
* Custom event name extractor. By default `event.type` is used.
* Useful for SSE consumers that want their own naming.
*/
readonly eventName?: (event: AgentfootprintEvent) => string;
/**
* Heartbeat interval in ms. SSE connections through proxies/load
* balancers often die after ~30s of silence; emit `: ping` comments
* at this interval. Default 0 (disabled).
*/
readonly heartbeatMs?: number;
/**
* How each event is written as a frame in the 'full' format — the framing
* STRATEGY ({@link SseFraming}). Default: {@link agentfootprintSse}, the
* frames `toSSE` has always written (`event: <type>` + `data: <json>`).
*
* Refused together with `eventName` (which names the event line of the
* default framing only) and with `format: 'text'` (which writes no frames).
*/
readonly framing?: SseFraming<AgentfootprintEvent>;
}
/**
* How ONE event becomes ONE Server-Sent Events frame — the framing strategy.
*
* A framing decides the bytes of a frame and nothing else: which events go out,
* in what order, and when the stream ends belong to whoever drives it. Two
* strategies ship: {@link agentfootprintSse} frames agentfootprint's own events
* (what `toSSE` writes), and `agUiSse()` from `agentfootprint/ag-ui` frames AG-UI
* events the way the official `@ag-ui/encoder` does (a `data:` line only).
*
* @example Frame each event under its short name (`stream.token`, not `agentfootprint.stream.token`)
* ```ts
* const shortNames: SseFraming<AgentfootprintEvent> = {
* contentType: 'text/event-stream',
* frame: (event) => encodeSSE(event.type.replace(/^agentfootprint\./, ''), event),
* };
* for await (const frame of toSSE(agent, { framing: shortNames })) res.write(frame);
* ```
*/
export interface SseFraming<E> {
/** The media type a response framed this way declares. */
readonly contentType: string;
/**
* The whole frame for one event, its terminating blank line included.
*
* `at` is where the frame stands in ITS stream ({@link SseFramePosition}),
* counted by whoever drives the stream: a framing that numbers its frames
* (a gateway's `eventSequence`) reads the number here and keeps no counter
* of its own, so one framing serving many streams at once never numbers one
* stream's frames with another's.
*/
frame(event: E, at: SseFramePosition): string;
}
/**
* Where one frame stands in its stream — handed to {@link SseFraming.frame} by
* whoever drives the stream (`toSSE`, `streamHandler`), never kept by a framing.
*/
export interface SseFramePosition {
/**
* The frame's number in this stream: 1 for the first, one more for each frame
* written after it. Counted per stream — one response, one run — and never
* shared: a resumed run is a new stream and counts from 1 again. Keep-alive
* comments are not frames and are not counted.
*/
readonly sequence: number;
}
/**
* agentfootprint's own framing — `event: <type>` then `data: <json>` — the
* frames `toSSE` writes by default, byte for byte as before the framing seam
* existed. Every payload goes through the wire rule (`lib/wireJson.ts`).
*/
export declare const agentfootprintSse: SseFraming<AgentfootprintEvent>;
/**
* Subscribe to a runner's `EventDispatcher` and yield SSE-formatted
* strings until the run completes.
*/
export declare function toSSE<TIn, TOut>(runner: RunnerBase<TIn, TOut>, options?: ToSSEOptions): AsyncIterable<string>;
/**
* Class form for consumers who prefer `new SSEFormatter(runner).stream()`.
* Identical behavior to `toSSE(runner)` — pick by preference.
*/
export declare class SSEFormatter<TIn = unknown, TOut = unknown> {
private readonly runner;
private readonly options;
constructor(runner: RunnerBase<TIn, TOut>, options?: ToSSEOptions);
/** Async iterable of SSE chunks. Consume with `for await`. */
stream(): AsyncIterable<string>;
}
/**
* Format any JSON-able payload as a single SSE event chunk.
*
* Useful for app-level events outside the runner's typed registry
* (auth/error frames, app-state echoes). Most consumers won't need this.
*/
export declare function encodeSSE(eventName: string, payload: unknown): string;
//# sourceMappingURL=stream.d.ts.map