UNPKG

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
/** * 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