@graphorin/client 0.6.0 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/README.md +3 -3
- package/dist/errors.d.ts.map +1 -1
- package/dist/graphorin-client.d.ts.map +1 -1
- package/dist/graphorin-client.js +3 -2
- package/dist/graphorin-client.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/package.js +6 -0
- package/dist/package.js.map +1 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @graphorin/client
|
|
2
2
|
|
|
3
|
+
## 0.6.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#142](https://github.com/o-stepper/graphorin/pull/142) [`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430) Thanks [@o-stepper](https://github.com/o-stepper)! - Version constants and version-bearing strings now derive from each package's manifest at build time (`VERSION = pkg.version`; writer ids, client/server info, OTLP framework attributes, build-info metrics interpolate it). No behavioral change at the current version: the rendered strings are byte-identical. A release bump no longer edits source; the new `check-version-consistency` gate fails any reintroduced hardcoded framework version.
|
|
8
|
+
|
|
9
|
+
- Updated dependencies [[`436d6ca`](https://github.com/o-stepper/graphorin/commit/436d6ca5ebbd16df094e915682d3915c279a8430)]:
|
|
10
|
+
- @graphorin/protocol@0.6.1
|
|
11
|
+
|
|
3
12
|
## 0.6.0
|
|
4
13
|
|
|
5
14
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](./LICENSE)
|
|
6
6
|
[](https://nodejs.org)
|
|
7
7
|
|
|
8
|
-
- **Version:** v0.6.
|
|
8
|
+
- **Version:** v0.6.1
|
|
9
9
|
- **License:** [MIT](./LICENSE) (© 2026 Oleksiy Stepurenko)
|
|
10
10
|
- **Repository:** <https://github.com/o-stepper/graphorin/tree/main/packages/client>
|
|
11
11
|
- **Issues:** <https://github.com/o-stepper/graphorin/issues>
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| Capability | Detail |
|
|
18
18
|
|---|---|
|
|
19
19
|
| **WebSocket transport** | Honours the `graphorin.protocol.v1` subprotocol; supports both bearer-token (Node SDK) and ticket-flow (browser) authentication. |
|
|
20
|
-
| **SSE fallback** | Read-only `EventSource`
|
|
20
|
+
| **SSE fallback** | Read-only fetch-streaming SSE transport (not `EventSource`) for environments that block WebSocket upgrades. It carries only the bound session stream, so it needs the `sessionId` client option and bearer auth; control-plane operations (`subscribe`, `cancel`, `resume`) fall back to REST. |
|
|
21
21
|
| **Async-iterable subscriptions** | `for await (const event of sub.events())` - typed `AgentEvent` / `WorkflowEvent` payload via `@graphorin/protocol`. |
|
|
22
22
|
| **Reconnect** | Exponential backoff with full jitter; resubscribes with the recorded `lastEventId` so the server replays buffered events. |
|
|
23
23
|
| **Bundle hygiene** | Browser-friendly. Zero Node-only dependencies; runtime depends only on `@graphorin/protocol` and `zod`. |
|
|
@@ -89,4 +89,4 @@ MIT © 2026 Oleksiy Stepurenko. See [`LICENSE`](./LICENSE).
|
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
92
|
-
**Project Graphorin** · v0.6.
|
|
92
|
+
**Project Graphorin** · v0.6.1 · MIT License · © 2026 Oleksiy Stepurenko · <https://github.com/o-stepper/graphorin>
|
package/dist/errors.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"sourcesContent":[],"mappings":";;AAiBA;AA2BA;AAuBA;;;;;;AAWA;AAQA;AAWA;AAgBA;AAQA;AAQa,iBAhHG,cAAA,CAgHuB,
|
|
1
|
+
{"version":3,"file":"errors.d.ts","names":[],"sources":["../src/errors.ts"],"sourcesContent":[],"mappings":";;AAiBA;AA2BA;AAuBA;;;;;;AAWA;AAQA;AAWA;AAgBA;AAQA;AAQa,iBAhHG,cAAA,CAgHuB,IAAA,EAAQ,MAAA,CAAA,EAhHD,wBAgHqB;AAcnE;AAQA;;;;AAKY,KAhHA,wBAAA,GAgHA,sBAAA,GAAA,kBAAA,GAAA,sBAAA,GAAA,aAAA,GAAA,oBAAA,GAAA,wBAAA,GAAA,cAAA,GAAA,cAAA,GAAA,eAAA,GAAA,cAAA,GAAA,SAAA,GAAA,sBAAA;;;;;;;;cAzFC,oBAAA,SAA6B,KAAA;iBACzB;oBAEG,qDAAoD;;;cAQ3D,uBAAA,SAAgC,oBAAA;;;;cAQhC,oBAAA,SAA6B,oBAAA;;yCAGF;;;;;cAQ3B,wBAAA,SAAiC,oBAAA;;;;;;cAgBjC,eAAA,SAAwB,oBAAA;;;;cAQxB,sBAAA,SAA+B,oBAAA;yCACJ;;;cAO3B,yBAAA,SAAkC,oBAAA;;;;;cAclC,kBAAA,SAA2B,oBAAA;;;;cAQ3B,uBAAA,SAAgC,oBAAA;mBAC1B;UAAsB;;;uCAI7B;UAAsB"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphorin-client.d.ts","names":[],"sources":["../src/graphorin-client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"graphorin-client.d.ts","names":[],"sources":["../src/graphorin-client.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;KAuEY,kBAAA;;;;;;;;;;;;;;;;;;;;;KAoBA,mBAAA,GAAsB;;;;;;UAOjB,sBAAA;;;;;;;;;;;;;;iBAcA;uBACM;;;;;;;;;;uBAUA;;8BAEO;;gCAEE;;0BAEN;;;;;;;;;;;;;;;;;UAkBT,oBAAA;;;mBAGE;;;;;;;;;;UAWF,YAAA;;;YAGL,cAAc;;;;iBAIT;cACH;;;;;cAmBD,eAAA;;uBAYU;;;;;;;;;aAeJ;;UAkCH;;;;;;;oBAWJ;;MAEP,QAAQ;;;;;;;;;;MAuER;;;;;;;;;;;;MA+BA;;;;;;;;;;gBA4DiB;;uBAwBC"}
|
package/dist/graphorin-client.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { version } from "./package.js";
|
|
1
2
|
import { ClientAbortedError, ClientNotConnectedError, GraphorinClientError, ProtocolViolationError, TransportFailedError, kindForRpcCode } from "./errors.js";
|
|
2
3
|
import { computeBackoffMs, sleep } from "./reconnect.js";
|
|
3
4
|
import { openSseTransport } from "./transport/sse.js";
|
|
@@ -78,7 +79,7 @@ var GraphorinClient = class {
|
|
|
78
79
|
if (this.#transport?.kind !== "ws") return;
|
|
79
80
|
await this.#sendRpc("initialize", { clientInfo: {
|
|
80
81
|
name: "graphorin-client",
|
|
81
|
-
version
|
|
82
|
+
version
|
|
82
83
|
} });
|
|
83
84
|
}
|
|
84
85
|
/** Send a `ping` RPC and resolve when the server replies with `pong`. */
|
|
@@ -558,7 +559,7 @@ function buildRpcFrame(method, id, params) {
|
|
|
558
559
|
method: "initialize",
|
|
559
560
|
params: params ?? { clientInfo: {
|
|
560
561
|
name: "graphorin-client",
|
|
561
|
-
version
|
|
562
|
+
version
|
|
562
563
|
} }
|
|
563
564
|
};
|
|
564
565
|
case "subscription.subscribe": return {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"graphorin-client.js","names":["#options","#pending","#subscriptions","#closed","#transport","#connectingPromise","#openWs","#initializeRpc","#openSse","#sendRpc","subject","#sseSubscription","#createSubscription","params: { subject: string; sinceEventId?: string }","params: {\n runId: string;\n drain?: boolean;\n reason?: string;\n onPendingApprovals?: 'deny' | 'preserve';\n }","headers: Record<string, string>","frame: ClientMessage","#abortController","#wsUrl","#listeners","#sseUrl","#handleFrame","#handleTransportError","#handleTransportClose","#sseFallback","#reconnect","queue: ServerEventFrame[]","lifecycle: ServerLifecycleFrame[]","replayMarkers: ServerReplayMarkerFrame[]","waiters: Array<{\n resolve: (value: IteratorResult<ServerEventFrame>) => void;\n reject: (err: Error) => void;\n }>","closeError: Error | undefined","lastEventId: string | undefined","#nextId","timer: ReturnType<typeof setTimeout> | undefined","pending: PendingRpc","#idCounter"],"sources":["../src/graphorin-client.ts"],"sourcesContent":["/**\n * `GraphorinClient` - ergonomic façade over the\n * {@link Transport} contract. Handles:\n *\n * - WS handshake (`openWebSocketTransport`) with optional ticket flow.\n * - Optional SSE fallback (`openSseTransport`) for environments\n * that block WebSocket upgrades.\n * - JSON-RPC request / response correlation for `subscribe` /\n * `unsubscribe` / `cancel` / `resume` / `ping` calls.\n * - Async-iterable subscriptions (`for await (const event of\n * sub.events())`).\n * - Exponential-backoff reconnect with `lastEventId` resume against\n * the server replay buffer.\n *\n * The class is intentionally small (≈ 400 LOC) so the production\n * cross-cuts (telemetry, sticky reconnect on transient errors,\n * load-shedding) live in higher-level wrappers consumers build on\n * top of `GraphorinClient` instead of leaking into the protocol\n * adapter itself.\n *\n * @packageDocumentation\n */\n\nimport {\n type ClientMessage,\n type ClientMessageId,\n isErrorFrame,\n isEventFrame,\n isLifecycleFrame,\n isPongFrame,\n isReplayMarkerFrame,\n isRpcFailure,\n isRpcSuccess,\n isSubscribedFrame,\n isUnsubscribedFrame,\n type ServerEventFrame,\n type ServerLifecycleFrame,\n type ServerMessage,\n type ServerReplayMarkerFrame,\n} from '@graphorin/protocol';\n\nimport {\n ClientAbortedError,\n ClientNotConnectedError,\n GraphorinClientError,\n kindForRpcCode,\n ProtocolViolationError,\n TransportFailedError,\n} from './errors.js';\nimport { type BackoffPolicy, computeBackoffMs, sleep } from './reconnect.js';\nimport {\n openSseTransport,\n openWebSocketTransport,\n type Transport,\n type TransportAuth,\n type TransportCloseReason,\n type TransportKind,\n} from './transport/index.js';\n\n/**\n * Discriminator for the subscription target. Mirrors the strict\n * subject grammar enforced by the server:\n * - `'session'`/`<id>` ⇒ `'session:<id>/events'`\n * - `'agent'`/`<id>` + `runId` ⇒ `'agent:<id>/runs/<runId>/events'`\n * - `'run'`/`<runId>` ⇒ `'session:<sessionId>/runs/<runId>/events'`\n * (when `sessionId` is provided)\n * - `'workflow'`/`<id>` ⇒ `'workflow:<id>/events'`\n *\n * @stable\n */\nexport type SubscriptionTarget =\n | { readonly target: 'session'; readonly id: string }\n | {\n readonly target: 'agent';\n readonly id: string;\n readonly runId: string;\n }\n | {\n readonly target: 'run';\n readonly runId: string;\n readonly sessionId?: string;\n }\n | { readonly target: 'workflow'; readonly id: string };\n\n/**\n * Transport selector. `'auto'` (default) attempts a WebSocket\n * handshake first and falls back to SSE on failure.\n *\n * @stable\n */\nexport type TransportPreference = TransportKind | 'auto';\n\n/**\n * Public configuration accepted by {@link GraphorinClient}.\n *\n * @stable\n */\nexport interface GraphorinClientOptions {\n /**\n * Session bound to the SSE fallback (IP-3): substituted into the\n * `:sessionId` slot of `sseSessionPath`. Required to connect over\n * SSE - the old client sent the literal template and could never\n * receive an event.\n */\n readonly sessionId?: string;\n /**\n * Server base URL. Examples: `'wss://graphorin.example.com'` or\n * `'http://localhost:8080'`. The path `/v1/ws` is appended for\n * the WebSocket transport.\n */\n readonly baseUrl: string;\n readonly auth: TransportAuth;\n readonly transport?: TransportPreference;\n /** Override the WS path (default `'/v1/ws'`). */\n readonly wsPath?: string;\n /**\n * SSE path template. The placeholder `:sessionId` is replaced at\n * subscribe time. Default: `'/v1/sessions/:sessionId/events'`.\n * Required when the transport is `'sse'` or when the WS handshake\n * fails on `'auto'`.\n */\n readonly sseSessionPath?: string;\n readonly reconnect?: BackoffPolicy;\n /** Inject a `WebSocket` constructor (Node SDKs / tests). */\n readonly WebSocket?: typeof WebSocket;\n /** Inject an `EventSource` constructor (Node SDKs / tests). */\n readonly EventSource?: typeof EventSource;\n /** Inject a `fetch` implementation (defaults to `globalThis.fetch`). */\n readonly fetch?: typeof fetch;\n /** Optional client identifier surfaced on diagnostics. */\n readonly clientId?: string;\n /**\n * IP-19: per-RPC reply timeout in milliseconds. When set (and > 0), an RPC\n * that receives no matching reply within this window rejects with a\n * {@link TransportFailedError} instead of hanging forever on a\n * non-responsive server. Default: unset - no timeout, so a legitimately\n * slow server reply is never aborted (opt-in).\n */\n readonly rpcTimeoutMs?: number;\n}\n\n/**\n * Snapshot returned by {@link Subscription.metadata}.\n *\n * @stable\n */\nexport interface SubscriptionMetadata {\n readonly id: string;\n readonly subject: string;\n readonly target: SubscriptionTarget;\n readonly snapshotEventId: string | undefined;\n readonly lastEventId: string | undefined;\n readonly closed: boolean;\n}\n\n/**\n * Public surface returned by {@link GraphorinClient.subscribe}.\n *\n * @stable\n */\nexport interface Subscription {\n readonly subscriptionId: string;\n readonly subject: string;\n events(): AsyncIterable<ServerEventFrame>;\n /**\n * Close the subscription on the server. Idempotent.\n */\n unsubscribe(): Promise<void>;\n metadata(): SubscriptionMetadata;\n}\n\ninterface SubscriptionInternal extends Subscription {\n __push(frame: ServerEventFrame): void;\n __pushLifecycle(frame: ServerLifecycleFrame): void;\n __pushReplayMarker(frame: ServerReplayMarkerFrame): void;\n __close(reason: 'unsubscribed' | 'transport-closed' | 'aborted', error?: Error): void;\n __subject(): string;\n __target(): SubscriptionTarget;\n __snapshotEventId(): string | undefined;\n __lastEventId(): string | undefined;\n /** IP-7: re-point this subscription at a new server subscriptionId. */\n __rebind(newSubscriptionId: string): void;\n}\n\n/**\n * @stable\n */\nexport class GraphorinClient {\n readonly #options: GraphorinClientOptions;\n readonly #pending: Map<ClientMessageId, PendingRpc> = new Map();\n readonly #subscriptions: Map<string, SubscriptionInternal> = new Map();\n /** IP-3: the synthetic single subscription an SSE connection carries. */\n #sseSubscription: SubscriptionInternal | undefined;\n #transport: Transport | undefined;\n #idCounter = 0;\n #closed = false;\n #connectingPromise: Promise<void> | undefined;\n #abortController: AbortController = new AbortController();\n\n constructor(options: GraphorinClientOptions) {\n if (typeof options.baseUrl !== 'string' || options.baseUrl.length === 0) {\n throw new TypeError('GraphorinClient: baseUrl must be a non-empty string.');\n }\n this.#options = options;\n }\n\n /**\n * Open the underlying transport. Resolves once the server has\n * accepted the handshake (`'open'`); rejects with a typed\n * {@link GraphorinClientError} otherwise.\n *\n * Calling `connect()` while already connected is a no-op; calling\n * it during another `connect()` returns the same promise.\n */\n async connect(): Promise<void> {\n if (this.#closed) {\n throw new ClientAbortedError('GraphorinClient was disconnected; create a new instance.');\n }\n if (this.#transport !== undefined) return;\n if (this.#connectingPromise !== undefined) return this.#connectingPromise;\n this.#connectingPromise = (async () => {\n const preference = this.#options.transport ?? 'auto';\n try {\n if (preference === 'ws' || preference === 'auto') {\n try {\n this.#transport = await this.#openWs();\n await this.#initializeRpc();\n return;\n } catch (err) {\n if (preference === 'ws') throw err;\n }\n }\n this.#transport = await this.#openSse();\n } finally {\n this.#connectingPromise = undefined;\n }\n })();\n return this.#connectingPromise;\n }\n\n async #initializeRpc(): Promise<void> {\n if (this.#transport?.kind !== 'ws') return;\n await this.#sendRpc('initialize', {\n clientInfo: { name: 'graphorin-client', version: '0.6.0' },\n });\n }\n\n /** Send a `ping` RPC and resolve when the server replies with `pong`. */\n async ping(): Promise<void> {\n await this.#sendRpc('ping');\n }\n\n /**\n * Subscribe to a server-side event stream. Resolves with a\n * {@link Subscription} once the server confirms with the matching\n * `subscribed` frame; rejects when the server returns an\n * `error` instead.\n */\n async subscribe(\n target: SubscriptionTarget,\n opts?: { readonly sinceEventId?: string },\n ): Promise<Subscription> {\n if (this.#transport === undefined) throw new ClientNotConnectedError();\n if (this.#transport.kind === 'sse') {\n // IP-3: the SSE connection IS one implicit subscription to the\n // bound session's events. Frames arrive with a server-generated\n // subscriptionId the client cannot know upfront, so routing\n // falls through to this synthetic subscription (see\n // #handleFrame). Other subjects still require the WS transport.\n const subject = subjectFor(target);\n const boundSubject = `session:${this.#options.sessionId ?? ''}/events`;\n if (subject !== boundSubject) {\n throw new TransportFailedError(\n `subscribe('${subject}') requires the WebSocket transport - the SSE fallback carries only the bound session stream ('${boundSubject}').`,\n );\n }\n // periphery-03: a CLOSED bound subscription (a prior terminal\n // reconnect failure) must not be handed back - recreate it so a\n // recovered client delivers events again.\n if (this.#sseSubscription === undefined || this.#sseSubscription.metadata().closed) {\n this.#sseSubscription = this.#createSubscription({\n subscriptionId: '__sse__',\n subject,\n target,\n snapshotEventId: undefined,\n });\n this.#subscriptions.set('__sse__', this.#sseSubscription);\n }\n return this.#sseSubscription;\n }\n const subject = subjectFor(target);\n // IP-7: a resubscribe passes the SUBSCRIPTION's own cursor - the\n // fresh transport's lastEventId is always undefined, so the old\n // code never consulted the server replay buffer.\n const lastEventId = opts?.sinceEventId ?? this.#transport.lastEventId;\n const params: { subject: string; sinceEventId?: string } = {\n subject,\n };\n if (lastEventId !== undefined) params.sinceEventId = lastEventId;\n const reply = await this.#sendRpc('subscription.subscribe', params);\n const result = reply as { subscriptionId?: unknown; snapshotEventId?: unknown };\n const subscriptionId =\n typeof result.subscriptionId === 'string' && result.subscriptionId.length > 0\n ? result.subscriptionId\n : undefined;\n if (subscriptionId === undefined) {\n throw new ProtocolViolationError('Server subscribe reply missing subscriptionId.');\n }\n const snapshotEventId =\n typeof result.snapshotEventId === 'string' ? result.snapshotEventId : undefined;\n const sub = this.#createSubscription({\n subscriptionId,\n subject,\n target,\n snapshotEventId,\n });\n this.#subscriptions.set(subscriptionId, sub);\n return sub;\n }\n\n /**\n * Cancel a server-side run. Sends the `run.cancel` RPC and\n * resolves with the server's `result` payload (typically\n * `{ cancelled: true, partialStateAvailable: true }`).\n */\n async cancel(\n runId: string,\n opts: {\n readonly drain?: boolean;\n readonly reason?: string;\n readonly onPendingApprovals?: 'deny' | 'preserve';\n } = {},\n ): Promise<unknown> {\n if (typeof runId !== 'string' || runId.length === 0) {\n throw new TypeError('cancel: runId must be a non-empty string.');\n }\n const params: {\n runId: string;\n drain?: boolean;\n reason?: string;\n onPendingApprovals?: 'deny' | 'preserve';\n } = { runId };\n if (opts.drain !== undefined) params.drain = opts.drain;\n if (opts.reason !== undefined) params.reason = opts.reason;\n if (opts.onPendingApprovals !== undefined) {\n params.onPendingApprovals = opts.onPendingApprovals;\n }\n return this.#sendRpc('run.cancel', params);\n }\n\n /**\n * Resume a paused (HITL) run. The WebSocket protocol intentionally\n * does NOT carry a `resume` control message - resumes are durable\n * + idempotent + body-carrying, which maps onto the REST endpoint\n * `POST /v1/runs/:runId/resume`. NOTE (IP-14): the server endpoint\n * currently answers **501** - server-side durable resume is not\n * implemented yet. Library-mode callers resume directly:\n * `agent.run(result.state, { directive })`.\n */\n async resume(\n runId: string,\n directive?: unknown,\n opts: { readonly idempotencyKey?: string } = {},\n ): Promise<unknown> {\n if (typeof runId !== 'string' || runId.length === 0) {\n throw new TypeError('resume: runId must be a non-empty string.');\n }\n const fetchImpl = this.#options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new TransportFailedError(\n 'No fetch implementation available; pass `fetch` via the client options.',\n );\n }\n const url = joinUrl(this.#options.baseUrl, `/v1/runs/${encodeURIComponent(runId)}/resume`, {\n ws: false,\n });\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.#options.auth.kind === 'bearer') {\n headers.Authorization = `Bearer ${this.#options.auth.token}`;\n }\n if (opts.idempotencyKey !== undefined) headers['Idempotency-Key'] = opts.idempotencyKey;\n const res = await fetchImpl(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(directive === undefined ? {} : { directive }),\n });\n if (!res.ok) {\n throw new TransportFailedError(\n `resume(${runId}): server responded ${res.status} ${res.statusText}.`,\n { code: res.status },\n );\n }\n try {\n return await res.json();\n } catch {\n return undefined;\n }\n }\n\n /**\n * Send an MCP-compatible cancellation notification. Does not wait\n * for a server reply (notifications have no `id`).\n */\n cancelNotify(requestId: string): void {\n if (this.#transport === undefined) throw new ClientNotConnectedError();\n if (typeof requestId !== 'string' || requestId.length === 0) {\n throw new TypeError('cancelNotify: requestId must be a non-empty string.');\n }\n const frame: ClientMessage = {\n v: '1',\n jsonrpc: '2.0',\n method: 'notifications/cancelled',\n params: { requestId },\n };\n this.#transport.send(frame);\n }\n\n /**\n * Disconnect the underlying transport and abort every pending RPC\n * + subscription. Idempotent.\n */\n async disconnect(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n const transport = this.#transport;\n this.#transport = undefined;\n this.#abortController.abort(new ClientAbortedError());\n for (const pending of this.#pending.values()) {\n pending.reject(new ClientAbortedError('Client disconnected before reply.'));\n }\n this.#pending.clear();\n for (const sub of this.#subscriptions.values()) {\n sub.__close('aborted', new ClientAbortedError('Client disconnected.'));\n }\n this.#subscriptions.clear();\n if (transport !== undefined) {\n try {\n transport.close(1000, 'client-disconnect');\n } catch {\n // Best-effort close: never throw from disconnect().\n }\n }\n }\n\n /** Return the active transport kind (or `undefined` if not connected). */\n get transportKind(): TransportKind | undefined {\n return this.#transport?.kind;\n }\n\n // -----------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------\n\n async #openWs(): Promise<Transport> {\n const url = this.#wsUrl();\n const transport = await openWebSocketTransport(\n {\n url,\n auth: this.#options.auth,\n ...(this.#options.WebSocket !== undefined ? { WebSocket: this.#options.WebSocket } : {}),\n ...(this.#options.clientId !== undefined ? { clientId: this.#options.clientId } : {}),\n },\n this.#listeners('ws'),\n );\n return transport;\n }\n\n async #openSse(): Promise<Transport> {\n if (this.#options.auth.kind !== 'bearer') {\n throw new TransportFailedError(\n \"SSE fallback requires the 'bearer' auth strategy. The WebSocket ticket flow does not extend to EventSource.\",\n );\n }\n const url = this.#sseUrl();\n // periphery-03: resume from where the bound session subscription\n // left off - without the cursor every reconnect replays the whole\n // server buffer into the consumer.\n const resumeCursor = this.#sseSubscription?.__lastEventId();\n const transport = await openSseTransport(\n {\n url,\n auth: this.#options.auth,\n // IP-3: the fetch-streaming transport uses the client's fetch\n // seam (the EventSource seam is gone with the rewrite).\n ...(this.#options.fetch !== undefined ? { fetch: this.#options.fetch } : {}),\n ...(this.#options.clientId !== undefined ? { clientId: this.#options.clientId } : {}),\n ...(resumeCursor !== undefined ? { lastEventId: resumeCursor } : {}),\n },\n this.#listeners('sse'),\n );\n return transport;\n }\n\n #wsUrl(): string {\n const path = this.#options.wsPath ?? '/v1/ws';\n return joinUrl(this.#options.baseUrl, path, { ws: true });\n }\n\n #sseUrl(): string {\n const template = this.#options.sseSessionPath ?? '/v1/sessions/:sessionId/events';\n // IP-3: bind the session into the path - the literal template was\n // previously sent as-is, so the SSE fallback could never connect\n // to a real session stream.\n if (template.includes(':sessionId')) {\n const sessionId = this.#options.sessionId;\n if (sessionId === undefined || sessionId.length === 0) {\n throw new TransportFailedError(\n \"The SSE fallback needs a session binding - pass `sessionId` in the client options (it fills ':sessionId' in sseSessionPath).\",\n );\n }\n return joinUrl(\n this.#options.baseUrl,\n template.replace(':sessionId', encodeURIComponent(sessionId)),\n { ws: false },\n );\n }\n return joinUrl(this.#options.baseUrl, template, { ws: false });\n }\n\n #listeners(kind: TransportKind): {\n onOpen(): void;\n onFrame(frame: ServerMessage): void;\n onError(err: Error): void;\n onClose(reason: TransportCloseReason): void;\n } {\n return {\n onOpen: () => {},\n onFrame: (frame) => this.#handleFrame(frame),\n onError: (err) => this.#handleTransportError(err, kind),\n onClose: (reason) => this.#handleTransportClose(reason, kind),\n };\n }\n\n #handleFrame(frame: ServerMessage): void {\n if (isRpcSuccess(frame)) {\n const pending = this.#pending.get(frame.id);\n if (pending !== undefined) {\n this.#pending.delete(frame.id);\n pending.resolve(frame.result);\n }\n return;\n }\n if (isRpcFailure(frame)) {\n const pending = this.#pending.get(frame.id);\n if (pending !== undefined) {\n this.#pending.delete(frame.id);\n // IP-19: surface the server's error class (rate-limited, scope-denied,\n // auth-failed, …) instead of collapsing every RPC failure to\n // 'protocol-violation'.\n pending.reject(\n new GraphorinClientError(\n kindForRpcCode(frame.error.code),\n `Server returned an error for RPC '${frame.id}': ${frame.error.message} (code=${frame.error.code}).`,\n ),\n );\n }\n return;\n }\n if (isSubscribedFrame(frame)) {\n // Subscribed frames are acknowledged by the matching RPC reply.\n return;\n }\n if (isUnsubscribedFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId);\n if (sub !== undefined) {\n sub.__close('unsubscribed');\n this.#subscriptions.delete(frame.subscriptionId);\n }\n return;\n }\n if (isPongFrame(frame)) return;\n if (isLifecycleFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId) ?? this.#sseFallback();\n if (sub !== undefined) sub.__pushLifecycle(frame);\n return;\n }\n if (isReplayMarkerFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId) ?? this.#sseFallback();\n if (sub !== undefined) sub.__pushReplayMarker(frame);\n return;\n }\n if (isEventFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId) ?? this.#sseFallback();\n if (sub !== undefined) sub.__push(frame);\n return;\n }\n if (isErrorFrame(frame)) {\n const target =\n frame.subscriptionId !== undefined\n ? this.#subscriptions.get(frame.subscriptionId)\n : undefined;\n const error = new GraphorinClientError(\n 'protocol-violation',\n `Server error frame: ${frame.message} (code=${frame.code}).`,\n );\n if (target !== undefined) {\n target.__close('aborted', error);\n if (frame.subscriptionId !== undefined) this.#subscriptions.delete(frame.subscriptionId);\n }\n }\n }\n\n /**\n * IP-3: on the SSE transport every frame belongs to the single\n * implicit session subscription regardless of the server-generated\n * subscriptionId it carries.\n */\n #sseFallback(): SubscriptionInternal | undefined {\n return this.#transport?.kind === 'sse' ? this.#sseSubscription : undefined;\n }\n\n #handleTransportError(err: Error, _kind: TransportKind): void {\n for (const pending of this.#pending.values()) {\n pending.reject(err);\n }\n this.#pending.clear();\n }\n\n async #handleTransportClose(reason: TransportCloseReason, _kind: TransportKind): Promise<void> {\n if (this.#closed) return;\n this.#transport = undefined;\n const closeError = new TransportFailedError(\n `Transport closed: ${reason.reason || reason.graphorinReason || 'unknown'} (code=${reason.code}).`,\n { code: reason.code },\n );\n // Best-effort drain of pending RPCs so callers see the failure\n // immediately rather than hanging until the reconnect window\n // closes.\n for (const pending of this.#pending.values()) {\n pending.reject(closeError);\n }\n this.#pending.clear();\n if (\n reason.graphorinReason === 'auth.required' ||\n reason.graphorinReason === 'auth.invalid' ||\n reason.graphorinReason === 'auth.revoked' ||\n reason.graphorinReason === 'protocol.violation'\n ) {\n // Fatal: stop trying to reconnect and surface the failure.\n for (const sub of this.#subscriptions.values()) {\n sub.__close('aborted', closeError);\n }\n this.#subscriptions.clear();\n return;\n }\n await this.#reconnect();\n }\n\n async #reconnect(): Promise<void> {\n if (this.#closed) return;\n let attempt = 0;\n while (!this.#closed) {\n attempt += 1;\n const delay = computeBackoffMs(attempt, this.#options.reconnect);\n if (delay === null) {\n const exhausted = new TransportFailedError(\n `Reconnect attempts exhausted after ${attempt - 1} retries.`,\n );\n for (const sub of this.#subscriptions.values()) {\n sub.__close('aborted', exhausted);\n }\n this.#subscriptions.clear();\n return;\n }\n try {\n await sleep(delay, this.#abortController.signal);\n await this.connect();\n // Best-effort resubscribe with the recorded lastEventId so\n // the server replays missed events from the buffer.\n const transport = this.#transport;\n if (transport === undefined) continue;\n // periphery-03: on the SSE fallback the connection IS the\n // subscription - the reconnect already carried the resume\n // cursor as `Last-Event-ID`, and frames keep flowing into the\n // same bound subscription object. The RPC resubscribe below\n // would call `transport.send(...)`, which the read-only SSE\n // transport rejects; pre-fix that error CLOSED the\n // subscription and the client silently stopped delivering\n // events for the life of the object.\n if (transport.kind === 'sse') return;\n for (const [oldId, sub] of [...this.#subscriptions]) {\n this.#subscriptions.delete(oldId);\n try {\n // IP-7: re-establish the server-side subscription with the\n // SUBSCRIPTION's own replay cursor, then re-point the SAME\n // object at the new server id - the consumer's in-flight\n // `for await` survives and the replayed events arrive in\n // the iterator it is already reading. The old code created\n // a NEW subscription and closed this one: the consumer's\n // loop ended `{done: true}` while events piled up unread\n // in an orphan.\n const cursor = sub.__lastEventId();\n const reply = await this.#sendRpc('subscription.subscribe', {\n subject: sub.__subject(),\n ...(cursor !== undefined ? { sinceEventId: cursor } : {}),\n });\n const result = reply as { subscriptionId?: unknown };\n const newId =\n typeof result.subscriptionId === 'string' && result.subscriptionId.length > 0\n ? result.subscriptionId\n : undefined;\n if (newId === undefined) {\n throw new ProtocolViolationError('Server resubscribe reply missing subscriptionId.');\n }\n sub.__rebind(newId);\n this.#subscriptions.set(newId, sub);\n } catch (err) {\n sub.__close('aborted', err instanceof Error ? err : new Error(String(err)));\n }\n }\n return;\n } catch (err) {\n if (this.#closed) return;\n // Loop and retry.\n if (err instanceof ClientAbortedError) return;\n }\n }\n }\n\n #createSubscription(args: {\n subscriptionId: string;\n readonly subject: string;\n readonly target: SubscriptionTarget;\n readonly snapshotEventId: string | undefined;\n }): SubscriptionInternal {\n const queue: ServerEventFrame[] = [];\n const lifecycle: ServerLifecycleFrame[] = [];\n const replayMarkers: ServerReplayMarkerFrame[] = [];\n const waiters: Array<{\n resolve: (value: IteratorResult<ServerEventFrame>) => void;\n reject: (err: Error) => void;\n }> = [];\n let closed = false;\n let closeError: Error | undefined;\n let lastEventId: string | undefined = args.snapshotEventId;\n\n const push = (frame: ServerEventFrame): void => {\n lastEventId = frame.eventId;\n const waiter = waiters.shift();\n if (waiter !== undefined) {\n waiter.resolve({ value: frame, done: false });\n return;\n }\n queue.push(frame);\n };\n\n const close = (reason: 'unsubscribed' | 'transport-closed' | 'aborted', err?: Error): void => {\n if (closed) return;\n closed = true;\n if (err !== undefined) closeError = err;\n else if (reason === 'aborted') closeError = new ClientAbortedError();\n while (waiters.length > 0) {\n const waiter = waiters.shift();\n if (waiter === undefined) continue;\n // IP-19(c): a waiter blocked in next() must observe the SAME outcome a\n // fresh next() would after close - reject with closeError when the\n // stream tore down abnormally, rather than silently resolving done:true\n // and swallowing the transport/abort failure.\n if (closeError !== undefined) {\n waiter.reject(closeError);\n } else {\n waiter.resolve({ value: undefined as unknown as ServerEventFrame, done: true });\n }\n }\n };\n\n const sub: SubscriptionInternal = {\n get subscriptionId(): string {\n return args.subscriptionId;\n },\n subject: args.subject,\n events: () => ({\n [Symbol.asyncIterator]: () => ({\n next: (): Promise<IteratorResult<ServerEventFrame>> => {\n const buffered = queue.shift();\n if (buffered !== undefined) {\n return Promise.resolve({ value: buffered, done: false });\n }\n if (closed) {\n if (closeError !== undefined) return Promise.reject(closeError);\n return Promise.resolve({\n value: undefined as unknown as ServerEventFrame,\n done: true,\n });\n }\n return new Promise<IteratorResult<ServerEventFrame>>((resolve, reject) => {\n waiters.push({ resolve, reject });\n });\n },\n return: (): Promise<IteratorResult<ServerEventFrame>> => {\n close('unsubscribed');\n return Promise.resolve({ value: undefined as unknown as ServerEventFrame, done: true });\n },\n }),\n }),\n unsubscribe: async () => {\n if (closed) return;\n try {\n await this.#sendRpc('subscription.unsubscribe', {\n subscriptionId: args.subscriptionId,\n });\n } catch (err) {\n if (!(err instanceof ClientAbortedError)) throw err;\n }\n close('unsubscribed');\n this.#subscriptions.delete(args.subscriptionId);\n },\n metadata: () => ({\n id: args.subscriptionId,\n subject: args.subject,\n target: args.target,\n snapshotEventId: args.snapshotEventId,\n lastEventId,\n closed,\n }),\n __push: push,\n __pushLifecycle: (frame) => {\n lifecycle.push(frame);\n if (\n frame.status === 'completed' ||\n frame.status === 'aborted' ||\n frame.status === 'failed'\n ) {\n close(frame.status === 'completed' ? 'unsubscribed' : 'aborted');\n this.#subscriptions.delete(args.subscriptionId);\n }\n },\n __pushReplayMarker: (frame) => {\n replayMarkers.push(frame);\n },\n __close: close,\n __subject: () => args.subject,\n __target: () => args.target,\n __snapshotEventId: () => args.snapshotEventId,\n __lastEventId: () => lastEventId,\n __rebind: (newSubscriptionId: string) => {\n args.subscriptionId = newSubscriptionId;\n },\n };\n return sub;\n }\n\n async #sendRpc(method: ClientMessage['method'], params?: object): Promise<unknown> {\n if (this.#transport === undefined) throw new ClientNotConnectedError();\n const id = this.#nextId();\n const frame = buildRpcFrame(method, id, params);\n const timeoutMs = this.#options.rpcTimeoutMs;\n return new Promise((resolve, reject) => {\n // IP-19: arm an optional reply timer. Wrapping resolve/reject so that\n // EVERY settle path (matching reply, disconnect, transport error) clears\n // the timer - otherwise a stray timer could reject an already-settled id.\n let timer: ReturnType<typeof setTimeout> | undefined;\n const clear = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n const pending: PendingRpc = {\n resolve: (value) => {\n clear();\n resolve(value);\n },\n reject: (reason) => {\n clear();\n reject(reason);\n },\n };\n this.#pending.set(id, pending);\n if (timeoutMs !== undefined && timeoutMs > 0) {\n timer = setTimeout(() => {\n // Fire only if this exact request is still outstanding.\n if (this.#pending.get(id) === pending) {\n this.#pending.delete(id);\n reject(\n new TransportFailedError(\n `RPC '${method}' (id=${id}) timed out after ${timeoutMs}ms with no server reply.`,\n ),\n );\n }\n }, timeoutMs);\n }\n try {\n // Asserting non-null because we just verified above.\n (this.#transport as Transport).send(frame);\n } catch (err) {\n clear();\n this.#pending.delete(id);\n reject(err);\n }\n });\n }\n\n #nextId(): string {\n this.#idCounter += 1;\n const prefix = this.#options.clientId ?? 'graphorin';\n return `${prefix}-${this.#idCounter.toString(36)}`;\n }\n}\n\ninterface PendingRpc {\n readonly resolve: (value: unknown) => void;\n readonly reject: (reason: Error) => void;\n}\n\nfunction subjectFor(target: SubscriptionTarget): string {\n switch (target.target) {\n case 'session':\n assertNonEmpty(target.id, 'session.id');\n return `session:${target.id}/events`;\n case 'agent':\n assertNonEmpty(target.id, 'agent.id');\n assertNonEmpty(target.runId, 'agent.runId');\n return `agent:${target.id}/runs/${target.runId}/events`;\n case 'run':\n assertNonEmpty(target.runId, 'run.runId');\n if (target.sessionId !== undefined) {\n assertNonEmpty(target.sessionId, 'run.sessionId');\n return `session:${target.sessionId}/runs/${target.runId}/events`;\n }\n // periphery-09: the server subject grammar has NO bare `run:`\n // form - the old `run:<id>/events` fallback always failed\n // server-side with an opaque unknown-subject error. Fail fast\n // with an actionable message instead.\n throw new TypeError(\n \"subscribe({ target: 'run' }) requires `sessionId` - the server subject grammar has no bare run form. \" +\n \"Pass { kind: 'run', runId, sessionId } or use the 'agent' target with the agent id.\",\n );\n case 'workflow':\n assertNonEmpty(target.id, 'workflow.id');\n return `workflow:${target.id}/events`;\n }\n}\n\nfunction assertNonEmpty(value: string, fieldName: string): void {\n if (typeof value !== 'string' || value.length === 0) {\n throw new TypeError(`${fieldName} must be a non-empty string.`);\n }\n}\n\nfunction joinUrl(baseUrl: string, path: string, opts: { readonly ws: boolean }): string {\n let normalized = baseUrl;\n if (opts.ws) {\n if (normalized.startsWith('http://')) normalized = normalized.replace(/^http:\\/\\//, 'ws://');\n else if (normalized.startsWith('https://'))\n normalized = normalized.replace(/^https:\\/\\//, 'wss://');\n } else {\n if (normalized.startsWith('ws://')) normalized = normalized.replace(/^ws:\\/\\//, 'http://');\n else if (normalized.startsWith('wss://'))\n normalized = normalized.replace(/^wss:\\/\\//, 'https://');\n }\n if (normalized.endsWith('/')) normalized = normalized.slice(0, -1);\n const tail = path.startsWith('/') ? path : `/${path}`;\n return `${normalized}${tail}`;\n}\n\nfunction buildRpcFrame(\n method: ClientMessage['method'],\n id: ClientMessageId,\n params?: object,\n): ClientMessage {\n switch (method) {\n case 'initialize':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'initialize',\n params: (params ?? { clientInfo: { name: 'graphorin-client', version: '0.6.0' } }) as never,\n };\n case 'subscription.subscribe':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'subscription.subscribe',\n params: (params ?? { subject: '' }) as never,\n };\n case 'subscription.unsubscribe':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'subscription.unsubscribe',\n params: (params ?? { subscriptionId: '' }) as never,\n };\n case 'run.cancel':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'run.cancel',\n params: (params ?? { runId: '' }) as never,\n };\n case 'ping':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'ping',\n ...(params !== undefined ? { params: params as never } : {}),\n };\n case 'notifications/cancelled':\n throw new TypeError(\n \"buildRpcFrame: 'notifications/cancelled' is a notification, not an RPC.\",\n );\n }\n // Exhaustiveness check; should be unreachable.\n throw new TypeError(`Unknown RPC method '${method}'.`);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"graphorin-client.js","names":["#options","#pending","#subscriptions","#closed","#transport","#connectingPromise","#openWs","#initializeRpc","#openSse","#sendRpc","pkg.version","subject","#sseSubscription","#createSubscription","params: { subject: string; sinceEventId?: string }","params: {\n runId: string;\n drain?: boolean;\n reason?: string;\n onPendingApprovals?: 'deny' | 'preserve';\n }","headers: Record<string, string>","frame: ClientMessage","#abortController","#wsUrl","#listeners","#sseUrl","#handleFrame","#handleTransportError","#handleTransportClose","#sseFallback","#reconnect","queue: ServerEventFrame[]","lifecycle: ServerLifecycleFrame[]","replayMarkers: ServerReplayMarkerFrame[]","waiters: Array<{\n resolve: (value: IteratorResult<ServerEventFrame>) => void;\n reject: (err: Error) => void;\n }>","closeError: Error | undefined","lastEventId: string | undefined","#nextId","timer: ReturnType<typeof setTimeout> | undefined","pending: PendingRpc","#idCounter"],"sources":["../src/graphorin-client.ts"],"sourcesContent":["import pkg from '../package.json' with { type: 'json' };\n/**\n * `GraphorinClient` - ergonomic façade over the\n * {@link Transport} contract. Handles:\n *\n * - WS handshake (`openWebSocketTransport`) with optional ticket flow.\n * - Optional SSE fallback (`openSseTransport`) for environments\n * that block WebSocket upgrades.\n * - JSON-RPC request / response correlation for `subscribe` /\n * `unsubscribe` / `cancel` / `resume` / `ping` calls.\n * - Async-iterable subscriptions (`for await (const event of\n * sub.events())`).\n * - Exponential-backoff reconnect with `lastEventId` resume against\n * the server replay buffer.\n *\n * The class is intentionally small (≈ 400 LOC) so the production\n * cross-cuts (telemetry, sticky reconnect on transient errors,\n * load-shedding) live in higher-level wrappers consumers build on\n * top of `GraphorinClient` instead of leaking into the protocol\n * adapter itself.\n *\n * @packageDocumentation\n */\n\nimport {\n type ClientMessage,\n type ClientMessageId,\n isErrorFrame,\n isEventFrame,\n isLifecycleFrame,\n isPongFrame,\n isReplayMarkerFrame,\n isRpcFailure,\n isRpcSuccess,\n isSubscribedFrame,\n isUnsubscribedFrame,\n type ServerEventFrame,\n type ServerLifecycleFrame,\n type ServerMessage,\n type ServerReplayMarkerFrame,\n} from '@graphorin/protocol';\n\nimport {\n ClientAbortedError,\n ClientNotConnectedError,\n GraphorinClientError,\n kindForRpcCode,\n ProtocolViolationError,\n TransportFailedError,\n} from './errors.js';\nimport { type BackoffPolicy, computeBackoffMs, sleep } from './reconnect.js';\nimport {\n openSseTransport,\n openWebSocketTransport,\n type Transport,\n type TransportAuth,\n type TransportCloseReason,\n type TransportKind,\n} from './transport/index.js';\n\n/**\n * Discriminator for the subscription target. Mirrors the strict\n * subject grammar enforced by the server:\n * - `'session'`/`<id>` ⇒ `'session:<id>/events'`\n * - `'agent'`/`<id>` + `runId` ⇒ `'agent:<id>/runs/<runId>/events'`\n * - `'run'`/`<runId>` ⇒ `'session:<sessionId>/runs/<runId>/events'`\n * (when `sessionId` is provided)\n * - `'workflow'`/`<id>` ⇒ `'workflow:<id>/events'`\n *\n * @stable\n */\nexport type SubscriptionTarget =\n | { readonly target: 'session'; readonly id: string }\n | {\n readonly target: 'agent';\n readonly id: string;\n readonly runId: string;\n }\n | {\n readonly target: 'run';\n readonly runId: string;\n readonly sessionId?: string;\n }\n | { readonly target: 'workflow'; readonly id: string };\n\n/**\n * Transport selector. `'auto'` (default) attempts a WebSocket\n * handshake first and falls back to SSE on failure.\n *\n * @stable\n */\nexport type TransportPreference = TransportKind | 'auto';\n\n/**\n * Public configuration accepted by {@link GraphorinClient}.\n *\n * @stable\n */\nexport interface GraphorinClientOptions {\n /**\n * Session bound to the SSE fallback (IP-3): substituted into the\n * `:sessionId` slot of `sseSessionPath`. Required to connect over\n * SSE - the old client sent the literal template and could never\n * receive an event.\n */\n readonly sessionId?: string;\n /**\n * Server base URL. Examples: `'wss://graphorin.example.com'` or\n * `'http://localhost:8080'`. The path `/v1/ws` is appended for\n * the WebSocket transport.\n */\n readonly baseUrl: string;\n readonly auth: TransportAuth;\n readonly transport?: TransportPreference;\n /** Override the WS path (default `'/v1/ws'`). */\n readonly wsPath?: string;\n /**\n * SSE path template. The placeholder `:sessionId` is replaced at\n * subscribe time. Default: `'/v1/sessions/:sessionId/events'`.\n * Required when the transport is `'sse'` or when the WS handshake\n * fails on `'auto'`.\n */\n readonly sseSessionPath?: string;\n readonly reconnect?: BackoffPolicy;\n /** Inject a `WebSocket` constructor (Node SDKs / tests). */\n readonly WebSocket?: typeof WebSocket;\n /** Inject an `EventSource` constructor (Node SDKs / tests). */\n readonly EventSource?: typeof EventSource;\n /** Inject a `fetch` implementation (defaults to `globalThis.fetch`). */\n readonly fetch?: typeof fetch;\n /** Optional client identifier surfaced on diagnostics. */\n readonly clientId?: string;\n /**\n * IP-19: per-RPC reply timeout in milliseconds. When set (and > 0), an RPC\n * that receives no matching reply within this window rejects with a\n * {@link TransportFailedError} instead of hanging forever on a\n * non-responsive server. Default: unset - no timeout, so a legitimately\n * slow server reply is never aborted (opt-in).\n */\n readonly rpcTimeoutMs?: number;\n}\n\n/**\n * Snapshot returned by {@link Subscription.metadata}.\n *\n * @stable\n */\nexport interface SubscriptionMetadata {\n readonly id: string;\n readonly subject: string;\n readonly target: SubscriptionTarget;\n readonly snapshotEventId: string | undefined;\n readonly lastEventId: string | undefined;\n readonly closed: boolean;\n}\n\n/**\n * Public surface returned by {@link GraphorinClient.subscribe}.\n *\n * @stable\n */\nexport interface Subscription {\n readonly subscriptionId: string;\n readonly subject: string;\n events(): AsyncIterable<ServerEventFrame>;\n /**\n * Close the subscription on the server. Idempotent.\n */\n unsubscribe(): Promise<void>;\n metadata(): SubscriptionMetadata;\n}\n\ninterface SubscriptionInternal extends Subscription {\n __push(frame: ServerEventFrame): void;\n __pushLifecycle(frame: ServerLifecycleFrame): void;\n __pushReplayMarker(frame: ServerReplayMarkerFrame): void;\n __close(reason: 'unsubscribed' | 'transport-closed' | 'aborted', error?: Error): void;\n __subject(): string;\n __target(): SubscriptionTarget;\n __snapshotEventId(): string | undefined;\n __lastEventId(): string | undefined;\n /** IP-7: re-point this subscription at a new server subscriptionId. */\n __rebind(newSubscriptionId: string): void;\n}\n\n/**\n * @stable\n */\nexport class GraphorinClient {\n readonly #options: GraphorinClientOptions;\n readonly #pending: Map<ClientMessageId, PendingRpc> = new Map();\n readonly #subscriptions: Map<string, SubscriptionInternal> = new Map();\n /** IP-3: the synthetic single subscription an SSE connection carries. */\n #sseSubscription: SubscriptionInternal | undefined;\n #transport: Transport | undefined;\n #idCounter = 0;\n #closed = false;\n #connectingPromise: Promise<void> | undefined;\n #abortController: AbortController = new AbortController();\n\n constructor(options: GraphorinClientOptions) {\n if (typeof options.baseUrl !== 'string' || options.baseUrl.length === 0) {\n throw new TypeError('GraphorinClient: baseUrl must be a non-empty string.');\n }\n this.#options = options;\n }\n\n /**\n * Open the underlying transport. Resolves once the server has\n * accepted the handshake (`'open'`); rejects with a typed\n * {@link GraphorinClientError} otherwise.\n *\n * Calling `connect()` while already connected is a no-op; calling\n * it during another `connect()` returns the same promise.\n */\n async connect(): Promise<void> {\n if (this.#closed) {\n throw new ClientAbortedError('GraphorinClient was disconnected; create a new instance.');\n }\n if (this.#transport !== undefined) return;\n if (this.#connectingPromise !== undefined) return this.#connectingPromise;\n this.#connectingPromise = (async () => {\n const preference = this.#options.transport ?? 'auto';\n try {\n if (preference === 'ws' || preference === 'auto') {\n try {\n this.#transport = await this.#openWs();\n await this.#initializeRpc();\n return;\n } catch (err) {\n if (preference === 'ws') throw err;\n }\n }\n this.#transport = await this.#openSse();\n } finally {\n this.#connectingPromise = undefined;\n }\n })();\n return this.#connectingPromise;\n }\n\n async #initializeRpc(): Promise<void> {\n if (this.#transport?.kind !== 'ws') return;\n await this.#sendRpc('initialize', {\n clientInfo: { name: 'graphorin-client', version: pkg.version },\n });\n }\n\n /** Send a `ping` RPC and resolve when the server replies with `pong`. */\n async ping(): Promise<void> {\n await this.#sendRpc('ping');\n }\n\n /**\n * Subscribe to a server-side event stream. Resolves with a\n * {@link Subscription} once the server confirms with the matching\n * `subscribed` frame; rejects when the server returns an\n * `error` instead.\n */\n async subscribe(\n target: SubscriptionTarget,\n opts?: { readonly sinceEventId?: string },\n ): Promise<Subscription> {\n if (this.#transport === undefined) throw new ClientNotConnectedError();\n if (this.#transport.kind === 'sse') {\n // IP-3: the SSE connection IS one implicit subscription to the\n // bound session's events. Frames arrive with a server-generated\n // subscriptionId the client cannot know upfront, so routing\n // falls through to this synthetic subscription (see\n // #handleFrame). Other subjects still require the WS transport.\n const subject = subjectFor(target);\n const boundSubject = `session:${this.#options.sessionId ?? ''}/events`;\n if (subject !== boundSubject) {\n throw new TransportFailedError(\n `subscribe('${subject}') requires the WebSocket transport - the SSE fallback carries only the bound session stream ('${boundSubject}').`,\n );\n }\n // periphery-03: a CLOSED bound subscription (a prior terminal\n // reconnect failure) must not be handed back - recreate it so a\n // recovered client delivers events again.\n if (this.#sseSubscription === undefined || this.#sseSubscription.metadata().closed) {\n this.#sseSubscription = this.#createSubscription({\n subscriptionId: '__sse__',\n subject,\n target,\n snapshotEventId: undefined,\n });\n this.#subscriptions.set('__sse__', this.#sseSubscription);\n }\n return this.#sseSubscription;\n }\n const subject = subjectFor(target);\n // IP-7: a resubscribe passes the SUBSCRIPTION's own cursor - the\n // fresh transport's lastEventId is always undefined, so the old\n // code never consulted the server replay buffer.\n const lastEventId = opts?.sinceEventId ?? this.#transport.lastEventId;\n const params: { subject: string; sinceEventId?: string } = {\n subject,\n };\n if (lastEventId !== undefined) params.sinceEventId = lastEventId;\n const reply = await this.#sendRpc('subscription.subscribe', params);\n const result = reply as { subscriptionId?: unknown; snapshotEventId?: unknown };\n const subscriptionId =\n typeof result.subscriptionId === 'string' && result.subscriptionId.length > 0\n ? result.subscriptionId\n : undefined;\n if (subscriptionId === undefined) {\n throw new ProtocolViolationError('Server subscribe reply missing subscriptionId.');\n }\n const snapshotEventId =\n typeof result.snapshotEventId === 'string' ? result.snapshotEventId : undefined;\n const sub = this.#createSubscription({\n subscriptionId,\n subject,\n target,\n snapshotEventId,\n });\n this.#subscriptions.set(subscriptionId, sub);\n return sub;\n }\n\n /**\n * Cancel a server-side run. Sends the `run.cancel` RPC and\n * resolves with the server's `result` payload (typically\n * `{ cancelled: true, partialStateAvailable: true }`).\n */\n async cancel(\n runId: string,\n opts: {\n readonly drain?: boolean;\n readonly reason?: string;\n readonly onPendingApprovals?: 'deny' | 'preserve';\n } = {},\n ): Promise<unknown> {\n if (typeof runId !== 'string' || runId.length === 0) {\n throw new TypeError('cancel: runId must be a non-empty string.');\n }\n const params: {\n runId: string;\n drain?: boolean;\n reason?: string;\n onPendingApprovals?: 'deny' | 'preserve';\n } = { runId };\n if (opts.drain !== undefined) params.drain = opts.drain;\n if (opts.reason !== undefined) params.reason = opts.reason;\n if (opts.onPendingApprovals !== undefined) {\n params.onPendingApprovals = opts.onPendingApprovals;\n }\n return this.#sendRpc('run.cancel', params);\n }\n\n /**\n * Resume a paused (HITL) run. The WebSocket protocol intentionally\n * does NOT carry a `resume` control message - resumes are durable\n * + idempotent + body-carrying, which maps onto the REST endpoint\n * `POST /v1/runs/:runId/resume`. NOTE (IP-14): the server endpoint\n * currently answers **501** - server-side durable resume is not\n * implemented yet. Library-mode callers resume directly:\n * `agent.run(result.state, { directive })`.\n */\n async resume(\n runId: string,\n directive?: unknown,\n opts: { readonly idempotencyKey?: string } = {},\n ): Promise<unknown> {\n if (typeof runId !== 'string' || runId.length === 0) {\n throw new TypeError('resume: runId must be a non-empty string.');\n }\n const fetchImpl = this.#options.fetch ?? globalThis.fetch;\n if (typeof fetchImpl !== 'function') {\n throw new TransportFailedError(\n 'No fetch implementation available; pass `fetch` via the client options.',\n );\n }\n const url = joinUrl(this.#options.baseUrl, `/v1/runs/${encodeURIComponent(runId)}/resume`, {\n ws: false,\n });\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n };\n if (this.#options.auth.kind === 'bearer') {\n headers.Authorization = `Bearer ${this.#options.auth.token}`;\n }\n if (opts.idempotencyKey !== undefined) headers['Idempotency-Key'] = opts.idempotencyKey;\n const res = await fetchImpl(url, {\n method: 'POST',\n headers,\n body: JSON.stringify(directive === undefined ? {} : { directive }),\n });\n if (!res.ok) {\n throw new TransportFailedError(\n `resume(${runId}): server responded ${res.status} ${res.statusText}.`,\n { code: res.status },\n );\n }\n try {\n return await res.json();\n } catch {\n return undefined;\n }\n }\n\n /**\n * Send an MCP-compatible cancellation notification. Does not wait\n * for a server reply (notifications have no `id`).\n */\n cancelNotify(requestId: string): void {\n if (this.#transport === undefined) throw new ClientNotConnectedError();\n if (typeof requestId !== 'string' || requestId.length === 0) {\n throw new TypeError('cancelNotify: requestId must be a non-empty string.');\n }\n const frame: ClientMessage = {\n v: '1',\n jsonrpc: '2.0',\n method: 'notifications/cancelled',\n params: { requestId },\n };\n this.#transport.send(frame);\n }\n\n /**\n * Disconnect the underlying transport and abort every pending RPC\n * + subscription. Idempotent.\n */\n async disconnect(): Promise<void> {\n if (this.#closed) return;\n this.#closed = true;\n const transport = this.#transport;\n this.#transport = undefined;\n this.#abortController.abort(new ClientAbortedError());\n for (const pending of this.#pending.values()) {\n pending.reject(new ClientAbortedError('Client disconnected before reply.'));\n }\n this.#pending.clear();\n for (const sub of this.#subscriptions.values()) {\n sub.__close('aborted', new ClientAbortedError('Client disconnected.'));\n }\n this.#subscriptions.clear();\n if (transport !== undefined) {\n try {\n transport.close(1000, 'client-disconnect');\n } catch {\n // Best-effort close: never throw from disconnect().\n }\n }\n }\n\n /** Return the active transport kind (or `undefined` if not connected). */\n get transportKind(): TransportKind | undefined {\n return this.#transport?.kind;\n }\n\n // -----------------------------------------------------------------\n // Internal helpers\n // -----------------------------------------------------------------\n\n async #openWs(): Promise<Transport> {\n const url = this.#wsUrl();\n const transport = await openWebSocketTransport(\n {\n url,\n auth: this.#options.auth,\n ...(this.#options.WebSocket !== undefined ? { WebSocket: this.#options.WebSocket } : {}),\n ...(this.#options.clientId !== undefined ? { clientId: this.#options.clientId } : {}),\n },\n this.#listeners('ws'),\n );\n return transport;\n }\n\n async #openSse(): Promise<Transport> {\n if (this.#options.auth.kind !== 'bearer') {\n throw new TransportFailedError(\n \"SSE fallback requires the 'bearer' auth strategy. The WebSocket ticket flow does not extend to EventSource.\",\n );\n }\n const url = this.#sseUrl();\n // periphery-03: resume from where the bound session subscription\n // left off - without the cursor every reconnect replays the whole\n // server buffer into the consumer.\n const resumeCursor = this.#sseSubscription?.__lastEventId();\n const transport = await openSseTransport(\n {\n url,\n auth: this.#options.auth,\n // IP-3: the fetch-streaming transport uses the client's fetch\n // seam (the EventSource seam is gone with the rewrite).\n ...(this.#options.fetch !== undefined ? { fetch: this.#options.fetch } : {}),\n ...(this.#options.clientId !== undefined ? { clientId: this.#options.clientId } : {}),\n ...(resumeCursor !== undefined ? { lastEventId: resumeCursor } : {}),\n },\n this.#listeners('sse'),\n );\n return transport;\n }\n\n #wsUrl(): string {\n const path = this.#options.wsPath ?? '/v1/ws';\n return joinUrl(this.#options.baseUrl, path, { ws: true });\n }\n\n #sseUrl(): string {\n const template = this.#options.sseSessionPath ?? '/v1/sessions/:sessionId/events';\n // IP-3: bind the session into the path - the literal template was\n // previously sent as-is, so the SSE fallback could never connect\n // to a real session stream.\n if (template.includes(':sessionId')) {\n const sessionId = this.#options.sessionId;\n if (sessionId === undefined || sessionId.length === 0) {\n throw new TransportFailedError(\n \"The SSE fallback needs a session binding - pass `sessionId` in the client options (it fills ':sessionId' in sseSessionPath).\",\n );\n }\n return joinUrl(\n this.#options.baseUrl,\n template.replace(':sessionId', encodeURIComponent(sessionId)),\n { ws: false },\n );\n }\n return joinUrl(this.#options.baseUrl, template, { ws: false });\n }\n\n #listeners(kind: TransportKind): {\n onOpen(): void;\n onFrame(frame: ServerMessage): void;\n onError(err: Error): void;\n onClose(reason: TransportCloseReason): void;\n } {\n return {\n onOpen: () => {},\n onFrame: (frame) => this.#handleFrame(frame),\n onError: (err) => this.#handleTransportError(err, kind),\n onClose: (reason) => this.#handleTransportClose(reason, kind),\n };\n }\n\n #handleFrame(frame: ServerMessage): void {\n if (isRpcSuccess(frame)) {\n const pending = this.#pending.get(frame.id);\n if (pending !== undefined) {\n this.#pending.delete(frame.id);\n pending.resolve(frame.result);\n }\n return;\n }\n if (isRpcFailure(frame)) {\n const pending = this.#pending.get(frame.id);\n if (pending !== undefined) {\n this.#pending.delete(frame.id);\n // IP-19: surface the server's error class (rate-limited, scope-denied,\n // auth-failed, …) instead of collapsing every RPC failure to\n // 'protocol-violation'.\n pending.reject(\n new GraphorinClientError(\n kindForRpcCode(frame.error.code),\n `Server returned an error for RPC '${frame.id}': ${frame.error.message} (code=${frame.error.code}).`,\n ),\n );\n }\n return;\n }\n if (isSubscribedFrame(frame)) {\n // Subscribed frames are acknowledged by the matching RPC reply.\n return;\n }\n if (isUnsubscribedFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId);\n if (sub !== undefined) {\n sub.__close('unsubscribed');\n this.#subscriptions.delete(frame.subscriptionId);\n }\n return;\n }\n if (isPongFrame(frame)) return;\n if (isLifecycleFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId) ?? this.#sseFallback();\n if (sub !== undefined) sub.__pushLifecycle(frame);\n return;\n }\n if (isReplayMarkerFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId) ?? this.#sseFallback();\n if (sub !== undefined) sub.__pushReplayMarker(frame);\n return;\n }\n if (isEventFrame(frame)) {\n const sub = this.#subscriptions.get(frame.subscriptionId) ?? this.#sseFallback();\n if (sub !== undefined) sub.__push(frame);\n return;\n }\n if (isErrorFrame(frame)) {\n const target =\n frame.subscriptionId !== undefined\n ? this.#subscriptions.get(frame.subscriptionId)\n : undefined;\n const error = new GraphorinClientError(\n 'protocol-violation',\n `Server error frame: ${frame.message} (code=${frame.code}).`,\n );\n if (target !== undefined) {\n target.__close('aborted', error);\n if (frame.subscriptionId !== undefined) this.#subscriptions.delete(frame.subscriptionId);\n }\n }\n }\n\n /**\n * IP-3: on the SSE transport every frame belongs to the single\n * implicit session subscription regardless of the server-generated\n * subscriptionId it carries.\n */\n #sseFallback(): SubscriptionInternal | undefined {\n return this.#transport?.kind === 'sse' ? this.#sseSubscription : undefined;\n }\n\n #handleTransportError(err: Error, _kind: TransportKind): void {\n for (const pending of this.#pending.values()) {\n pending.reject(err);\n }\n this.#pending.clear();\n }\n\n async #handleTransportClose(reason: TransportCloseReason, _kind: TransportKind): Promise<void> {\n if (this.#closed) return;\n this.#transport = undefined;\n const closeError = new TransportFailedError(\n `Transport closed: ${reason.reason || reason.graphorinReason || 'unknown'} (code=${reason.code}).`,\n { code: reason.code },\n );\n // Best-effort drain of pending RPCs so callers see the failure\n // immediately rather than hanging until the reconnect window\n // closes.\n for (const pending of this.#pending.values()) {\n pending.reject(closeError);\n }\n this.#pending.clear();\n if (\n reason.graphorinReason === 'auth.required' ||\n reason.graphorinReason === 'auth.invalid' ||\n reason.graphorinReason === 'auth.revoked' ||\n reason.graphorinReason === 'protocol.violation'\n ) {\n // Fatal: stop trying to reconnect and surface the failure.\n for (const sub of this.#subscriptions.values()) {\n sub.__close('aborted', closeError);\n }\n this.#subscriptions.clear();\n return;\n }\n await this.#reconnect();\n }\n\n async #reconnect(): Promise<void> {\n if (this.#closed) return;\n let attempt = 0;\n while (!this.#closed) {\n attempt += 1;\n const delay = computeBackoffMs(attempt, this.#options.reconnect);\n if (delay === null) {\n const exhausted = new TransportFailedError(\n `Reconnect attempts exhausted after ${attempt - 1} retries.`,\n );\n for (const sub of this.#subscriptions.values()) {\n sub.__close('aborted', exhausted);\n }\n this.#subscriptions.clear();\n return;\n }\n try {\n await sleep(delay, this.#abortController.signal);\n await this.connect();\n // Best-effort resubscribe with the recorded lastEventId so\n // the server replays missed events from the buffer.\n const transport = this.#transport;\n if (transport === undefined) continue;\n // periphery-03: on the SSE fallback the connection IS the\n // subscription - the reconnect already carried the resume\n // cursor as `Last-Event-ID`, and frames keep flowing into the\n // same bound subscription object. The RPC resubscribe below\n // would call `transport.send(...)`, which the read-only SSE\n // transport rejects; pre-fix that error CLOSED the\n // subscription and the client silently stopped delivering\n // events for the life of the object.\n if (transport.kind === 'sse') return;\n for (const [oldId, sub] of [...this.#subscriptions]) {\n this.#subscriptions.delete(oldId);\n try {\n // IP-7: re-establish the server-side subscription with the\n // SUBSCRIPTION's own replay cursor, then re-point the SAME\n // object at the new server id - the consumer's in-flight\n // `for await` survives and the replayed events arrive in\n // the iterator it is already reading. The old code created\n // a NEW subscription and closed this one: the consumer's\n // loop ended `{done: true}` while events piled up unread\n // in an orphan.\n const cursor = sub.__lastEventId();\n const reply = await this.#sendRpc('subscription.subscribe', {\n subject: sub.__subject(),\n ...(cursor !== undefined ? { sinceEventId: cursor } : {}),\n });\n const result = reply as { subscriptionId?: unknown };\n const newId =\n typeof result.subscriptionId === 'string' && result.subscriptionId.length > 0\n ? result.subscriptionId\n : undefined;\n if (newId === undefined) {\n throw new ProtocolViolationError('Server resubscribe reply missing subscriptionId.');\n }\n sub.__rebind(newId);\n this.#subscriptions.set(newId, sub);\n } catch (err) {\n sub.__close('aborted', err instanceof Error ? err : new Error(String(err)));\n }\n }\n return;\n } catch (err) {\n if (this.#closed) return;\n // Loop and retry.\n if (err instanceof ClientAbortedError) return;\n }\n }\n }\n\n #createSubscription(args: {\n subscriptionId: string;\n readonly subject: string;\n readonly target: SubscriptionTarget;\n readonly snapshotEventId: string | undefined;\n }): SubscriptionInternal {\n const queue: ServerEventFrame[] = [];\n const lifecycle: ServerLifecycleFrame[] = [];\n const replayMarkers: ServerReplayMarkerFrame[] = [];\n const waiters: Array<{\n resolve: (value: IteratorResult<ServerEventFrame>) => void;\n reject: (err: Error) => void;\n }> = [];\n let closed = false;\n let closeError: Error | undefined;\n let lastEventId: string | undefined = args.snapshotEventId;\n\n const push = (frame: ServerEventFrame): void => {\n lastEventId = frame.eventId;\n const waiter = waiters.shift();\n if (waiter !== undefined) {\n waiter.resolve({ value: frame, done: false });\n return;\n }\n queue.push(frame);\n };\n\n const close = (reason: 'unsubscribed' | 'transport-closed' | 'aborted', err?: Error): void => {\n if (closed) return;\n closed = true;\n if (err !== undefined) closeError = err;\n else if (reason === 'aborted') closeError = new ClientAbortedError();\n while (waiters.length > 0) {\n const waiter = waiters.shift();\n if (waiter === undefined) continue;\n // IP-19(c): a waiter blocked in next() must observe the SAME outcome a\n // fresh next() would after close - reject with closeError when the\n // stream tore down abnormally, rather than silently resolving done:true\n // and swallowing the transport/abort failure.\n if (closeError !== undefined) {\n waiter.reject(closeError);\n } else {\n waiter.resolve({ value: undefined as unknown as ServerEventFrame, done: true });\n }\n }\n };\n\n const sub: SubscriptionInternal = {\n get subscriptionId(): string {\n return args.subscriptionId;\n },\n subject: args.subject,\n events: () => ({\n [Symbol.asyncIterator]: () => ({\n next: (): Promise<IteratorResult<ServerEventFrame>> => {\n const buffered = queue.shift();\n if (buffered !== undefined) {\n return Promise.resolve({ value: buffered, done: false });\n }\n if (closed) {\n if (closeError !== undefined) return Promise.reject(closeError);\n return Promise.resolve({\n value: undefined as unknown as ServerEventFrame,\n done: true,\n });\n }\n return new Promise<IteratorResult<ServerEventFrame>>((resolve, reject) => {\n waiters.push({ resolve, reject });\n });\n },\n return: (): Promise<IteratorResult<ServerEventFrame>> => {\n close('unsubscribed');\n return Promise.resolve({ value: undefined as unknown as ServerEventFrame, done: true });\n },\n }),\n }),\n unsubscribe: async () => {\n if (closed) return;\n try {\n await this.#sendRpc('subscription.unsubscribe', {\n subscriptionId: args.subscriptionId,\n });\n } catch (err) {\n if (!(err instanceof ClientAbortedError)) throw err;\n }\n close('unsubscribed');\n this.#subscriptions.delete(args.subscriptionId);\n },\n metadata: () => ({\n id: args.subscriptionId,\n subject: args.subject,\n target: args.target,\n snapshotEventId: args.snapshotEventId,\n lastEventId,\n closed,\n }),\n __push: push,\n __pushLifecycle: (frame) => {\n lifecycle.push(frame);\n if (\n frame.status === 'completed' ||\n frame.status === 'aborted' ||\n frame.status === 'failed'\n ) {\n close(frame.status === 'completed' ? 'unsubscribed' : 'aborted');\n this.#subscriptions.delete(args.subscriptionId);\n }\n },\n __pushReplayMarker: (frame) => {\n replayMarkers.push(frame);\n },\n __close: close,\n __subject: () => args.subject,\n __target: () => args.target,\n __snapshotEventId: () => args.snapshotEventId,\n __lastEventId: () => lastEventId,\n __rebind: (newSubscriptionId: string) => {\n args.subscriptionId = newSubscriptionId;\n },\n };\n return sub;\n }\n\n async #sendRpc(method: ClientMessage['method'], params?: object): Promise<unknown> {\n if (this.#transport === undefined) throw new ClientNotConnectedError();\n const id = this.#nextId();\n const frame = buildRpcFrame(method, id, params);\n const timeoutMs = this.#options.rpcTimeoutMs;\n return new Promise((resolve, reject) => {\n // IP-19: arm an optional reply timer. Wrapping resolve/reject so that\n // EVERY settle path (matching reply, disconnect, transport error) clears\n // the timer - otherwise a stray timer could reject an already-settled id.\n let timer: ReturnType<typeof setTimeout> | undefined;\n const clear = (): void => {\n if (timer !== undefined) {\n clearTimeout(timer);\n timer = undefined;\n }\n };\n const pending: PendingRpc = {\n resolve: (value) => {\n clear();\n resolve(value);\n },\n reject: (reason) => {\n clear();\n reject(reason);\n },\n };\n this.#pending.set(id, pending);\n if (timeoutMs !== undefined && timeoutMs > 0) {\n timer = setTimeout(() => {\n // Fire only if this exact request is still outstanding.\n if (this.#pending.get(id) === pending) {\n this.#pending.delete(id);\n reject(\n new TransportFailedError(\n `RPC '${method}' (id=${id}) timed out after ${timeoutMs}ms with no server reply.`,\n ),\n );\n }\n }, timeoutMs);\n }\n try {\n // Asserting non-null because we just verified above.\n (this.#transport as Transport).send(frame);\n } catch (err) {\n clear();\n this.#pending.delete(id);\n reject(err);\n }\n });\n }\n\n #nextId(): string {\n this.#idCounter += 1;\n const prefix = this.#options.clientId ?? 'graphorin';\n return `${prefix}-${this.#idCounter.toString(36)}`;\n }\n}\n\ninterface PendingRpc {\n readonly resolve: (value: unknown) => void;\n readonly reject: (reason: Error) => void;\n}\n\nfunction subjectFor(target: SubscriptionTarget): string {\n switch (target.target) {\n case 'session':\n assertNonEmpty(target.id, 'session.id');\n return `session:${target.id}/events`;\n case 'agent':\n assertNonEmpty(target.id, 'agent.id');\n assertNonEmpty(target.runId, 'agent.runId');\n return `agent:${target.id}/runs/${target.runId}/events`;\n case 'run':\n assertNonEmpty(target.runId, 'run.runId');\n if (target.sessionId !== undefined) {\n assertNonEmpty(target.sessionId, 'run.sessionId');\n return `session:${target.sessionId}/runs/${target.runId}/events`;\n }\n // periphery-09: the server subject grammar has NO bare `run:`\n // form - the old `run:<id>/events` fallback always failed\n // server-side with an opaque unknown-subject error. Fail fast\n // with an actionable message instead.\n throw new TypeError(\n \"subscribe({ target: 'run' }) requires `sessionId` - the server subject grammar has no bare run form. \" +\n \"Pass { kind: 'run', runId, sessionId } or use the 'agent' target with the agent id.\",\n );\n case 'workflow':\n assertNonEmpty(target.id, 'workflow.id');\n return `workflow:${target.id}/events`;\n }\n}\n\nfunction assertNonEmpty(value: string, fieldName: string): void {\n if (typeof value !== 'string' || value.length === 0) {\n throw new TypeError(`${fieldName} must be a non-empty string.`);\n }\n}\n\nfunction joinUrl(baseUrl: string, path: string, opts: { readonly ws: boolean }): string {\n let normalized = baseUrl;\n if (opts.ws) {\n if (normalized.startsWith('http://')) normalized = normalized.replace(/^http:\\/\\//, 'ws://');\n else if (normalized.startsWith('https://'))\n normalized = normalized.replace(/^https:\\/\\//, 'wss://');\n } else {\n if (normalized.startsWith('ws://')) normalized = normalized.replace(/^ws:\\/\\//, 'http://');\n else if (normalized.startsWith('wss://'))\n normalized = normalized.replace(/^wss:\\/\\//, 'https://');\n }\n if (normalized.endsWith('/')) normalized = normalized.slice(0, -1);\n const tail = path.startsWith('/') ? path : `/${path}`;\n return `${normalized}${tail}`;\n}\n\nfunction buildRpcFrame(\n method: ClientMessage['method'],\n id: ClientMessageId,\n params?: object,\n): ClientMessage {\n switch (method) {\n case 'initialize':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'initialize',\n params: (params ?? {\n clientInfo: { name: 'graphorin-client', version: pkg.version },\n }) as never,\n };\n case 'subscription.subscribe':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'subscription.subscribe',\n params: (params ?? { subject: '' }) as never,\n };\n case 'subscription.unsubscribe':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'subscription.unsubscribe',\n params: (params ?? { subscriptionId: '' }) as never,\n };\n case 'run.cancel':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'run.cancel',\n params: (params ?? { runId: '' }) as never,\n };\n case 'ping':\n return {\n v: '1',\n jsonrpc: '2.0',\n id,\n method: 'ping',\n ...(params !== undefined ? { params: params as never } : {}),\n };\n case 'notifications/cancelled':\n throw new TypeError(\n \"buildRpcFrame: 'notifications/cancelled' is a notification, not an RPC.\",\n );\n }\n // Exhaustiveness check; should be unreachable.\n throw new TypeError(`Unknown RPC method '${method}'.`);\n}\n"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -27,8 +27,7 @@ import { GraphorinClient, GraphorinClientOptions, Subscription, SubscriptionMeta
|
|
|
27
27
|
*
|
|
28
28
|
* @packageDocumentation
|
|
29
29
|
*/
|
|
30
|
-
|
|
31
|
-
declare const VERSION = "0.6.0";
|
|
30
|
+
declare const VERSION: string;
|
|
32
31
|
//#endregion
|
|
33
32
|
export { AuthFailedError, type BackoffPolicy, ClientAbortedError, ClientNotConnectedError, GraphorinClient, GraphorinClientError, GraphorinClientErrorKind, type GraphorinClientOptions, InvalidServerFrameError, ProtocolViolationError, SubprotocolMismatchError, type Subscription, type SubscriptionMetadata, SubscriptionNotFoundError, type SubscriptionTarget, type Transport, type TransportAuth, type TransportCloseReason, TransportFailedError, type TransportKind, type TransportListeners, type TransportOptions, type TransportPreference, VERSION, computeBackoffMs, kindForRpcCode, openSseTransport, openWebSocketTransport, sleep };
|
|
34
33
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;;;;;;;;;;AAuBA;;;;;;;;;;cAAa"}
|
package/dist/index.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { version } from "./package.js";
|
|
1
2
|
import { AuthFailedError, ClientAbortedError, ClientNotConnectedError, GraphorinClientError, InvalidServerFrameError, ProtocolViolationError, SubprotocolMismatchError, SubscriptionNotFoundError, TransportFailedError, kindForRpcCode } from "./errors.js";
|
|
2
3
|
import { computeBackoffMs, sleep } from "./reconnect.js";
|
|
3
4
|
import { openSseTransport } from "./transport/sse.js";
|
|
@@ -24,8 +25,8 @@ import { GraphorinClient } from "./graphorin-client.js";
|
|
|
24
25
|
*
|
|
25
26
|
* @packageDocumentation
|
|
26
27
|
*/
|
|
27
|
-
/** Canonical version constant
|
|
28
|
-
const VERSION =
|
|
28
|
+
/** Canonical version constant, derived from `package.json` at build time. */
|
|
29
|
+
const VERSION = version;
|
|
29
30
|
|
|
30
31
|
//#endregion
|
|
31
32
|
export { AuthFailedError, ClientAbortedError, ClientNotConnectedError, GraphorinClient, GraphorinClientError, InvalidServerFrameError, ProtocolViolationError, SubprotocolMismatchError, SubscriptionNotFoundError, TransportFailedError, VERSION, computeBackoffMs, kindForRpcCode, openSseTransport, openWebSocketTransport, sleep };
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphorin/client` - reference TypeScript client for the\n * Graphorin standalone server.\n *\n * Wraps the WebSocket subprotocol `graphorin.protocol.v1` (with an\n * optional Server-Sent Events fallback for proxy-restricted\n * environments) behind an ergonomic {@link GraphorinClient} class:\n * `connect()`, `subscribe({ target, id })` returning an\n * async-iterable subscription, `cancel(runId, opts)`,\n * `disconnect()`. Handles the browser ticket flow, exponential-\n * backoff reconnect with `lastEventId` resume against the server\n * replay buffer, and Zod-validated frame parsing on both\n * directions.\n *\n * Browser-friendly: zero Node-only dependencies; the only runtime\n * dependencies are `@graphorin/protocol` and `zod`.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant
|
|
1
|
+
{"version":3,"file":"index.js","names":["VERSION: string","pkg.version"],"sources":["../src/index.ts"],"sourcesContent":["/**\n * `@graphorin/client` - reference TypeScript client for the\n * Graphorin standalone server.\n *\n * Wraps the WebSocket subprotocol `graphorin.protocol.v1` (with an\n * optional Server-Sent Events fallback for proxy-restricted\n * environments) behind an ergonomic {@link GraphorinClient} class:\n * `connect()`, `subscribe({ target, id })` returning an\n * async-iterable subscription, `cancel(runId, opts)`,\n * `disconnect()`. Handles the browser ticket flow, exponential-\n * backoff reconnect with `lastEventId` resume against the server\n * replay buffer, and Zod-validated frame parsing on both\n * directions.\n *\n * Browser-friendly: zero Node-only dependencies; the only runtime\n * dependencies are `@graphorin/protocol` and `zod`.\n *\n * @packageDocumentation\n */\n\n/** Canonical version constant, derived from `package.json` at build time. */\nimport pkg from '../package.json' with { type: 'json' };\n\nexport const VERSION: string = pkg.version;\n\nexport * from './errors.js';\nexport {\n GraphorinClient,\n type GraphorinClientOptions,\n type Subscription,\n type SubscriptionMetadata,\n type SubscriptionTarget,\n type TransportPreference,\n} from './graphorin-client.js';\nexport {\n type BackoffPolicy,\n computeBackoffMs,\n sleep,\n} from './reconnect.js';\nexport {\n openSseTransport,\n openWebSocketTransport,\n type Transport,\n type TransportAuth,\n type TransportCloseReason,\n type TransportKind,\n type TransportListeners,\n type TransportOptions,\n} from './transport/index.js';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuBA,MAAaA,UAAkBC"}
|
package/dist/package.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.js","names":[],"sources":["../package.json"],"sourcesContent":["{\n \"name\": \"@graphorin/client\",\n \"version\": \"0.6.1\",\n \"description\": \"Reference TypeScript client for the Graphorin standalone server. Wraps the WebSocket subprotocol `graphorin.protocol.v1` (with the optional Server-Sent Events fallback for proxy-restricted environments) behind an ergonomic `GraphorinClient` class: `connect()`, `subscribe({ target, id })` returning an async-iterable subscription, `cancel(runId, opts)`, `resume(runId, directive)`, `ping()`, `disconnect()`. Handles the browser ticket flow (single-use ticket attached as a `ticket.<value>` `Sec-WebSocket-Protocol` token), exponential-backoff reconnect with `lastEventId` resume against the server replay buffer, and Zod-validated frame parsing on both directions. Browser-friendly: zero Node-only dependencies; the only runtime dependencies are `@graphorin/protocol` and `zod`. Created and maintained by Oleksiy Stepurenko.\",\n \"license\": \"MIT\",\n \"author\": \"Oleksiy Stepurenko\",\n \"homepage\": \"https://github.com/o-stepper/graphorin/tree/main/packages/client\",\n \"repository\": {\n \"type\": \"git\",\n \"url\": \"git+https://github.com/o-stepper/graphorin.git\",\n \"directory\": \"packages/client\"\n },\n \"bugs\": {\n \"url\": \"https://github.com/o-stepper/graphorin/issues\"\n },\n \"keywords\": [\n \"graphorin\",\n \"ai\",\n \"agents\",\n \"framework\",\n \"client\",\n \"websocket\",\n \"sse\",\n \"browser-friendly\",\n \"ticket-flow\",\n \"reconnect\"\n ],\n \"type\": \"module\",\n \"engines\": {\n \"node\": \">=22.0.0\"\n },\n \"main\": \"./dist/index.js\",\n \"module\": \"./dist/index.js\",\n \"types\": \"./dist/index.d.ts\",\n \"sideEffects\": false,\n \"exports\": {\n \".\": {\n \"types\": \"./dist/index.d.ts\",\n \"import\": \"./dist/index.js\"\n },\n \"./client\": {\n \"types\": \"./dist/graphorin-client.d.ts\",\n \"import\": \"./dist/graphorin-client.js\"\n },\n \"./transport\": {\n \"types\": \"./dist/transport/index.d.ts\",\n \"import\": \"./dist/transport/index.js\"\n },\n \"./reconnect\": {\n \"types\": \"./dist/reconnect.d.ts\",\n \"import\": \"./dist/reconnect.js\"\n },\n \"./errors\": {\n \"types\": \"./dist/errors.d.ts\",\n \"import\": \"./dist/errors.js\"\n },\n \"./package.json\": \"./package.json\"\n },\n \"files\": [\n \"dist\",\n \"README.md\",\n \"CHANGELOG.md\",\n \"LICENSE\"\n ],\n \"scripts\": {\n \"build\": \"tsdown\",\n \"typecheck\": \"tsc --noEmit && tsc -p tsconfig.tests.json\",\n \"test\": \"vitest run\",\n \"lint\": \"biome check .\",\n \"clean\": \"rimraf dist .turbo *.tsbuildinfo\"\n },\n \"dependencies\": {\n \"@graphorin/protocol\": \"workspace:*\",\n \"zod\": \"^3.25.0\"\n },\n \"publishConfig\": {\n \"access\": \"public\",\n \"provenance\": true\n },\n \"devDependencies\": {\n \"@graphorin/security\": \"workspace:*\",\n \"@graphorin/server\": \"workspace:*\",\n \"@graphorin/store-sqlite\": \"workspace:*\",\n \"@types/ws\": \"^8.18.1\",\n \"ws\": \"^8.20.1\"\n }\n}\n"],"mappings":";cAEa"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@graphorin/client",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.1",
|
|
4
4
|
"description": "Reference TypeScript client for the Graphorin standalone server. Wraps the WebSocket subprotocol `graphorin.protocol.v1` (with the optional Server-Sent Events fallback for proxy-restricted environments) behind an ergonomic `GraphorinClient` class: `connect()`, `subscribe({ target, id })` returning an async-iterable subscription, `cancel(runId, opts)`, `resume(runId, directive)`, `ping()`, `disconnect()`. Handles the browser ticket flow (single-use ticket attached as a `ticket.<value>` `Sec-WebSocket-Protocol` token), exponential-backoff reconnect with `lastEventId` resume against the server replay buffer, and Zod-validated frame parsing on both directions. Browser-friendly: zero Node-only dependencies; the only runtime dependencies are `@graphorin/protocol` and `zod`. Created and maintained by Oleksiy Stepurenko.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Oleksiy Stepurenko",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
],
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"zod": "^3.25.0",
|
|
67
|
-
"@graphorin/protocol": "0.6.
|
|
67
|
+
"@graphorin/protocol": "0.6.1"
|
|
68
68
|
},
|
|
69
69
|
"publishConfig": {
|
|
70
70
|
"access": "public",
|
|
@@ -73,9 +73,9 @@
|
|
|
73
73
|
"devDependencies": {
|
|
74
74
|
"@types/ws": "^8.18.1",
|
|
75
75
|
"ws": "^8.20.1",
|
|
76
|
-
"@graphorin/security": "0.6.
|
|
77
|
-
"@graphorin/server": "0.6.
|
|
78
|
-
"@graphorin/store-sqlite": "0.6.
|
|
76
|
+
"@graphorin/security": "0.6.1",
|
|
77
|
+
"@graphorin/server": "0.6.1",
|
|
78
|
+
"@graphorin/store-sqlite": "0.6.1"
|
|
79
79
|
},
|
|
80
80
|
"scripts": {
|
|
81
81
|
"build": "tsdown",
|