Skip to main content
The transport layer beneath the UI: a typed client for the Gateway’s HTTP RPC and WebSocket protocol, plus the TanStack DB collection factory that turns those calls into live, reconnecting data. Framework-free on purpose.
Most UIs shouldn’t touch this directly. Use the React bindings in /reference/gateway-react, which hold one client for you and expose live hooks. Drop to this client only for non-React hosts, custom transports, or scripts.
Everything on this page lives at the subpath smthrs/gateway-client, not the bare smthrs facade; import from the subpath or the build fails.

SmithersGatewayClient

Construct it with a base URL (and optional auth token); call a named RPC method, or open a WebSocket and subscribe to a run’s event stream.
SmithersGatewayClientOptions

RPC methods

Each method is a thin typed wrapper over rpc(method, params), which POSTs to /v1/rpc/<method> and unwraps the response frame. Params and return shapes come from the method catalog; see /rpc/launch-run for one in full.
methods
launchRun, resumeRun, pauseRun, cancelRun, hijackRun, rewindRun.
LaunchRunRequest.options.startedBy accepts { harness?, sessionId?, prompt?, detected?: true } as durable caller-provided provenance. It is not an auth credential; use the configured bearer/token identity for access control.
methods
submitApproval, submitSignal, listApprovals.
methods
getRun, listRuns, listWorkflows, getNodeOutput, getNodeDiff, getRunDiff, whatHappened.
methods
cronList, cronCreate, cronDelete, cronRun.
methods
rpc(method, params, { signal? }) for any typed method, rpcRaw(method, params?, { signal? }) for an untyped one, and extensionRpc(namespace, key, params?) / streamExtension(namespace, key, params?) for gateway extensions.

Subscribing to events

connect() opens a SmithersGatewayConnection. The higher-level generators below filter and yield frames for you; most callers iterate one of those instead of driving the connection directly.
Run streams use a stable two-level envelope. The outer GatewayEventFrame.event is the stream frame kind. For an outer "run.event" frame, frame.payload.event is the run lifecycle name (such as "node.started"), and frame.payload.payload contains that lifecycle event’s fields:
Check frame.event for control frames (run.heartbeat, run.gap_resync). Only read frame.payload.event and frame.payload.payload when frame.event === "run.event". Use frame.payload.seq, not the outer connection frame.seq, to order run events or persist an afterSeq cursor. A reducer must not compare the outer frame.event to lifecycle names such as "node.started"; heartbeat frames report transport liveness and do not represent lifecycle progress.
AsyncGenerator<GatewayEventFrame>
Subscribes to one run and yields its run.* frames (run.event, run.gap_resync, run.heartbeat). Pass afterSeq to resume. A run.error frame (the gateway dropping the subscription, e.g. a backpressure disconnect) throws a GatewayRpcError carrying the gateway’s code. Closes the socket when iteration ends.
AsyncGenerator<GatewayEventFrame>
Same frames, but reconnects with backoff + jitter on a silent drop and resumes from the last observed seq. Stops on terminal completion or abort. Options: signal, backoff (GatewayBackoffOptions), healthyAfterMs, and onReconnect(event).
AsyncGenerator<GatewayEventFrame>
Subscribes to a run’s DevTools snapshot stream.
Promise<SmithersGatewayConnection>
Opens the WebSocket, performs the connect handshake (protocol + auth), and returns the live connection. Use only for request/response over the socket or raw event frames.

Disposal

void
Releases everything the client owns: aborts its in-flight RPCs and streams, closes the WebSockets connect() opened, and marks the client closed so a later call throws instead of opening a new socket. Idempotent, and it never aborts a caller’s own AbortSignal.
boolean
Whether close() has run.
Only the owner closes. SmithersGatewayProvider closes the client it built from options when the options rotate one in and on unmount, and never closes a client you passed as the client prop, so a client shared across trees stays yours to dispose.

SmithersGatewayConnection

A single open WebSocket: RPC via request / requestRaw, plus a single in-order async stream of server event frames via events(signal?). One consumer per connection; the generators above each own one.
Promise<payload>
Typed request/response over the socket.
Promise<unknown>
Untyped request/response over the socket.
AsyncGenerator<GatewayEventFrame>
In-order server event frames. Ends on close; throws on an error or invalid frame.
void
Closes the socket, rejects pending requests, and ends events().
Source SmithersGatewayClient.ts · SmithersGatewayConnection.ts · Tests SmithersGatewayClient.test.ts · See also /rpc/launch-run, Gateway integration

GatewayRpcError

The error every RPC rejects with on a failed frame or HTTP error. Inspect code to branch (for example, requiredScope is set on an authorization failure).
string
The RPC method that failed (or "websocket" for a malformed socket frame).
string
Machine-readable error code, e.g. HTTP_ERROR, INVALID_GATEWAY_RESPONSE, or the gateway’s own code from the response frame.
number
HTTP status when the failure came over HTTP RPC.
string
The scope the call needed, on an authorization failure.
string
Set when the gateway asks the client to refresh credentials.
unknown
Extra context attached by the gateway.
Source GatewayRpcError.ts · Tests gateway-client.test.ts

isGatewayUnavailableError

True when nothing that speaks the gateway protocol answered the URL: a 2xx response whose body isn’t an { ok, ... } envelope, or a fetch-level rejection (connection refused, DNS failure) because nothing is listening. The usual cause is a host app’s SPA fallback serving index.html for /v1/api/* because no gateway is wired. The data client throws these as code GATEWAY_UNAVAILABLE, and the QueryCollection layer already degrades them quietly: collections settle on the last rows a real gateway served (empty before the first success), one session-level console.info notice replaces the per-collection [QueryCollection] error spam, and the SSE change stream refuses non-text/event-stream answers so the connection status parks offline instead of flapping online. Recovery is automatic: the stream’s reconnect backoff keeps probing, and the reset emitted on a real reconnect refetches every collection. Branch on it when calling api.* directly for the same behavior.
Real gateway errors and non-2xx HTTP failures never classify as unavailable. Source isGatewayUnavailableError.ts · Tests gatewayUnavailableQuietDegrade.test.ts

gatewayBackoffDelay

Exponential backoff with full jitter for one 0-based attempt. The resilient stream uses it internally; call it directly for your own reconnect loop.
number
required
0-based attempt index. The base delay grows by factor ** attempt, capped at maxMs.
GatewayBackoffOptions
number
Milliseconds to wait, never negative.
Source gatewayBackoffDelay.ts · Tests gatewayBackoffDelay.test.ts

createSmithersCollections

Builds the TanStack DB collection registry. Local mode uses the Gateway REST domain API plus /v1/api/stream SSE invalidation. Multiplayer mode uses Electric shapes from electricBaseUrl for reads and keeps the same domain API write path.
WorkspaceMode
required
{ kind: "local", apiBaseUrl, token? } or { kind: "multiplayer", apiBaseUrl, electricBaseUrl, workspaceId, token? }.
QueryClient
required
The TanStack Query client used by QueryCollection and invalidation.
object
Registry methods include runs, run, runTree, runEvents, nodes, approvals, workflows, docs, prompts, scores, tickets, memoryFacts, and crons, plus connect, invalidate, and close.
Source createSmithersCollections.ts · Tests smithers-collections

createSmithersDataClient

Creates the domain API client that collection mutation handlers call. Reads and writes target /v1/api/*; stream.subscribe opens /v1/api/stream and emits change, reset, and heartbeat invalidation events.
WorkspaceMode
required
Workspace mode and auth token.
typeof fetch
Fetch override for tests or custom runtimes.
typeof EventSource
SSE implementation override. When omitted, the client falls back to streaming fetch.
HeadersInit
Extra headers merged into every /v1/api/* request and the change stream, so an API-key or proxy header authorizes the collection API and SSE exactly as it does RPC. The content type and authorization: Bearer <mode.token> win over conflicting entries. SmithersGatewayProvider forwards the gateway client’s headers here automatically.
object
{ mode, api, stream, close }. The api object covers run lifecycle, approvals, signals, crons, docs, prompts, memory facts, scores, tickets, node output, node diffs, and schema signatures.

Collection keys and row types

smithersCollectionKeys holds the TanStack Query keys used by the registry. The same package exports the row types: GatewayRunRow, GatewayRunSummaryRow, GatewayRunEventRow, GatewayRunNode, GatewayApprovalRow, GatewayWorkflowRow, GatewayCronRow, GatewayMemoryFactRow, GatewayScoreRow, GatewayTicketRow, and GatewayPromptRow. Run-tree helpers stay public for custom inspectors: flattenGatewayRunNode, snapshotToGatewayRunNode, and reconcileSnapshotNodes.
Source index.ts · Tests packages/gateway-client/tests · See also Gateway React API, Gateway integration, /rpc/launch-run