/** * Duplex-agnostic MSP NDJSON connection (spec 14990 FR-012/014/015/016). * * The transport supplies decoded UTF-8 chunks. Chunks may be empty and may * split anywhere — including mid surrogate pair: the byte accounting below * reconciles split pairs (FR-012(a)), so transports need not align chunks * to character boundaries. The Node child-process binding — which sets * stdout's encoding before implementing this interface — is one * well-behaved transport, not the general guarantee. */ import type { ErrorKind, ErrorObject, Notification, Request, RequestId } from "../msp.js"; /** * The two-way text stream a `Connection` speaks MSP over. * * Implement it to run the protocol over any duplex channel: yield decoded * UTF-8 chunks on `incoming` and accept outgoing frames through `write`. * Chunks may be empty and may split anywhere — even mid surrogate pair — the * connection reassembles them. If your transport decodes bytes itself, use a * streaming decoder: a multi-byte character split across two byte chunks and * decoded separately becomes a replacement character before the connection * ever sees it. The SDK's spawned child-process stdio binding sets the stream * encoding, so it is safe, and is one ready-made implementation. */ export interface DuplexTransport { readonly incoming: AsyncIterable; write(chunk: string): Promise; /** * `flushed` settles once every frame the connection had accepted when * `close()` was called has been HANDED to `write()` — submission, not * completion. A process-owning transport waits for it inside its own * shutdown budget before ending the peer's input, so a frame still queued * on a microtask is not dropped; it is never GATED by it, because * completion is exactly what a wedged peer withholds. Optional and * additive: a transport that ignores it behaves as before. */ close?(flushed?: Promise): void | Promise; } /** * Module-private friend seam (never on the package barrel, like spawn.ts's * `ownedTransport`): the submission tail a process-owning transport adopts as * its DEFAULT `flushed`, so a close routed around this class — e.g. the * public `child.close()` — gets the same step 0 (PR #22819 round 4). */ export declare const submissionTail: unique symbol; /** * Module-private friend seam, like {@link submissionTail}: a request whose * promise also yields the exact response frame it settled on, so a caller * that refuses a successful result (the handshake's schema-fingerprint * check) can put the whole refused frame on `ProtocolError.line`, the same * value every other refusal carries (#50588 review). */ export declare const requestWithFrame: unique symbol; /** What {@link requestWithFrame} resolves with: the result and its wire line. */ export interface FramedResult { readonly result: Record; readonly line: string; } /** * Tuning knobs for a `Connection`. Every option is optional: the defaults * suit a spawned host, so most applications never pass this at all. */ export interface ConnectionOptions { readonly frameLimitBytes?: number; readonly mintCommandId?: () => string; /** Injectable for deterministic transcript clients; values must be unique in flight. */ readonly mintRequestId?: () => RequestId; } /** * Per-command overrides for `Connection.command`: pin a command id so a * reconnect can replay the same logical command, cap the attempt count, or * supply your own retry delay. */ export interface CommandOptions { /** Reuse this id for a reconnect/replay of the same logical command. */ readonly commandId?: string; /** Total attempts, including the first. */ readonly maxAttempts?: number; /** Injectable so deterministic tests do not sleep. */ readonly retryDelay?: (attempt: number, error: MspError) => Promise; } /** Handles one server-initiated request and resolves with the result to send back. */ export type ServerRequestHandler = (request: Request) => Promise>; /** Receives every server notification, in arrival order. */ export type NotificationHandler = (notification: Notification) => void; /** Receives each framing or correlation violation the connection detects on its inbound stream. */ export type ProtocolErrorHandler = (error: ProtocolError) => void; /** The one typed error family consumers branch on (INV-012). */ export declare class MspError extends Error { readonly code: number; readonly kind: ErrorKind; readonly data: Readonly>; readonly retryable: boolean | undefined; constructor(error: ErrorObject); } /** A local framing/correlation violation, never a server-authored MSP error. */ export declare class ProtocolError extends Error { /** * The inbound frame (wire line) the violation was detected on, when there * is one — the whole line, e.g. the response a pending request refused; * an oversized frame is cut to the frame limit. Absent for local state * errors (closed connection, EOF) that no frame caused. */ readonly line: string | undefined; constructor(message: string, line?: string); } /** Create the SDK-owned UUIDv7 command-id source for a connection/client. */ export declare function createUuidV7Mint(): () => string; /** * One MSP connection over newline-delimited JSON. * * The connection sends your requests and commands, correlates their * responses, enforces the frame-size limit, and hands inbound notifications * and server requests to the handlers you register. It is transport-agnostic: * pair it with any `DuplexTransport`. Most applications never build one * directly — `MuseClient.spawn` or `spawnMspConnection` does it for you; * reach for a bare `Connection` when you need the raw notification feed or * your own transport. */ export declare class Connection { #private; constructor(transport: DuplexTransport, options?: ConnectionOptions); get closed(): Promise; /** See {@link submissionTail}. */ [submissionTail](): Promise; request(method: string, params?: Record): Promise>; /** See {@link requestWithFrame}. */ [requestWithFrame](method: string, params?: Record): Promise; /** * Take one id from THIS connection's single `commandId` minter (INV-013). * * Exposed for the callers that need the id BEFORE the ack: `sendUserTurn` * records its optimistic SS4.13 entry under it, and an entry created only on * the ack is invisible for exactly the window it exists to cover. The * alternative was a facade-side mint, which is the second minter INV-013 * forbids — delegating here keeps the single-minter property intact and * preserves the injected `mintCommandId` seam for deterministic transcripts. * * Pair it with `command(..., { commandId })`, which reuses the id instead of * minting a second one. */ mintCommandId(): string; notify(method: string, params?: Record): void; onServerRequest(handler: ServerRequestHandler): void; onNotification(handler: NotificationHandler): void; onProtocolError(handler: ProtocolErrorHandler): void; flush(): Promise; command(method: string, params: Record, options?: CommandOptions): Promise>; close(): Promise; }