UNPKG

kestrel.markets

Version:

A typed, token-efficient language + runtime for agentic trading: agents author bounded plans, the runtime fires them at the tick. CLI + typed library + MCP server.

73 lines 4.89 kB
/** * # client/sse-transport — the LIBRARY-SAFE SSE transport leaf (kestrel-bsn8). * * ONE SSE framing + opaque-cursor resume loop for every streamed Operation, shared by the * library-safe {@link ./index.ts KestrelClient} AND the CLI's {@link ../cli/backend/sse.ts} * (→ RemoteBackend, the oversight client): extract and reuse, never re-implement. The leaf knows * the WIRE (framing, the opaque cursor, resume, the reconnect budget) and NOTHING about any * caller's event vocabulary — the closed enum arrives as the caller's `isEventType` guard, and the * decoded body stays the caller's business. * * - Framing: `\n\n`-delimited frames; `event:` name, concatenated `data:`, and the raw `id:` * line as an OPAQUE STRING (never JSON-parsed). Comment lines (leading `:`) are SSE * heartbeats and are ignored. * - Resume: `?cursor=` and `Last-Event-ID` carry the SAME opaque value; a dropped connection * re-opens from the LAST cursor — never from the beginning. * - Fail closed: an `event:` outside the caller's vocabulary is a protocol violation, and a * handler throw is a hard error — both are re-thrown past the reconnect loop (a drift is not a * drop). A transient stream error reconnects, bounded by {@link MAX_RECONNECTS}; exhausting the * budget without a terminal event is a hard 504, never a silent empty result. * * LIBRARY-SAFE: this module imports NOTHING from `src/cli`, holds no error type of its own, and * uses only web-standard globals (`fetch`/`ReadableStream`/`TextDecoder`/`URLSearchParams`) — so it * stays inside the `types:[]` light-build closure (tsconfig.build.json) that `./client` publishes. * It is ERROR-AGNOSTIC: the caller INJECTS the fail-closed error factories (`httpError` / * `unknownEventError`), so the SDK throws `KestrelClientError` and the CLI throws `CliError` over * this ONE loop, and this leaf depends on neither. Hard vs. transient is discriminated * STRUCTURALLY (a protocol violation / a handler throw is fail-closed; only a stream drop * reconnects), never by the error's class. node ≥18 + bun + Workers. */ /** Bounded SSE reconnect budget (network resilience; a dropped connection re-opens * from the last opaque cursor — never restarts at the beginning). */ export declare const MAX_RECONNECTS = 5; export type FetchLike = (url: string, init?: RequestInit) => Promise<Response>; /** What the caller's frame handler tells the loop: whether the stream reached the caller's * terminal event, and an optional cursor override carried inside the decoded body. */ export interface SseFrameOutcome { readonly done?: boolean; readonly cursor?: string; } export interface SseStreamOptions { readonly fetch: FetchLike; /** The events endpoint WITHOUT a cursor query — the loop owns `?cursor=`/`Last-Event-ID`. */ readonly endpoint: string; /** The initial opaque cursor, handed back verbatim (never `.token` of a JSON object). */ readonly cursor: string; /** Caller headers (e.g. `Authorization`); the SSE `accept` + resume headers are added here. */ readonly headers?: Record<string, string>; /** The caller's CLOSED event vocabulary. Anything else → {@link SseStreamOptions.unknownEventError}. */ readonly isEventType: (type: string) => boolean; /** Decode/accumulate one in-vocabulary frame. `cursor` is the opaque resume bookmark AT this * frame (post `id:` advance); throw (any error) to fail closed. */ readonly onFrame: (type: string, data: string, cursor: string) => SseFrameOutcome; readonly maxReconnects?: number; /** Build the fail-closed transport error for a bad HTTP status (non-ok stream, no body, budget * exhaustion). REQUIRED — the leaf holds no error type of its own, so the caller supplies its * own (e.g. `CliError` for the CLI, `KestrelClientError` for the SDK). */ readonly httpError: (status: number, message: string) => Error; /** Build the fail-closed error for an off-vocabulary `event:`. REQUIRED. */ readonly unknownEventError: (type: string) => Error; } /** Read an event stream to the caller's terminal frame, resuming across drops from the last * opaque cursor. Returns once `onFrame` reports `done`; otherwise fails closed. */ export declare function consumeSseStream(opts: SseStreamOptions): Promise<void>; /** Minimal SSE frame parser over a web ReadableStream<Uint8Array> (node + bun + workers). Yields * one record per `\n\n`-delimited frame: the `event:` name, the concatenated `data:` payload, and * the raw `id:` line as an OPAQUE STRING (never JSON-parsed). Comment lines (leading `:`) are * ignored (SSE heartbeats). */ export declare function parseSSE(body: ReadableStream<Uint8Array>): AsyncGenerator<{ event: string | undefined; data: string; id: string | undefined; }>; //# sourceMappingURL=sse-transport.d.ts.map