@proj-airi/server-sdk 0.12.0-beta.4 → 0.12.0-beta.6

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/README.md CHANGED
@@ -38,6 +38,23 @@ Useful runtime helpers:
38
38
  - `client.isReady` tells you whether the client has completed authentication + announce
39
39
  - `client.send()` returns `false` instead of silently dropping messages when the socket is unavailable
40
40
  - `client.sendOrThrow()` is available when you want strict delivery semantics
41
+
42
+ ### Read chat images and recordings
43
+
44
+ Chat events do not carry image or audio bytes. `output:gen-ai:chat:message` lists the images and recordings of the user message in `gen-ai:chat.attachments`, each with a reference such as `airi-asset:<id>`. Read the bytes with `getAsset`:
45
+
46
+ ```typescript
47
+ client.onEvent('output:gen-ai:chat:message', async (event) => {
48
+ for (const attachment of event.data['gen-ai:chat']?.attachments ?? []) {
49
+ const { mimeType, data } = await client.getAsset(attachment.ref)
50
+ console.info(attachment.type, mimeType, data.byteLength, attachment.transcript)
51
+ }
52
+ })
53
+ ```
54
+
55
+ The stage answers the request. `getAsset` rejects when the asset is missing, when it is larger than 50 MB, or when no stage answers within 10 seconds.
56
+
57
+ The stage answers only a client that announced a module. The server tells the stage which connection asked, and the answer goes back to that connection only.
41
58
  - `client.onEvent()` returns an unsubscribe function
42
59
 
43
60
  ## License
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { ClientConnector, ClientConnector as ClientConnector$1, ClientEvents } from "@proj-airi/better-ws";
2
- import { ContextUpdateStrategy, ExtensionIdentity, ExtensionModuleIdentity, MessageHeartbeat, ModuleConfigSchema, ModuleDependency, ModulePermissionDeclaration, ProtocolEvents, WebSocketBaseEvent, WebSocketEvent, WebSocketEventOptionalSource, WebSocketEventSource, WebSocketEvents } from "@proj-airi/server-shared/types";
2
+ import { ASSET_REF_PREFIX, ContextUpdateStrategy, ExtensionIdentity, ExtensionModuleIdentity, MessageHeartbeat, ModuleConfigSchema, ModuleDependency, ModulePermissionDeclaration, ProtocolEvents, WebSocketBaseEvent, WebSocketEvent, WebSocketEventOptionalSource, WebSocketEventSource, WebSocketEvents } from "@proj-airi/server-shared/types";
3
3
  export type * from "@proj-airi/server-shared/types";
4
4
  //#region src/client.d.ts
5
5
  type ClientStatus = 'idle' | 'connecting' | 'authenticating' | 'announcing' | 'ready' | 'reconnecting' | 'closing' | 'closed' | 'failed';
@@ -67,6 +67,18 @@ declare class Client<C = undefined> {
67
67
  offEvent<E extends keyof WebSocketEvents<C>>(event: E, callback?: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>): void;
68
68
  send(data: WebSocketEventOptionalSource<C>): boolean;
69
69
  sendOrThrow(data: WebSocketEventOptionalSource<C>): void;
70
+ /**
71
+ * Reads the bytes of an image or recording that a chat message references as `airi-asset:<id>`.
72
+ *
73
+ * The stage answers the request. The call rejects with the stage error, when the client is not connected, or when
74
+ * no answer arrives within `timeoutMs`.
75
+ */
76
+ getAsset(ref: string, options?: {
77
+ timeoutMs?: number;
78
+ }): Promise<{
79
+ mimeType: string;
80
+ data: Uint8Array;
81
+ }>;
70
82
  close(code?: number, reason?: string): void;
71
83
  private createReconnectOptions;
72
84
  private createHeartbeatOptions;
@@ -173,5 +185,5 @@ declare class WebSocketExtensionPeer<C = undefined> {
173
185
  /** Creates a websocket extension peer over a server-sdk protocol client. */
174
186
  declare function createWebSocketExtensionPeer<C = undefined>(options: WebSocketExtensionPeerOptions<C>): WebSocketExtensionPeer<C>;
175
187
  //#endregion
176
- export { AnnounceExtensionModuleInput, Client, type ClientConnector, type ClientEvents, ClientHeartbeatOptions, ClientOptions, ClientStateChangeContext, ClientStatus, ConnectOptions, ContextUpdateStrategy, ExtensionPeerClient, InvalidMessageError, InvalidMessageErrorOptions, WebSocketEventSource, WebSocketExtensionPeer, WebSocketExtensionPeerOptions, createClient, createTextProtocolConnector, createWebSocketExtensionPeer, parseEvent, stringifyEvent };
188
+ export { ASSET_REF_PREFIX, AnnounceExtensionModuleInput, Client, type ClientConnector, type ClientEvents, ClientHeartbeatOptions, ClientOptions, ClientStateChangeContext, ClientStatus, ConnectOptions, ContextUpdateStrategy, ExtensionPeerClient, InvalidMessageError, InvalidMessageErrorOptions, WebSocketEventSource, WebSocketExtensionPeer, WebSocketExtensionPeerOptions, createClient, createTextProtocolConnector, createWebSocketExtensionPeer, parseEvent, stringifyEvent };
177
189
  //# sourceMappingURL=index.d.mts.map
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import { errorMessageFrom } from "@moeru/std";
2
+ import { decodeBase64 } from "@moeru/std/base64";
2
3
  import { createClient as createClient$1 } from "@proj-airi/better-ws";
3
4
  import { createCrossWsConnector } from "@proj-airi/better-ws/client/crossws";
4
5
  import { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from "@proj-airi/server-shared";
5
- import { ContextUpdateStrategy, MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from "@proj-airi/server-shared/types";
6
+ import { ASSET_REF_PREFIX, ContextUpdateStrategy, MessageHeartbeat, MessageHeartbeatKind, WebSocketEventSource } from "@proj-airi/server-shared/types";
6
7
  import { parse, stringify } from "superjson";
7
8
  import { check, objectWithRest, pipe, safeParse, string, unknown } from "valibot";
8
9
  //#region src/codec.ts
@@ -223,6 +224,36 @@ var Client = class {
223
224
  sendOrThrow(data) {
224
225
  if (!this.send(data)) throw new Error(`Client is not connected, current status: ${this.status}`);
225
226
  }
227
+ /**
228
+ * Reads the bytes of an image or recording that a chat message references as `airi-asset:<id>`.
229
+ *
230
+ * The stage answers the request. The call rejects with the stage error, when the client is not connected, or when
231
+ * no answer arrives within `timeoutMs`.
232
+ */
233
+ async getAsset(ref, options = {}) {
234
+ const id = createEventId();
235
+ const { promise, resolve, reject } = Promise.withResolvers();
236
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`The stage did not answer for asset ${ref}`)), options.timeoutMs ?? 1e4);
237
+ const stop = this.onEvent("asset:get:response", (event) => {
238
+ if (event.metadata.event.parentId === id) resolve(event.data);
239
+ });
240
+ try {
241
+ this.sendOrThrow({
242
+ type: "asset:get:request",
243
+ data: { ref },
244
+ metadata: { event: { id } }
245
+ });
246
+ const answer = await promise;
247
+ if ("error" in answer) throw new Error(answer.error);
248
+ return {
249
+ mimeType: answer.mimeType,
250
+ data: decodeBase64(answer.data)
251
+ };
252
+ } finally {
253
+ clearTimeout(timer);
254
+ stop();
255
+ }
256
+ }
226
257
  close(code, reason) {
227
258
  this.transport.close(code, reason);
228
259
  }
@@ -525,6 +556,6 @@ function createWebSocketExtensionPeer(options) {
525
556
  return new WebSocketExtensionPeer(options);
526
557
  }
527
558
  //#endregion
528
- export { Client, ContextUpdateStrategy, InvalidMessageError, WebSocketEventSource, WebSocketExtensionPeer, createClient, createTextProtocolConnector, createWebSocketExtensionPeer, parseEvent, stringifyEvent };
559
+ export { ASSET_REF_PREFIX, Client, ContextUpdateStrategy, InvalidMessageError, WebSocketEventSource, WebSocketExtensionPeer, createClient, createTextProtocolConnector, createWebSocketExtensionPeer, parseEvent, stringifyEvent };
529
560
 
530
561
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":["createBetterWsClient"],"sources":["../src/codec.ts","../src/client.ts","../src/extension-peer.ts"],"sourcesContent":["import type { WebSocketBaseEvent, WebSocketEvent } from '@proj-airi/server-shared/types'\n\nimport { parse, stringify } from 'superjson'\nimport { check, objectWithRest, pipe, safeParse, string, unknown } from 'valibot'\n\nconst invalidMessage = 'Invalid AIRI websocket message.'\n\nconst eventDataSchema = pipe(\n unknown(),\n check(\n value => Boolean(value) && typeof value === 'object' && !Array.isArray(value),\n 'Expected event data to be a non-array object.',\n ),\n)\n\nconst eventEnvelopeSchema = objectWithRest({\n type: string(),\n data: eventDataSchema,\n}, unknown())\n\n/** Options for websocket message validation failures. */\nexport interface InvalidMessageErrorOptions {\n /** Original parser or validator failure that made the websocket message unusable. */\n cause?: unknown\n /** Parsed candidate event when available; otherwise the original websocket text. */\n source?: unknown\n}\n\n/** Error thrown when websocket text cannot be parsed as an AIRI event envelope. */\nexport class InvalidMessageError extends Error {\n readonly source?: unknown\n\n constructor(options: InvalidMessageErrorOptions = {}) {\n super(invalidMessage, { cause: options.cause })\n this.name = 'InvalidMessageError'\n this.source = options.source\n }\n}\n\n/** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */\nexport function parseEvent<C = undefined>(text: string): WebSocketEvent<C> {\n let superJsonParsed: WebSocketEvent<C> | undefined\n let superJsonError: unknown\n\n try {\n superJsonParsed = parse<WebSocketEvent<C>>(text)\n }\n catch (error) {\n superJsonError = error\n }\n\n const potentialEvent = superJsonParsed && typeof superJsonParsed === 'object' && 'type' in superJsonParsed\n ? superJsonParsed\n : parsePlainJson(text, superJsonError)\n\n const result = safeParse(eventEnvelopeSchema, potentialEvent)\n if (!result.success) {\n throw new InvalidMessageError({ cause: result.issues, source: potentialEvent })\n }\n\n return potentialEvent as WebSocketEvent<C>\n}\n\n/** Serializes one AIRI websocket protocol event with SuperJSON. */\nexport function stringifyEvent<C = undefined>(\n event: WebSocketBaseEvent<string, unknown> | WebSocketEvent<C>,\n) {\n return stringify(event)\n}\n\nfunction parsePlainJson(text: string, superJsonError: unknown): unknown {\n try {\n return JSON.parse(text)\n }\n catch (jsonError) {\n throw new InvalidMessageError({\n cause: superJsonError ?? jsonError,\n source: text,\n })\n }\n}\n","import type {\n Client as BetterWsClient,\n ClientConnector,\n PrepareContext,\n ReconnectOptions,\n} from '@proj-airi/better-ws'\nimport type {\n ExtensionIdentity,\n ExtensionModuleIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n WebSocketBaseEvent,\n WebSocketEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport { errorMessageFrom } from '@moeru/std'\nimport { createClient as createBetterWsClient } from '@proj-airi/better-ws'\nimport { createCrossWsConnector } from '@proj-airi/better-ws/client/crossws'\nimport { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from '@proj-airi/server-shared'\nimport { MessageHeartbeat, MessageHeartbeatKind } from '@proj-airi/server-shared/types'\n\nimport { parseEvent, stringifyEvent } from './codec'\n\nexport type { ClientConnector, ClientEvents } from '@proj-airi/better-ws'\n\nexport type ClientStatus\n = | 'idle'\n | 'connecting'\n | 'authenticating'\n | 'announcing'\n | 'ready'\n | 'reconnecting'\n | 'closing'\n | 'closed'\n | 'failed'\n\nexport interface ClientHeartbeatOptions {\n pingInterval?: number\n readTimeout?: number\n message?: MessageHeartbeat | string\n}\n\nexport interface ClientStateChangeContext {\n previousStatus: ClientStatus\n status: ClientStatus\n}\n\nexport interface ConnectOptions {\n abortSignal?: AbortSignal\n timeout?: number\n}\n\nexport interface ClientOptions<C = undefined> {\n url?: string\n name: string\n token?: string\n connector?: ClientConnector<WebSocketEvent<C>>\n /**\n * Selects the connection handshake owned by this client.\n *\n * @default 'module'\n */\n handshake?: 'module' | 'manual'\n\n connectTimeoutMs?: number\n possibleEvents?: Array<keyof WebSocketEvents<C>>\n extension?: ExtensionIdentity\n identity?: ExtensionModuleIdentity\n dependencies?: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat?: false | ClientHeartbeatOptions\n\n autoConnect?: boolean\n autoReconnect?: boolean\n maxReconnectAttempts?: number\n\n onError?: (error: unknown) => void\n onClose?: () => void\n onReady?: () => void\n onStateChange?: (context: ClientStateChangeContext) => void\n\n onAnyMessage?: (data: WebSocketEvent<C>) => void\n onAnySend?: (data: WebSocketEvent<C>) => void\n}\n\ninterface NormalizedClientOptions<C> {\n url: string\n name: string\n token?: string\n connector: ClientConnector<WebSocketEvent<C>>\n handshake: 'module' | 'manual'\n connectTimeoutMs: number\n possibleEvents: Array<keyof WebSocketEvents<C>>\n extension: ExtensionIdentity\n identity: ExtensionModuleIdentity\n dependencies: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat: false | Required<ClientHeartbeatOptions>\n autoConnect: boolean\n autoReconnect: boolean\n maxReconnectAttempts: number\n onError: (error: unknown) => void\n onClose: () => void\n onReady: () => void\n onStateChange: (context: ClientStateChangeContext) => void\n onAnyMessage: (data: WebSocketEvent<C>) => void\n onAnySend: (data: WebSocketEvent<C>) => void\n}\n\ninterface ProtocolWaitResult {\n ready: boolean\n error?: Error\n}\n\nfunction createInstanceId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction createEventId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction normalizeHeartbeatOptions(heartbeat?: false | ClientHeartbeatOptions): false | Required<ClientHeartbeatOptions> {\n if (heartbeat === false) {\n return false\n }\n\n const readTimeout = heartbeat?.readTimeout ?? 30_000\n const pingInterval = heartbeat?.pingInterval ?? Math.max(1_000, Math.floor(readTimeout / 2))\n\n return {\n readTimeout,\n pingInterval: Math.min(pingInterval, readTimeout),\n message: heartbeat?.message ?? MessageHeartbeat.Ping,\n }\n}\n\n/** Wraps a text websocket connector with AIRI protocol serialization. */\nexport function createTextProtocolConnector<C = undefined>(\n textConnector: ClientConnector<string>,\n): ClientConnector<WebSocketEvent<C>> {\n return {\n async connect(events) {\n const connection = await textConnector.connect({\n message(text) {\n try {\n events.message(parseEvent<C>(text))\n }\n catch (error) {\n events.error(error)\n }\n },\n close: details => events.close(details),\n error: error => events.error(error),\n })\n\n return {\n send: message => connection.send(stringifyEvent(message)),\n close: (code, reason) => connection.close?.(code, reason),\n ping: connection.ping,\n pong: connection.pong,\n }\n },\n }\n}\n\nfunction createDefaultProtocolConnector<C>(url: string): ClientConnector<WebSocketEvent<C>> {\n return createTextProtocolConnector(createCrossWsConnector({ url }))\n}\n\nfunction normalizeOptions<C>(options: ClientOptions<C>): NormalizedClientOptions<C> {\n const url = options.url ?? 'ws://localhost:6121/ws'\n const extension = options.extension ?? { id: options.name }\n const identity = options.identity ?? {\n id: createInstanceId(),\n extension,\n }\n\n return {\n url,\n name: options.name,\n token: options.token,\n connector: options.connector ?? createDefaultProtocolConnector<C>(url),\n handshake: options.handshake ?? 'module',\n connectTimeoutMs: options.connectTimeoutMs ?? 15_000,\n possibleEvents: options.possibleEvents ?? [],\n extension,\n identity,\n dependencies: options.dependencies ?? [],\n configSchema: options.configSchema,\n heartbeat: normalizeHeartbeatOptions(options.heartbeat),\n autoConnect: options.autoConnect ?? true,\n autoReconnect: options.autoReconnect ?? true,\n maxReconnectAttempts: options.maxReconnectAttempts ?? -1,\n onError: options.onError ?? (() => {}),\n onClose: options.onClose ?? (() => {}),\n onReady: options.onReady ?? (() => {}),\n onStateChange: options.onStateChange ?? (() => {}),\n onAnyMessage: options.onAnyMessage ?? (() => {}),\n onAnySend: options.onAnySend ?? (() => {}),\n }\n}\n\nfunction createConnectionTimeoutError(timeout: number) {\n return new Error(`Connection timed out after ${timeout}ms`)\n}\n\nfunction createAbortError() {\n return new Error('Connection aborted')\n}\n\nexport class Client<C = undefined> {\n private readonly opts: NormalizedClientOptions<C>\n private readonly transport: BetterWsClient<WebSocketEvent<C>>\n private readonly eventListeners = new Map<\n keyof WebSocketEvents<C>,\n Set<(data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>>\n >()\n\n private readonly stateListeners = new Set<(context: ClientStateChangeContext) => void>()\n private status: ClientStatus = 'idle'\n private connectTask?: Promise<void>\n private failureReason?: Error\n\n constructor(options: ClientOptions<C>) {\n this.opts = normalizeOptions(options)\n this.transport = createBetterWsClient<WebSocketEvent<C>>({\n connector: this.opts.connector,\n reconnect: this.createReconnectOptions(),\n heartbeat: this.createHeartbeatOptions(),\n prepare: context => this.prepareProtocolConnection(context),\n })\n\n this.transport.onMessage(({ message }) => {\n void this.handleMessage(message)\n })\n this.transport.onStateChange(({ previousState, state }) => {\n this.handleTransportStateChange(previousState, state)\n })\n\n if (this.opts.autoConnect) {\n void this.connect().catch((error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n this.failureReason = normalized\n this.opts.onError(normalized)\n })\n }\n }\n\n get connectionStatus() {\n return this.status\n }\n\n get isReady() {\n return this.status === 'ready'\n }\n\n get isSocketOpen() {\n return this.transport.state === 'open' || this.transport.state === 'preparing' || this.transport.state === 'ready'\n }\n\n get lastError() {\n return this.failureReason\n }\n\n async connect(options?: ConnectOptions) {\n if (this.status === 'ready') {\n return\n }\n\n if (!this.connectTask && this.transport.state === 'reconnecting') {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask && (this.transport.state === 'open' || this.transport.state === 'preparing')) {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask) {\n this.connectTask = this.transport.connect().finally(() => {\n this.connectTask = undefined\n })\n }\n\n return this.waitForConnection(this.connectTask, options)\n }\n\n ready(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n ensureConnected(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n onConnectionStateChange(callback: (context: ClientStateChangeContext) => void): () => void {\n this.stateListeners.add(callback)\n\n return () => {\n this.stateListeners.delete(callback)\n }\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n let listeners = this.eventListeners.get(event)\n if (!listeners) {\n listeners = new Set()\n this.eventListeners.set(event, listeners)\n }\n\n listeners.add(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n\n return () => {\n this.offEvent(event, callback)\n }\n }\n\n offEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback?: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): void {\n const listeners = this.eventListeners.get(event)\n if (!listeners) {\n return\n }\n\n if (callback) {\n listeners.delete(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n if (!listeners.size) {\n this.eventListeners.delete(event)\n }\n return\n }\n\n this.eventListeners.delete(event)\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n const payload = this.createPayload(data)\n const result = this.transport.send(payload)\n if (!result.ok) {\n return false\n }\n\n this.opts.onAnySend(payload)\n return true\n }\n\n sendOrThrow(data: WebSocketEventOptionalSource<C>): void {\n if (!this.send(data)) {\n throw new Error(`Client is not connected, current status: ${this.status}`)\n }\n }\n\n close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n\n private createReconnectOptions(): false | ReconnectOptions {\n if (!this.opts.autoReconnect) {\n return false\n }\n\n return {\n retries: (attempt, error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (isTerminalAuthenticationServerErrorMessage(normalized.message)) {\n return false\n }\n\n return this.opts.maxReconnectAttempts === -1 || attempt <= this.opts.maxReconnectAttempts\n },\n onFailed: (error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (this.failureReason === normalized) {\n return\n }\n\n this.failureReason = normalized\n this.opts.onError(normalized)\n },\n }\n }\n\n private createHeartbeatOptions() {\n if (!this.opts.heartbeat) {\n return false\n }\n\n return {\n mode: 'message' as const,\n interval: this.opts.heartbeat.pingInterval,\n timeout: this.opts.heartbeat.readTimeout,\n message: () => this.createPayload({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Ping,\n message: this.opts.heartbeat ? this.opts.heartbeat.message : MessageHeartbeat.Ping,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>),\n }\n }\n\n private async prepareProtocolConnection(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n if (this.opts.handshake === 'manual') {\n if (!context.reconnecting) {\n return\n }\n\n this.transitionTo('authenticating')\n await this.waitForManualReconnectHandshake(context)\n return\n }\n\n if (this.opts.token) {\n this.transitionTo('authenticating')\n context.send(this.createPayload({\n type: 'module:authenticate',\n data: { token: this.opts.token },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'module:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n this.transitionTo('announcing')\n context.send(this.createPayload({\n type: 'extension:module:announce',\n data: {\n name: this.opts.name,\n identity: this.opts.identity,\n possibleEvents: this.opts.possibleEvents,\n configSchema: this.opts.configSchema,\n dependencies: this.opts.dependencies,\n },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return result.ready\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private async waitForManualReconnectHandshake(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'peer:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n\n this.transitionTo('announcing')\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'extension:announced'\n && message.data.identity.id === this.opts.extension.id\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private consumePrepareMessage(message: WebSocketEvent<C>): ProtocolWaitResult {\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n return { ready: false, error }\n }\n\n if (message.type === 'extension:module:announced') {\n return { ready: this.isSelfModuleAnnouncement(message) }\n }\n\n if (message.type === 'registry:modules:sync') {\n return { ready: this.hasSelfModuleInRegistrySync(message) }\n }\n\n return { ready: false }\n }\n\n private async handleMessage(message: WebSocketEvent<C>): Promise<void> {\n this.opts.onAnyMessage(message)\n\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n this.opts.onError(error)\n }\n\n if (message.type === 'transport:connection:heartbeat' && message.data.kind === MessageHeartbeatKind.Ping) {\n this.send({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Pong,\n message: MessageHeartbeat.Pong,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>)\n }\n\n const listeners = this.eventListeners.get(message.type)\n if (!listeners?.size) {\n return\n }\n\n const results = await Promise.allSettled(\n Array.from(listeners).map(listener => Promise.resolve(listener(message as WebSocketBaseEvent<string, unknown>))),\n )\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.failureReason = this.normalizeError(result.reason, 'Client event listener failed')\n this.opts.onError(result.reason)\n }\n }\n }\n\n private handleTransportStateChange(previousState: BetterWsClient<WebSocketEvent<C>>['state'], state: BetterWsClient<WebSocketEvent<C>>['state']) {\n if (state === 'ready') {\n this.transitionTo('ready')\n this.opts.onReady()\n return\n }\n\n const nextStatus = this.mapTransportStatus(state)\n if (!nextStatus) {\n return\n }\n\n this.transitionTo(nextStatus)\n\n if (previousState === 'ready' && state === 'reconnecting') {\n this.opts.onClose()\n }\n else if (state === 'closed') {\n this.opts.onClose()\n }\n }\n\n private mapTransportStatus(state: BetterWsClient<WebSocketEvent<C>>['state']): ClientStatus | undefined {\n switch (state) {\n case 'idle':\n case 'connecting':\n case 'reconnecting':\n case 'closing':\n case 'closed':\n case 'failed':\n return state\n case 'open':\n case 'preparing':\n case 'ready':\n return undefined\n }\n }\n\n private transitionTo(status: ClientStatus) {\n if (this.status === status) {\n return\n }\n\n const previousStatus = this.status\n this.status = status\n const context = { previousStatus, status }\n\n this.opts.onStateChange(context)\n\n for (const listener of this.stateListeners) {\n listener(context)\n }\n }\n\n private async waitForConnection(connectPromise: Promise<void>, options?: ConnectOptions) {\n if (!options?.timeout && !options?.abortSignal) {\n return connectPromise\n }\n\n const timeout = options?.timeout\n if (typeof timeout !== 'undefined' && timeout <= 0) {\n throw createConnectionTimeoutError(timeout)\n }\n\n const abortSignal = options?.abortSignal\n if (abortSignal?.aborted) {\n throw createAbortError()\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n let removeAbortListener: (() => void) | undefined\n\n try {\n await Promise.race([\n connectPromise,\n new Promise<void>((_, reject) => {\n if (typeof timeout !== 'undefined') {\n timeoutHandle = setTimeout(() => {\n reject(createConnectionTimeoutError(timeout))\n }, timeout)\n }\n\n if (abortSignal) {\n const onAbort = () => reject(createAbortError())\n abortSignal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => abortSignal.removeEventListener('abort', onAbort)\n }\n }),\n ])\n }\n finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n\n removeAbortListener?.()\n }\n }\n\n private waitForReady(): Promise<void> {\n if (this.status === 'ready') {\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve, reject) => {\n const dispose = this.onConnectionStateChange(({ status }) => {\n if (status === 'ready') {\n dispose()\n resolve()\n return\n }\n\n if (status === 'failed' || status === 'closed') {\n dispose()\n reject(this.failureReason ?? new Error(`Client connection ended with status: ${status}`))\n }\n })\n })\n }\n\n private errorFromServerEvent(message: WebSocketEvent<C>): Error | undefined {\n if (message.type !== 'error') {\n return undefined\n }\n\n const errorMessage = typeof message.data.message === 'string'\n ? message.data.message\n : 'Unknown server error'\n const parsed = parseServerErrorMessage(errorMessage)\n\n if (parsed.code === 'unknown') {\n return new Error(errorMessage)\n }\n\n return new Error(parsed.message)\n }\n\n private normalizeError(error: unknown, fallback: string): Error {\n return error instanceof Error\n ? error\n : new Error(errorMessageFrom(error) ?? fallback)\n }\n\n private isSelfModuleAnnouncement(event: WebSocketBaseEvent<'extension:module:announced', WebSocketEvents<C>['extension:module:announced']>) {\n return event.data.name === this.opts.name && event.data.identity?.id === this.opts.identity.id\n }\n\n private hasSelfModuleInRegistrySync(event: WebSocketBaseEvent<'registry:modules:sync', WebSocketEvents<C>['registry:modules:sync']>) {\n return event.data.modules.some(module =>\n module.name === this.opts.name\n && module.identity?.id === this.opts.identity.id,\n )\n }\n\n private createPayload(data: WebSocketEventOptionalSource<C>) {\n return {\n ...data,\n metadata: {\n ...data.metadata,\n source: data.metadata?.source ?? {\n kind: 'plugin',\n ...this.opts.identity,\n plugin: { id: this.opts.extension.id },\n },\n event: {\n ...data.metadata?.event,\n id: data.metadata?.event?.id ?? createEventId(),\n },\n },\n } as WebSocketEvent<C>\n }\n}\n\nexport function createClient<C = undefined>(options: ClientOptions<C>): Client<C> {\n return new Client(options)\n}\n","import type {\n ExtensionIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n ModulePermissionDeclaration,\n ProtocolEvents,\n WebSocketBaseEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport type { ClientOptions, ConnectOptions } from './client'\n\nimport { createClient } from './client'\n\n/**\n * Describes the client operations required by {@link WebSocketExtensionPeer}.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface ExtensionPeerClient<C = undefined> {\n connect: (options?: ConnectOptions) => Promise<void>\n send: (data: WebSocketEventOptionalSource<C>) => boolean\n sendOrThrow: (data: WebSocketEventOptionalSource<C>) => void\n close: () => void\n onEvent?: <E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ) => () => void\n}\n\n/**\n * Describes one module announcement emitted through a websocket extension peer.\n *\n * @param C - Optional custom protocol event map used for possible event declarations.\n */\nexport interface AnnounceExtensionModuleInput<C = undefined> {\n /** Stable module id within the owning extension session. */\n id: string\n /** Human-readable module name used by registry and diagnostics. */\n name: string\n /** Protocol events this module may emit or handle. */\n possibleEvents?: Array<keyof ProtocolEvents<C>>\n /** Runtime permissions requested by this module. */\n permissions?: ModulePermissionDeclaration\n /** Optional configuration schema understood by the module. */\n configSchema?: ModuleConfigSchema\n /** Other modules or capabilities this module expects to exist. */\n dependencies?: ModuleDependency[]\n /** Optional labels for routing, diagnostics, or inspector views. */\n labels?: Record<string, string>\n}\n\n/**\n * Options for creating a websocket-backed extension peer. Supplying `client` lets\n * tests and embedding runtimes provide their own protocol client implementation.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface WebSocketExtensionPeerOptions<C = undefined> {\n extension: ExtensionIdentity\n client?: ExtensionPeerClient<C>\n clientOptions?: Omit<ClientOptions<C>, 'name' | 'identity'>\n}\n\n/**\n * Provides extension-level protocol helpers over a server-sdk protocol client.\n */\nexport class WebSocketExtensionPeer<C = undefined> {\n private readonly client: ExtensionPeerClient<C>\n private readonly extension: ExtensionIdentity\n\n constructor(options: WebSocketExtensionPeerOptions<C>) {\n this.extension = options.extension\n this.client = options.client ?? createClient<C>({\n ...options.clientOptions,\n name: options.extension.id,\n handshake: 'manual',\n autoConnect: options.clientOptions?.autoConnect ?? false,\n autoReconnect: options.clientOptions?.autoReconnect ?? false,\n })\n }\n\n connect(options?: ConnectOptions): Promise<void> {\n return this.client.connect(options)\n }\n\n authenticatePeer(input: { token?: string, peerId?: string } = {}): void {\n this.client.sendOrThrow({\n type: 'peer:authenticate',\n data: input,\n })\n }\n\n announceExtension(input: { permissions?: ModulePermissionDeclaration } = {}): void {\n this.client.sendOrThrow({\n type: 'extension:announce',\n data: {\n identity: this.extension,\n permissions: input.permissions,\n },\n })\n }\n\n announceModule(input: AnnounceExtensionModuleInput<C>): void {\n this.client.sendOrThrow({\n type: 'extension:module:announce',\n data: {\n name: input.name,\n identity: {\n id: input.id,\n extension: this.extension,\n labels: input.labels,\n },\n possibleEvents: input.possibleEvents ?? [],\n permissions: input.permissions,\n configSchema: input.configSchema,\n dependencies: input.dependencies,\n },\n })\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n return this.client.send(data)\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n if (!this.client.onEvent) {\n throw new Error('Wrapped extension peer client does not support event listeners.')\n }\n\n return this.client.onEvent(event, callback)\n }\n\n close(): void {\n this.client.close()\n }\n}\n\n/** Creates a websocket extension peer over a server-sdk protocol client. */\nexport function createWebSocketExtensionPeer<C = undefined>(\n options: WebSocketExtensionPeerOptions<C>,\n): WebSocketExtensionPeer<C> {\n return new WebSocketExtensionPeer(options)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["createBetterWsClient"],"sources":["../src/codec.ts","../src/client.ts","../src/extension-peer.ts"],"sourcesContent":["import type { WebSocketBaseEvent, WebSocketEvent } from '@proj-airi/server-shared/types'\n\nimport { parse, stringify } from 'superjson'\nimport { check, objectWithRest, pipe, safeParse, string, unknown } from 'valibot'\n\nconst invalidMessage = 'Invalid AIRI websocket message.'\n\nconst eventDataSchema = pipe(\n unknown(),\n check(\n value => Boolean(value) && typeof value === 'object' && !Array.isArray(value),\n 'Expected event data to be a non-array object.',\n ),\n)\n\nconst eventEnvelopeSchema = objectWithRest({\n type: string(),\n data: eventDataSchema,\n}, unknown())\n\n/** Options for websocket message validation failures. */\nexport interface InvalidMessageErrorOptions {\n /** Original parser or validator failure that made the websocket message unusable. */\n cause?: unknown\n /** Parsed candidate event when available; otherwise the original websocket text. */\n source?: unknown\n}\n\n/** Error thrown when websocket text cannot be parsed as an AIRI event envelope. */\nexport class InvalidMessageError extends Error {\n readonly source?: unknown\n\n constructor(options: InvalidMessageErrorOptions = {}) {\n super(invalidMessage, { cause: options.cause })\n this.name = 'InvalidMessageError'\n this.source = options.source\n }\n}\n\n/** Parses one AIRI websocket protocol event from SuperJSON or plain JSON text. */\nexport function parseEvent<C = undefined>(text: string): WebSocketEvent<C> {\n let superJsonParsed: WebSocketEvent<C> | undefined\n let superJsonError: unknown\n\n try {\n superJsonParsed = parse<WebSocketEvent<C>>(text)\n }\n catch (error) {\n superJsonError = error\n }\n\n const potentialEvent = superJsonParsed && typeof superJsonParsed === 'object' && 'type' in superJsonParsed\n ? superJsonParsed\n : parsePlainJson(text, superJsonError)\n\n const result = safeParse(eventEnvelopeSchema, potentialEvent)\n if (!result.success) {\n throw new InvalidMessageError({ cause: result.issues, source: potentialEvent })\n }\n\n return potentialEvent as WebSocketEvent<C>\n}\n\n/** Serializes one AIRI websocket protocol event with SuperJSON. */\nexport function stringifyEvent<C = undefined>(\n event: WebSocketBaseEvent<string, unknown> | WebSocketEvent<C>,\n) {\n return stringify(event)\n}\n\nfunction parsePlainJson(text: string, superJsonError: unknown): unknown {\n try {\n return JSON.parse(text)\n }\n catch (jsonError) {\n throw new InvalidMessageError({\n cause: superJsonError ?? jsonError,\n source: text,\n })\n }\n}\n","import type {\n Client as BetterWsClient,\n ClientConnector,\n PrepareContext,\n ReconnectOptions,\n} from '@proj-airi/better-ws'\nimport type {\n ExtensionIdentity,\n ExtensionModuleIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n WebSocketBaseEvent,\n WebSocketEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport { errorMessageFrom } from '@moeru/std'\nimport { decodeBase64 } from '@moeru/std/base64'\nimport { createClient as createBetterWsClient } from '@proj-airi/better-ws'\nimport { createCrossWsConnector } from '@proj-airi/better-ws/client/crossws'\nimport { isTerminalAuthenticationServerErrorMessage, parseServerErrorMessage } from '@proj-airi/server-shared'\nimport { MessageHeartbeat, MessageHeartbeatKind } from '@proj-airi/server-shared/types'\n\nimport { parseEvent, stringifyEvent } from './codec'\n\nexport type { ClientConnector, ClientEvents } from '@proj-airi/better-ws'\n\nexport type ClientStatus\n = | 'idle'\n | 'connecting'\n | 'authenticating'\n | 'announcing'\n | 'ready'\n | 'reconnecting'\n | 'closing'\n | 'closed'\n | 'failed'\n\nexport interface ClientHeartbeatOptions {\n pingInterval?: number\n readTimeout?: number\n message?: MessageHeartbeat | string\n}\n\nexport interface ClientStateChangeContext {\n previousStatus: ClientStatus\n status: ClientStatus\n}\n\nexport interface ConnectOptions {\n abortSignal?: AbortSignal\n timeout?: number\n}\n\nexport interface ClientOptions<C = undefined> {\n url?: string\n name: string\n token?: string\n connector?: ClientConnector<WebSocketEvent<C>>\n /**\n * Selects the connection handshake owned by this client.\n *\n * @default 'module'\n */\n handshake?: 'module' | 'manual'\n\n connectTimeoutMs?: number\n possibleEvents?: Array<keyof WebSocketEvents<C>>\n extension?: ExtensionIdentity\n identity?: ExtensionModuleIdentity\n dependencies?: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat?: false | ClientHeartbeatOptions\n\n autoConnect?: boolean\n autoReconnect?: boolean\n maxReconnectAttempts?: number\n\n onError?: (error: unknown) => void\n onClose?: () => void\n onReady?: () => void\n onStateChange?: (context: ClientStateChangeContext) => void\n\n onAnyMessage?: (data: WebSocketEvent<C>) => void\n onAnySend?: (data: WebSocketEvent<C>) => void\n}\n\ninterface NormalizedClientOptions<C> {\n url: string\n name: string\n token?: string\n connector: ClientConnector<WebSocketEvent<C>>\n handshake: 'module' | 'manual'\n connectTimeoutMs: number\n possibleEvents: Array<keyof WebSocketEvents<C>>\n extension: ExtensionIdentity\n identity: ExtensionModuleIdentity\n dependencies: ModuleDependency[]\n configSchema?: ModuleConfigSchema\n heartbeat: false | Required<ClientHeartbeatOptions>\n autoConnect: boolean\n autoReconnect: boolean\n maxReconnectAttempts: number\n onError: (error: unknown) => void\n onClose: () => void\n onReady: () => void\n onStateChange: (context: ClientStateChangeContext) => void\n onAnyMessage: (data: WebSocketEvent<C>) => void\n onAnySend: (data: WebSocketEvent<C>) => void\n}\n\ninterface ProtocolWaitResult {\n ready: boolean\n error?: Error\n}\n\nfunction createInstanceId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`\n}\n\nfunction createEventId() {\n return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`\n}\n\nfunction normalizeHeartbeatOptions(heartbeat?: false | ClientHeartbeatOptions): false | Required<ClientHeartbeatOptions> {\n if (heartbeat === false) {\n return false\n }\n\n const readTimeout = heartbeat?.readTimeout ?? 30_000\n const pingInterval = heartbeat?.pingInterval ?? Math.max(1_000, Math.floor(readTimeout / 2))\n\n return {\n readTimeout,\n pingInterval: Math.min(pingInterval, readTimeout),\n message: heartbeat?.message ?? MessageHeartbeat.Ping,\n }\n}\n\n/** Wraps a text websocket connector with AIRI protocol serialization. */\nexport function createTextProtocolConnector<C = undefined>(\n textConnector: ClientConnector<string>,\n): ClientConnector<WebSocketEvent<C>> {\n return {\n async connect(events) {\n const connection = await textConnector.connect({\n message(text) {\n try {\n events.message(parseEvent<C>(text))\n }\n catch (error) {\n events.error(error)\n }\n },\n close: details => events.close(details),\n error: error => events.error(error),\n })\n\n return {\n send: message => connection.send(stringifyEvent(message)),\n close: (code, reason) => connection.close?.(code, reason),\n ping: connection.ping,\n pong: connection.pong,\n }\n },\n }\n}\n\nfunction createDefaultProtocolConnector<C>(url: string): ClientConnector<WebSocketEvent<C>> {\n return createTextProtocolConnector(createCrossWsConnector({ url }))\n}\n\nfunction normalizeOptions<C>(options: ClientOptions<C>): NormalizedClientOptions<C> {\n const url = options.url ?? 'ws://localhost:6121/ws'\n const extension = options.extension ?? { id: options.name }\n const identity = options.identity ?? {\n id: createInstanceId(),\n extension,\n }\n\n return {\n url,\n name: options.name,\n token: options.token,\n connector: options.connector ?? createDefaultProtocolConnector<C>(url),\n handshake: options.handshake ?? 'module',\n connectTimeoutMs: options.connectTimeoutMs ?? 15_000,\n possibleEvents: options.possibleEvents ?? [],\n extension,\n identity,\n dependencies: options.dependencies ?? [],\n configSchema: options.configSchema,\n heartbeat: normalizeHeartbeatOptions(options.heartbeat),\n autoConnect: options.autoConnect ?? true,\n autoReconnect: options.autoReconnect ?? true,\n maxReconnectAttempts: options.maxReconnectAttempts ?? -1,\n onError: options.onError ?? (() => {}),\n onClose: options.onClose ?? (() => {}),\n onReady: options.onReady ?? (() => {}),\n onStateChange: options.onStateChange ?? (() => {}),\n onAnyMessage: options.onAnyMessage ?? (() => {}),\n onAnySend: options.onAnySend ?? (() => {}),\n }\n}\n\nfunction createConnectionTimeoutError(timeout: number) {\n return new Error(`Connection timed out after ${timeout}ms`)\n}\n\nfunction createAbortError() {\n return new Error('Connection aborted')\n}\n\nexport class Client<C = undefined> {\n private readonly opts: NormalizedClientOptions<C>\n private readonly transport: BetterWsClient<WebSocketEvent<C>>\n private readonly eventListeners = new Map<\n keyof WebSocketEvents<C>,\n Set<(data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>>\n >()\n\n private readonly stateListeners = new Set<(context: ClientStateChangeContext) => void>()\n private status: ClientStatus = 'idle'\n private connectTask?: Promise<void>\n private failureReason?: Error\n\n constructor(options: ClientOptions<C>) {\n this.opts = normalizeOptions(options)\n this.transport = createBetterWsClient<WebSocketEvent<C>>({\n connector: this.opts.connector,\n reconnect: this.createReconnectOptions(),\n heartbeat: this.createHeartbeatOptions(),\n prepare: context => this.prepareProtocolConnection(context),\n })\n\n this.transport.onMessage(({ message }) => {\n void this.handleMessage(message)\n })\n this.transport.onStateChange(({ previousState, state }) => {\n this.handleTransportStateChange(previousState, state)\n })\n\n if (this.opts.autoConnect) {\n void this.connect().catch((error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n this.failureReason = normalized\n this.opts.onError(normalized)\n })\n }\n }\n\n get connectionStatus() {\n return this.status\n }\n\n get isReady() {\n return this.status === 'ready'\n }\n\n get isSocketOpen() {\n return this.transport.state === 'open' || this.transport.state === 'preparing' || this.transport.state === 'ready'\n }\n\n get lastError() {\n return this.failureReason\n }\n\n async connect(options?: ConnectOptions) {\n if (this.status === 'ready') {\n return\n }\n\n if (!this.connectTask && this.transport.state === 'reconnecting') {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask && (this.transport.state === 'open' || this.transport.state === 'preparing')) {\n return this.waitForConnection(this.waitForReady(), options)\n }\n\n if (!this.connectTask) {\n this.connectTask = this.transport.connect().finally(() => {\n this.connectTask = undefined\n })\n }\n\n return this.waitForConnection(this.connectTask, options)\n }\n\n ready(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n ensureConnected(options?: ConnectOptions) {\n return this.connect(options)\n }\n\n onConnectionStateChange(callback: (context: ClientStateChangeContext) => void): () => void {\n this.stateListeners.add(callback)\n\n return () => {\n this.stateListeners.delete(callback)\n }\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n let listeners = this.eventListeners.get(event)\n if (!listeners) {\n listeners = new Set()\n this.eventListeners.set(event, listeners)\n }\n\n listeners.add(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n\n return () => {\n this.offEvent(event, callback)\n }\n }\n\n offEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback?: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): void {\n const listeners = this.eventListeners.get(event)\n if (!listeners) {\n return\n }\n\n if (callback) {\n listeners.delete(callback as (data: WebSocketBaseEvent<string, unknown>) => void | Promise<void>)\n if (!listeners.size) {\n this.eventListeners.delete(event)\n }\n return\n }\n\n this.eventListeners.delete(event)\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n const payload = this.createPayload(data)\n const result = this.transport.send(payload)\n if (!result.ok) {\n return false\n }\n\n this.opts.onAnySend(payload)\n return true\n }\n\n sendOrThrow(data: WebSocketEventOptionalSource<C>): void {\n if (!this.send(data)) {\n throw new Error(`Client is not connected, current status: ${this.status}`)\n }\n }\n\n /**\n * Reads the bytes of an image or recording that a chat message references as `airi-asset:<id>`.\n *\n * The stage answers the request. The call rejects with the stage error, when the client is not connected, or when\n * no answer arrives within `timeoutMs`.\n */\n async getAsset(ref: string, options: { timeoutMs?: number } = {}): Promise<{ mimeType: string, data: Uint8Array }> {\n const id = createEventId()\n const { promise, resolve, reject } = Promise.withResolvers<WebSocketEvents<C>['asset:get:response']>()\n const timer = setTimeout(() => reject(new Error(`The stage did not answer for asset ${ref}`)), options.timeoutMs ?? 10_000)\n const stop = this.onEvent('asset:get:response', (event) => {\n if (event.metadata.event.parentId === id)\n resolve(event.data)\n })\n\n try {\n this.sendOrThrow({ type: 'asset:get:request', data: { ref }, metadata: { event: { id } } } as WebSocketEventOptionalSource<C>)\n const answer = await promise\n if ('error' in answer)\n throw new Error(answer.error)\n return { mimeType: answer.mimeType, data: decodeBase64(answer.data) }\n }\n finally {\n clearTimeout(timer)\n stop()\n }\n }\n\n close(code?: number, reason?: string): void {\n this.transport.close(code, reason)\n }\n\n private createReconnectOptions(): false | ReconnectOptions {\n if (!this.opts.autoReconnect) {\n return false\n }\n\n return {\n retries: (attempt, error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (isTerminalAuthenticationServerErrorMessage(normalized.message)) {\n return false\n }\n\n return this.opts.maxReconnectAttempts === -1 || attempt <= this.opts.maxReconnectAttempts\n },\n onFailed: (error) => {\n const normalized = this.normalizeError(error, 'Failed to connect websocket client')\n if (this.failureReason === normalized) {\n return\n }\n\n this.failureReason = normalized\n this.opts.onError(normalized)\n },\n }\n }\n\n private createHeartbeatOptions() {\n if (!this.opts.heartbeat) {\n return false\n }\n\n return {\n mode: 'message' as const,\n interval: this.opts.heartbeat.pingInterval,\n timeout: this.opts.heartbeat.readTimeout,\n message: () => this.createPayload({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Ping,\n message: this.opts.heartbeat ? this.opts.heartbeat.message : MessageHeartbeat.Ping,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>),\n }\n }\n\n private async prepareProtocolConnection(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n if (this.opts.handshake === 'manual') {\n if (!context.reconnecting) {\n return\n }\n\n this.transitionTo('authenticating')\n await this.waitForManualReconnectHandshake(context)\n return\n }\n\n if (this.opts.token) {\n this.transitionTo('authenticating')\n context.send(this.createPayload({\n type: 'module:authenticate',\n data: { token: this.opts.token },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'module:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n this.transitionTo('announcing')\n context.send(this.createPayload({\n type: 'extension:module:announce',\n data: {\n name: this.opts.name,\n identity: this.opts.identity,\n possibleEvents: this.opts.possibleEvents,\n configSchema: this.opts.configSchema,\n dependencies: this.opts.dependencies,\n },\n } as WebSocketEventOptionalSource<C>))\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return result.ready\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private async waitForManualReconnectHandshake(context: PrepareContext<WebSocketEvent<C>>): Promise<void> {\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'peer:authenticated' && message.data.authenticated === true\n }, { timeout: this.opts.connectTimeoutMs })\n\n this.transitionTo('announcing')\n\n await context.waitFor((message) => {\n const result = this.consumePrepareMessage(message)\n if (result.error) {\n throw result.error\n }\n\n return message.type === 'extension:announced'\n && message.data.identity.id === this.opts.extension.id\n }, { timeout: this.opts.connectTimeoutMs })\n }\n\n private consumePrepareMessage(message: WebSocketEvent<C>): ProtocolWaitResult {\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n return { ready: false, error }\n }\n\n if (message.type === 'extension:module:announced') {\n return { ready: this.isSelfModuleAnnouncement(message) }\n }\n\n if (message.type === 'registry:modules:sync') {\n return { ready: this.hasSelfModuleInRegistrySync(message) }\n }\n\n return { ready: false }\n }\n\n private async handleMessage(message: WebSocketEvent<C>): Promise<void> {\n this.opts.onAnyMessage(message)\n\n const error = this.errorFromServerEvent(message)\n if (error) {\n this.failureReason = error\n this.opts.onError(error)\n }\n\n if (message.type === 'transport:connection:heartbeat' && message.data.kind === MessageHeartbeatKind.Ping) {\n this.send({\n type: 'transport:connection:heartbeat',\n data: {\n kind: MessageHeartbeatKind.Pong,\n message: MessageHeartbeat.Pong,\n at: Date.now(),\n },\n } as WebSocketEventOptionalSource<C>)\n }\n\n const listeners = this.eventListeners.get(message.type)\n if (!listeners?.size) {\n return\n }\n\n const results = await Promise.allSettled(\n Array.from(listeners).map(listener => Promise.resolve(listener(message as WebSocketBaseEvent<string, unknown>))),\n )\n\n for (const result of results) {\n if (result.status === 'rejected') {\n this.failureReason = this.normalizeError(result.reason, 'Client event listener failed')\n this.opts.onError(result.reason)\n }\n }\n }\n\n private handleTransportStateChange(previousState: BetterWsClient<WebSocketEvent<C>>['state'], state: BetterWsClient<WebSocketEvent<C>>['state']) {\n if (state === 'ready') {\n this.transitionTo('ready')\n this.opts.onReady()\n return\n }\n\n const nextStatus = this.mapTransportStatus(state)\n if (!nextStatus) {\n return\n }\n\n this.transitionTo(nextStatus)\n\n if (previousState === 'ready' && state === 'reconnecting') {\n this.opts.onClose()\n }\n else if (state === 'closed') {\n this.opts.onClose()\n }\n }\n\n private mapTransportStatus(state: BetterWsClient<WebSocketEvent<C>>['state']): ClientStatus | undefined {\n switch (state) {\n case 'idle':\n case 'connecting':\n case 'reconnecting':\n case 'closing':\n case 'closed':\n case 'failed':\n return state\n case 'open':\n case 'preparing':\n case 'ready':\n return undefined\n }\n }\n\n private transitionTo(status: ClientStatus) {\n if (this.status === status) {\n return\n }\n\n const previousStatus = this.status\n this.status = status\n const context = { previousStatus, status }\n\n this.opts.onStateChange(context)\n\n for (const listener of this.stateListeners) {\n listener(context)\n }\n }\n\n private async waitForConnection(connectPromise: Promise<void>, options?: ConnectOptions) {\n if (!options?.timeout && !options?.abortSignal) {\n return connectPromise\n }\n\n const timeout = options?.timeout\n if (typeof timeout !== 'undefined' && timeout <= 0) {\n throw createConnectionTimeoutError(timeout)\n }\n\n const abortSignal = options?.abortSignal\n if (abortSignal?.aborted) {\n throw createAbortError()\n }\n\n let timeoutHandle: ReturnType<typeof setTimeout> | undefined\n let removeAbortListener: (() => void) | undefined\n\n try {\n await Promise.race([\n connectPromise,\n new Promise<void>((_, reject) => {\n if (typeof timeout !== 'undefined') {\n timeoutHandle = setTimeout(() => {\n reject(createConnectionTimeoutError(timeout))\n }, timeout)\n }\n\n if (abortSignal) {\n const onAbort = () => reject(createAbortError())\n abortSignal.addEventListener('abort', onAbort, { once: true })\n removeAbortListener = () => abortSignal.removeEventListener('abort', onAbort)\n }\n }),\n ])\n }\n finally {\n if (timeoutHandle) {\n clearTimeout(timeoutHandle)\n }\n\n removeAbortListener?.()\n }\n }\n\n private waitForReady(): Promise<void> {\n if (this.status === 'ready') {\n return Promise.resolve()\n }\n\n return new Promise<void>((resolve, reject) => {\n const dispose = this.onConnectionStateChange(({ status }) => {\n if (status === 'ready') {\n dispose()\n resolve()\n return\n }\n\n if (status === 'failed' || status === 'closed') {\n dispose()\n reject(this.failureReason ?? new Error(`Client connection ended with status: ${status}`))\n }\n })\n })\n }\n\n private errorFromServerEvent(message: WebSocketEvent<C>): Error | undefined {\n if (message.type !== 'error') {\n return undefined\n }\n\n const errorMessage = typeof message.data.message === 'string'\n ? message.data.message\n : 'Unknown server error'\n const parsed = parseServerErrorMessage(errorMessage)\n\n if (parsed.code === 'unknown') {\n return new Error(errorMessage)\n }\n\n return new Error(parsed.message)\n }\n\n private normalizeError(error: unknown, fallback: string): Error {\n return error instanceof Error\n ? error\n : new Error(errorMessageFrom(error) ?? fallback)\n }\n\n private isSelfModuleAnnouncement(event: WebSocketBaseEvent<'extension:module:announced', WebSocketEvents<C>['extension:module:announced']>) {\n return event.data.name === this.opts.name && event.data.identity?.id === this.opts.identity.id\n }\n\n private hasSelfModuleInRegistrySync(event: WebSocketBaseEvent<'registry:modules:sync', WebSocketEvents<C>['registry:modules:sync']>) {\n return event.data.modules.some(module =>\n module.name === this.opts.name\n && module.identity?.id === this.opts.identity.id,\n )\n }\n\n private createPayload(data: WebSocketEventOptionalSource<C>) {\n return {\n ...data,\n metadata: {\n ...data.metadata,\n source: data.metadata?.source ?? {\n kind: 'plugin',\n ...this.opts.identity,\n plugin: { id: this.opts.extension.id },\n },\n event: {\n ...data.metadata?.event,\n id: data.metadata?.event?.id ?? createEventId(),\n },\n },\n } as WebSocketEvent<C>\n }\n}\n\nexport function createClient<C = undefined>(options: ClientOptions<C>): Client<C> {\n return new Client(options)\n}\n","import type {\n ExtensionIdentity,\n ModuleConfigSchema,\n ModuleDependency,\n ModulePermissionDeclaration,\n ProtocolEvents,\n WebSocketBaseEvent,\n WebSocketEventOptionalSource,\n WebSocketEvents,\n} from '@proj-airi/server-shared/types'\n\nimport type { ClientOptions, ConnectOptions } from './client'\n\nimport { createClient } from './client'\n\n/**\n * Describes the client operations required by {@link WebSocketExtensionPeer}.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface ExtensionPeerClient<C = undefined> {\n connect: (options?: ConnectOptions) => Promise<void>\n send: (data: WebSocketEventOptionalSource<C>) => boolean\n sendOrThrow: (data: WebSocketEventOptionalSource<C>) => void\n close: () => void\n onEvent?: <E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ) => () => void\n}\n\n/**\n * Describes one module announcement emitted through a websocket extension peer.\n *\n * @param C - Optional custom protocol event map used for possible event declarations.\n */\nexport interface AnnounceExtensionModuleInput<C = undefined> {\n /** Stable module id within the owning extension session. */\n id: string\n /** Human-readable module name used by registry and diagnostics. */\n name: string\n /** Protocol events this module may emit or handle. */\n possibleEvents?: Array<keyof ProtocolEvents<C>>\n /** Runtime permissions requested by this module. */\n permissions?: ModulePermissionDeclaration\n /** Optional configuration schema understood by the module. */\n configSchema?: ModuleConfigSchema\n /** Other modules or capabilities this module expects to exist. */\n dependencies?: ModuleDependency[]\n /** Optional labels for routing, diagnostics, or inspector views. */\n labels?: Record<string, string>\n}\n\n/**\n * Options for creating a websocket-backed extension peer. Supplying `client` lets\n * tests and embedding runtimes provide their own protocol client implementation.\n *\n * @param C - Optional custom protocol event map carried by the websocket client.\n */\nexport interface WebSocketExtensionPeerOptions<C = undefined> {\n extension: ExtensionIdentity\n client?: ExtensionPeerClient<C>\n clientOptions?: Omit<ClientOptions<C>, 'name' | 'identity'>\n}\n\n/**\n * Provides extension-level protocol helpers over a server-sdk protocol client.\n */\nexport class WebSocketExtensionPeer<C = undefined> {\n private readonly client: ExtensionPeerClient<C>\n private readonly extension: ExtensionIdentity\n\n constructor(options: WebSocketExtensionPeerOptions<C>) {\n this.extension = options.extension\n this.client = options.client ?? createClient<C>({\n ...options.clientOptions,\n name: options.extension.id,\n handshake: 'manual',\n autoConnect: options.clientOptions?.autoConnect ?? false,\n autoReconnect: options.clientOptions?.autoReconnect ?? false,\n })\n }\n\n connect(options?: ConnectOptions): Promise<void> {\n return this.client.connect(options)\n }\n\n authenticatePeer(input: { token?: string, peerId?: string } = {}): void {\n this.client.sendOrThrow({\n type: 'peer:authenticate',\n data: input,\n })\n }\n\n announceExtension(input: { permissions?: ModulePermissionDeclaration } = {}): void {\n this.client.sendOrThrow({\n type: 'extension:announce',\n data: {\n identity: this.extension,\n permissions: input.permissions,\n },\n })\n }\n\n announceModule(input: AnnounceExtensionModuleInput<C>): void {\n this.client.sendOrThrow({\n type: 'extension:module:announce',\n data: {\n name: input.name,\n identity: {\n id: input.id,\n extension: this.extension,\n labels: input.labels,\n },\n possibleEvents: input.possibleEvents ?? [],\n permissions: input.permissions,\n configSchema: input.configSchema,\n dependencies: input.dependencies,\n },\n })\n }\n\n send(data: WebSocketEventOptionalSource<C>): boolean {\n return this.client.send(data)\n }\n\n onEvent<E extends keyof WebSocketEvents<C>>(\n event: E,\n callback: (data: WebSocketBaseEvent<E, WebSocketEvents<C>[E]>) => void | Promise<void>,\n ): () => void {\n if (!this.client.onEvent) {\n throw new Error('Wrapped extension peer client does not support event listeners.')\n }\n\n return this.client.onEvent(event, callback)\n }\n\n close(): void {\n this.client.close()\n }\n}\n\n/** Creates a websocket extension peer over a server-sdk protocol client. */\nexport function createWebSocketExtensionPeer<C = undefined>(\n options: WebSocketExtensionPeerOptions<C>,\n): WebSocketExtensionPeer<C> {\n return new WebSocketExtensionPeer(options)\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@proj-airi/server-sdk",
3
3
  "type": "module",
4
- "version": "0.12.0-beta.4",
4
+ "version": "0.12.0-beta.6",
5
5
  "description": "Client-side SDK implementation for connecting to AIRI server components and runtimes",
6
6
  "author": {
7
7
  "name": "Moeru AI Project AIRI Team",
@@ -33,12 +33,13 @@
33
33
  ],
34
34
  "dependencies": {
35
35
  "@moeru/std": "0.1.0-beta.17",
36
- "@proj-airi/better-ws": "^0.12.0-beta.4",
37
- "@proj-airi/server-shared": "^0.12.0-beta.4",
36
+ "@proj-airi/better-ws": "^0.12.0-beta.6",
37
+ "@proj-airi/server-shared": "^0.12.0-beta.6",
38
38
  "superjson": "^2.2.6",
39
39
  "valibot": "^1.4.2"
40
40
  },
41
41
  "scripts": {
42
+ "test:run": "vitest run --config vitest.config.ts",
42
43
  "dev": "pnpm run build",
43
44
  "build": "tsdown",
44
45
  "typecheck": "tsc --noEmit"