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