@meshagent/meshagent 0.37.2 → 0.38.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.
Files changed (77) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/browser/agent.js +74 -10
  3. package/dist/browser/developer-client.js +3 -0
  4. package/dist/browser/helpers.d.ts +2 -2
  5. package/dist/browser/helpers.js +1 -1
  6. package/dist/browser/meshagent-client.d.ts +25 -0
  7. package/dist/browser/meshagent-client.js +65 -0
  8. package/dist/browser/messaging-client.d.ts +29 -16
  9. package/dist/browser/messaging-client.js +256 -154
  10. package/dist/browser/participant.d.ts +7 -2
  11. package/dist/browser/participant.js +9 -9
  12. package/dist/browser/protocol.d.ts +85 -28
  13. package/dist/browser/protocol.js +356 -119
  14. package/dist/browser/room-client.d.ts +165 -29
  15. package/dist/browser/room-client.js +1114 -74
  16. package/dist/browser/room-event.d.ts +11 -0
  17. package/dist/browser/room-event.js +21 -1
  18. package/dist/browser/room-server-client.d.ts +2 -0
  19. package/dist/browser/room-server-client.js +6 -0
  20. package/dist/browser/runtime.d.ts +1 -1
  21. package/dist/browser/runtime.js +3 -1
  22. package/dist/browser/secrets-client.js +6 -2
  23. package/dist/browser/storage-client.d.ts +1 -0
  24. package/dist/browser/storage-client.js +9 -0
  25. package/dist/browser/sync-client.d.ts +16 -14
  26. package/dist/browser/sync-client.js +195 -116
  27. package/dist/esm/agent.js +74 -10
  28. package/dist/esm/developer-client.js +3 -0
  29. package/dist/esm/helpers.d.ts +2 -2
  30. package/dist/esm/helpers.js +1 -1
  31. package/dist/esm/meshagent-client.d.ts +25 -0
  32. package/dist/esm/meshagent-client.js +65 -0
  33. package/dist/esm/messaging-client.d.ts +29 -16
  34. package/dist/esm/messaging-client.js +256 -154
  35. package/dist/esm/participant.d.ts +7 -2
  36. package/dist/esm/participant.js +9 -9
  37. package/dist/esm/protocol.d.ts +85 -28
  38. package/dist/esm/protocol.js +352 -118
  39. package/dist/esm/room-client.d.ts +165 -29
  40. package/dist/esm/room-client.js +1112 -73
  41. package/dist/esm/room-event.d.ts +11 -0
  42. package/dist/esm/room-event.js +19 -0
  43. package/dist/esm/room-server-client.d.ts +2 -0
  44. package/dist/esm/room-server-client.js +7 -1
  45. package/dist/esm/runtime.d.ts +1 -1
  46. package/dist/esm/runtime.js +1 -1
  47. package/dist/esm/secrets-client.js +6 -2
  48. package/dist/esm/storage-client.d.ts +1 -0
  49. package/dist/esm/storage-client.js +9 -0
  50. package/dist/esm/sync-client.d.ts +16 -14
  51. package/dist/esm/sync-client.js +196 -117
  52. package/dist/node/agent.js +74 -10
  53. package/dist/node/developer-client.js +3 -0
  54. package/dist/node/helpers.d.ts +2 -2
  55. package/dist/node/helpers.js +1 -1
  56. package/dist/node/meshagent-client.d.ts +25 -0
  57. package/dist/node/meshagent-client.js +65 -0
  58. package/dist/node/messaging-client.d.ts +29 -16
  59. package/dist/node/messaging-client.js +256 -154
  60. package/dist/node/participant.d.ts +7 -2
  61. package/dist/node/participant.js +9 -9
  62. package/dist/node/protocol.d.ts +85 -28
  63. package/dist/node/protocol.js +356 -119
  64. package/dist/node/room-client.d.ts +165 -29
  65. package/dist/node/room-client.js +1114 -74
  66. package/dist/node/room-event.d.ts +11 -0
  67. package/dist/node/room-event.js +21 -1
  68. package/dist/node/room-server-client.d.ts +2 -0
  69. package/dist/node/room-server-client.js +6 -0
  70. package/dist/node/runtime.d.ts +1 -1
  71. package/dist/node/runtime.js +3 -1
  72. package/dist/node/secrets-client.js +6 -2
  73. package/dist/node/storage-client.d.ts +1 -0
  74. package/dist/node/storage-client.js +9 -0
  75. package/dist/node/sync-client.d.ts +16 -14
  76. package/dist/node/sync-client.js +195 -116
  77. package/package.json +6 -3
@@ -1,32 +1,244 @@
1
1
  import { Completer } from "./completer";
2
- import { packMessage, splitMessageHeader, splitMessagePayload, unpackMessage } from "./utils";
3
- import { LocalParticipant } from "./participant";
4
- import { StreamController } from "./stream-controller";
5
- import { SyncClient } from "./sync-client";
2
+ import { DatabaseClient } from "./database-client";
6
3
  import { DeveloperClient } from "./developer-client";
7
- import { StorageClient } from "./storage-client";
4
+ import { EventEmitter } from "./event-emitter";
8
5
  import { MessagingClient } from "./messaging-client";
6
+ import { MemoryClient } from "./memory-client";
7
+ import { LocalParticipant } from "./participant";
8
+ import { ProtocolCloseException, ProtocolCloseKind, ProtocolHandshakeException, ProtocolReconnectUnsupportedException, WebSocketClientProtocol, } from "./protocol";
9
9
  import { QueuesClient } from "./queues-client";
10
- import { DatabaseClient } from "./database-client";
11
- import { AgentsClient, ToolkitDescription } from "./agent-client";
10
+ import { BinaryContent, ControlContent, EmptyContent, ErrorContent, FileContent, JsonContent, LinkContent, TextContent, unpackContent } from "./response";
11
+ import { RoomStatusEvent } from "./room-event";
12
+ import { RoomServerException } from "./room-server-client";
12
13
  import { SecretsClient } from "./secrets-client";
13
- import { ContainersClient } from "./containers-client";
14
- import { MemoryClient } from "./memory-client";
15
14
  import { ServicesClient } from "./services-client";
16
- import { RoomServerException } from "./room-server-client";
17
- import { BinaryContent, ControlContent, EmptyContent, ErrorContent, FileContent, JsonContent, LinkContent, TextContent, unpackContent } from "./response";
15
+ import { StorageClient } from "./storage-client";
16
+ import { StreamController } from "./stream-controller";
17
+ import { SyncClient } from "./sync-client";
18
+ import { splitMessageHeader, splitMessagePayload, packMessage, unpackMessage } from "./utils";
19
+ import { AgentsClient, ToolkitDescription } from "./agent-client";
20
+ import { ContainersClient } from "./containers-client";
21
+ class ProtocolStartupFailure extends Error {
22
+ constructor({ kind, reason }) {
23
+ super(reason ?? kind);
24
+ this.name = "ProtocolStartupFailure";
25
+ this.kind = kind;
26
+ this.reason = reason;
27
+ }
28
+ }
29
+ class RoomClientTerminalState {
30
+ constructor({ requestMessage, toolCallMessage, messageSendMessage, }) {
31
+ this.requestMessage = requestMessage;
32
+ this.toolCallMessage = toolCallMessage;
33
+ this.messageSendMessage = messageSendMessage;
34
+ }
35
+ requestError() {
36
+ return new RoomServerException(this.requestMessage);
37
+ }
38
+ toolCallError() {
39
+ return new RoomServerException(this.toolCallMessage);
40
+ }
41
+ messageSendError() {
42
+ return new RoomServerException(this.messageSendMessage);
43
+ }
44
+ }
45
+ class RoomConnectionStatusException extends RoomServerException {
46
+ constructor({ statusCode, statusText, }) {
47
+ const normalizedStatusText = statusText?.trim();
48
+ super(normalizedStatusText == null || normalizedStatusText.length === 0
49
+ ? `websocket connect failed with status ${statusCode}`
50
+ : `websocket connect failed with status ${statusCode}: ${normalizedStatusText}`);
51
+ this.name = "RoomConnectionStatusException";
52
+ this.statusCode = statusCode;
53
+ }
54
+ }
55
+ function normalizeCloseReason(reason) {
56
+ if (reason == null) {
57
+ return null;
58
+ }
59
+ const normalized = reason.trim();
60
+ return normalized.length === 0 ? null : normalized;
61
+ }
62
+ function wrapRoomConnectionError(error) {
63
+ if (error instanceof RoomServerException) {
64
+ return error;
65
+ }
66
+ if (error instanceof ProtocolHandshakeException) {
67
+ return new RoomConnectionStatusException({
68
+ statusCode: error.statusCode,
69
+ statusText: error.statusText,
70
+ });
71
+ }
72
+ if (error instanceof ProtocolCloseException) {
73
+ return new RoomServerException(normalizeCloseReason(error.reason) ?? `room connection closed with status ${error.closeCode}`);
74
+ }
75
+ return new RoomServerException(`room connection error: ${String(error)}`);
76
+ }
77
+ function nonRetryableConnectFailureReason(error) {
78
+ if (error instanceof RoomConnectionStatusException
79
+ && (error.statusCode === 403 || error.statusCode === 404)) {
80
+ return error.message;
81
+ }
82
+ return null;
83
+ }
84
+ function roomClosedBeforeReadyError(protocol) {
85
+ return new RoomServerException(normalizeCloseReason(protocol.closeReason) ?? "room connection closed before request completed");
86
+ }
87
+ function getEnvironmentValue(name) {
88
+ if (typeof process === "undefined") {
89
+ return undefined;
90
+ }
91
+ return process.env?.[name];
92
+ }
93
+ function websocketRoomUrlFromEnvironment(roomName) {
94
+ const configuredBaseUrl = getEnvironmentValue("MESHAGENT_ROOM_URL")
95
+ ?? getEnvironmentValue("MESHAGENT_API_URL")
96
+ ?? "wss://api.meshagent.com";
97
+ let baseUrl = configuredBaseUrl;
98
+ if (baseUrl.startsWith("https:")) {
99
+ baseUrl = `wss:${baseUrl.slice("https:".length)}`;
100
+ }
101
+ else if (baseUrl.startsWith("http:")) {
102
+ baseUrl = `ws:${baseUrl.slice("http:".length)}`;
103
+ }
104
+ return `${baseUrl}/rooms/${roomName}`;
105
+ }
106
+ function createProtocolFactoryFromEnvironment() {
107
+ const roomName = getEnvironmentValue("MESHAGENT_ROOM");
108
+ const token = getEnvironmentValue("MESHAGENT_TOKEN");
109
+ if (roomName == null || roomName.trim().length === 0 || token == null || token.trim().length === 0) {
110
+ throw new Error("protocolFactory must be configured or MESHAGENT_ROOM and MESHAGENT_TOKEN must be set in the environment");
111
+ }
112
+ return WebSocketClientProtocol.createFactory({
113
+ url: websocketRoomUrlFromEnvironment(roomName),
114
+ token,
115
+ });
116
+ }
117
+ export class RoomProtocolProxy {
118
+ constructor({ room }) {
119
+ this._handlers = new Map();
120
+ this._room = room;
121
+ }
122
+ _bind(protocol) {
123
+ for (const [type, handler] of this._handlers.entries()) {
124
+ if (protocol.getHandler(type) === handler) {
125
+ continue;
126
+ }
127
+ protocol.addHandler(type, handler);
128
+ }
129
+ }
130
+ _unbind(protocol) {
131
+ for (const [type, handler] of this._handlers.entries()) {
132
+ const current = protocol.getHandler(type);
133
+ if (current === handler) {
134
+ protocol.removeHandler(type, handler);
135
+ }
136
+ }
137
+ }
138
+ addHandler(type, handler) {
139
+ if (this._handlers.has(type)) {
140
+ throw new Error(`already registered handler for ${type}`);
141
+ }
142
+ this._handlers.set(type, handler);
143
+ this._bind(this._room._protocolInstance);
144
+ }
145
+ removeHandler(type, handler) {
146
+ const registered = this._handlers.get(type);
147
+ if (registered !== handler) {
148
+ throw new Error(`handler mismatch for ${type}`);
149
+ }
150
+ this._handlers.delete(type);
151
+ if (this._room._protocolInstance.getHandler(type) === handler) {
152
+ this._room._protocolInstance.removeHandler(type, handler);
153
+ }
154
+ }
155
+ getHandler(type) {
156
+ return this._handlers.get(type);
157
+ }
158
+ async send(type, data, { id } = {}) {
159
+ if (this._room._entered && !this._room.isConnected && !this._room._allowDisconnectedRequests) {
160
+ throw this._room._disconnectedError({ baseMessage: "room connection is disconnected" });
161
+ }
162
+ await this._room._protocolInstance.send(type, data, id);
163
+ }
164
+ sendNowait(type, data, { id } = {}) {
165
+ if (this._room._entered && !this._room.isConnected && !this._room._allowDisconnectedRequests) {
166
+ throw this._room._disconnectedError({ baseMessage: "room connection is disconnected" });
167
+ }
168
+ return this._room._protocolInstance.sendNowait(type, data, { id });
169
+ }
170
+ getNextMessageId() {
171
+ if (this._room._entered && !this._room.isConnected && !this._room._allowDisconnectedRequests) {
172
+ throw this._room._disconnectedError({ baseMessage: "room connection is disconnected" });
173
+ }
174
+ return this._room._protocolInstance.getNextMessageId();
175
+ }
176
+ get done() {
177
+ return this._room.waitForClose();
178
+ }
179
+ async waitForClose() {
180
+ await this._room.waitForClose();
181
+ }
182
+ get closeKind() {
183
+ return this._room.closeKind;
184
+ }
185
+ get closeReason() {
186
+ return this._room.closeReason;
187
+ }
188
+ get isOpen() {
189
+ return this._room._protocolInstance.isOpen;
190
+ }
191
+ get isClosed() {
192
+ return this._room.isClosed;
193
+ }
194
+ get token() {
195
+ return this._room._protocolInstance.token;
196
+ }
197
+ get url() {
198
+ return this._room._protocolInstance.url;
199
+ }
200
+ }
18
201
  export class RoomClient {
19
- constructor({ protocol }) {
202
+ constructor({ protocolFactory = null, reconnectTimeout = null, } = {}) {
203
+ this._entered = false;
204
+ this._allowDisconnectedRequests = false;
205
+ this._eventsController = new StreamController();
206
+ this._eventEmitter = new EventEmitter();
20
207
  this._pendingRequests = new Map();
208
+ this._toolCallStreams = new Map();
209
+ this._ignoredResponseLabels = new Map();
21
210
  this._ready = new Completer();
211
+ this._roomClosed = new Completer();
212
+ this._connectionReady = new Completer();
213
+ this._localParticipantReady = new Completer();
214
+ this._connected = false;
215
+ this._closing = false;
22
216
  this._localParticipant = null;
23
- this._eventsController = new StreamController();
24
- this._toolCallStreams = new Map();
25
- this.protocol = protocol;
26
- protocol.addHandler("room_ready", this._handleRoomReady.bind(this));
27
- protocol.addHandler("connected", this._handleParticipant.bind(this));
28
- protocol.addHandler("__response__", this._handleResponse.bind(this));
29
- protocol.addHandler("room.tool_call_response_chunk", this._handleToolCallResponseChunk.bind(this));
217
+ this._lifecycleTask = null;
218
+ this._terminalState = null;
219
+ this._closeKind = null;
220
+ this._closeReason = null;
221
+ this._terminalCallbacksInvoked = false;
222
+ this._roomName = null;
223
+ this._roomUrl = null;
224
+ this._sessionId = null;
225
+ this._handleRoomReadyBound = this._handleRoomReady.bind(this);
226
+ this._handleRoomStatusBound = this._handleRoomStatus.bind(this);
227
+ this._handleParticipantBound = this._handleParticipant.bind(this);
228
+ this._handleResponseBound = this._handleResponse.bind(this);
229
+ this._handleToolCallResponseChunkBound = this._handleToolCallResponseChunk.bind(this);
230
+ if (reconnectTimeout != null && reconnectTimeout < 0) {
231
+ throw new Error("reconnectTimeout must be null or non-negative");
232
+ }
233
+ this._protocolFactory = protocolFactory ?? createProtocolFactoryFromEnvironment();
234
+ this._reconnectTimeout = reconnectTimeout;
235
+ this._protocolInstance = this._protocolFactory();
236
+ this.protocol = new RoomProtocolProxy({ room: this });
237
+ this.protocol.addHandler("room_ready", this._handleRoomReadyBound);
238
+ this.protocol.addHandler("room.status", this._handleRoomStatusBound);
239
+ this.protocol.addHandler("connected", this._handleParticipantBound);
240
+ this.protocol.addHandler("__response__", this._handleResponseBound);
241
+ this.protocol.addHandler("room.tool_call_response_chunk", this._handleToolCallResponseChunkBound);
30
242
  this.sync = new SyncClient({ room: this });
31
243
  this.storage = new StorageClient({ room: this });
32
244
  this.developer = new DeveloperClient({ room: this });
@@ -45,33 +257,820 @@ export class RoomClient {
45
257
  get ready() {
46
258
  return this._ready.fut;
47
259
  }
48
- async start({ onDone, onError } = {}) {
49
- await this.messaging.start();
50
- this.sync.start({ onDone, onError });
51
- await this.ready;
260
+ get isConnected() {
261
+ return this._connected;
52
262
  }
53
- dispose() {
54
- for (const prKey of this._pendingRequests.keys()) {
55
- const pr = this._pendingRequests.get(prKey);
56
- pr?.reject(new Error("Disposed"));
57
- this._pendingRequests.delete(prKey);
263
+ get isClosed() {
264
+ return this._closing || this._terminalState != null || this._roomClosed.completed;
265
+ }
266
+ get isClosing() {
267
+ return this._closing;
268
+ }
269
+ get closeKind() {
270
+ return this._closeKind ?? this._protocolInstance.closeKind;
271
+ }
272
+ get closeReason() {
273
+ return this._closeReason ?? normalizeCloseReason(this._protocolInstance.closeReason);
274
+ }
275
+ get roomName() {
276
+ return this._roomName;
277
+ }
278
+ get roomUrl() {
279
+ return this._roomUrl;
280
+ }
281
+ get sessionId() {
282
+ return this._sessionId;
283
+ }
284
+ isActiveProtocol(protocol) {
285
+ return protocol === this._protocolInstance;
286
+ }
287
+ on(eventName, callback) {
288
+ this._eventEmitter.on(eventName, callback);
289
+ }
290
+ off(eventName, callback) {
291
+ this._eventEmitter.off(eventName, callback);
292
+ }
293
+ emit(event) {
294
+ this._eventsController.add(event);
295
+ this._eventEmitter.emit(event.name, event);
296
+ }
297
+ listen() {
298
+ return this._eventsController.stream;
299
+ }
300
+ async waitForClose() {
301
+ await this._roomClosed.fut;
302
+ }
303
+ async waitUntilConnected() {
304
+ while (!this._connected) {
305
+ this._raiseIfTerminal();
306
+ if (this._roomClosed.completed) {
307
+ this._raiseIfTerminal();
308
+ throw this._disconnectedError({
309
+ baseMessage: "room connection closed before reconnect completed",
310
+ });
311
+ }
312
+ await new Promise((resolve) => setTimeout(resolve, 50));
58
313
  }
59
- this.messaging.dispose();
60
- this.sync.dispose();
61
- for (const stream of this._toolCallStreams.values()) {
62
- stream.close();
314
+ }
315
+ async _waitUntilConnectedForMessages() {
316
+ while (!this._connected) {
317
+ this._raiseIfTerminalForMessages();
318
+ if (this._roomClosed.completed) {
319
+ this._raiseIfTerminalForMessages();
320
+ throw this._messageDisconnectedError({
321
+ baseMessage: "room connection closed before message send completed",
322
+ });
323
+ }
324
+ await new Promise((resolve) => setTimeout(resolve, 50));
325
+ }
326
+ }
327
+ _markConnected() {
328
+ this._connected = true;
329
+ this._closeKind = null;
330
+ this._closeReason = null;
331
+ }
332
+ _markDisconnected({ reason, kind, }) {
333
+ this._connected = false;
334
+ this._closeKind = kind;
335
+ this._closeReason = normalizeCloseReason(reason);
336
+ this._ignoredResponseLabels.clear();
337
+ }
338
+ _completeRoomClosed() {
339
+ if (!this._roomClosed.completed) {
340
+ this._roomClosed.complete();
341
+ }
342
+ }
343
+ _invokeTerminalCallbacks({ useErrorCallback, error, }) {
344
+ if (this._terminalCallbacksInvoked) {
345
+ return;
346
+ }
347
+ this._terminalCallbacksInvoked = true;
348
+ if (useErrorCallback) {
349
+ this._errorHandler?.(error);
350
+ return;
351
+ }
352
+ this._doneHandler?.();
353
+ }
354
+ _formatClosedMessage({ baseMessage, protocol, closeReason, }) {
355
+ const normalized = normalizeCloseReason(closeReason) ??
356
+ normalizeCloseReason((protocol ?? this._protocolInstance).closeReason);
357
+ if (normalized == null) {
358
+ return baseMessage;
359
+ }
360
+ return `${baseMessage}: ${normalized}`;
361
+ }
362
+ _connectionFailureReason(error) {
363
+ if (error instanceof RoomServerException) {
364
+ return normalizeCloseReason(error.message);
365
+ }
366
+ return normalizeCloseReason(String(error));
367
+ }
368
+ _protocolTerminalState({ protocol } = {}) {
369
+ return new RoomClientTerminalState({
370
+ requestMessage: this._formatClosedMessage({
371
+ baseMessage: "room connection closed before request completed",
372
+ protocol,
373
+ }),
374
+ toolCallMessage: this._formatClosedMessage({
375
+ baseMessage: "room connection closed before tool call completed",
376
+ protocol,
377
+ }),
378
+ messageSendMessage: this._formatClosedMessage({
379
+ baseMessage: "room connection closed before message send completed",
380
+ protocol,
381
+ }),
382
+ });
383
+ }
384
+ _clientClosedTerminalState() {
385
+ return new RoomClientTerminalState({
386
+ requestMessage: "room client was closed before request completed",
387
+ toolCallMessage: "room client was closed before tool call completed",
388
+ messageSendMessage: "room client was closed before message send completed",
389
+ });
390
+ }
391
+ _unexpectedCloseTerminalState({ closeReason, }) {
392
+ return new RoomClientTerminalState({
393
+ requestMessage: this._formatClosedMessage({
394
+ baseMessage: "room connection unexpectedly closed before request completed",
395
+ closeReason,
396
+ }),
397
+ toolCallMessage: this._formatClosedMessage({
398
+ baseMessage: "room connection unexpectedly closed before tool call completed",
399
+ closeReason,
400
+ }),
401
+ messageSendMessage: this._formatClosedMessage({
402
+ baseMessage: "room connection unexpectedly closed before message send completed",
403
+ closeReason,
404
+ }),
405
+ });
406
+ }
407
+ _setStartupTerminalState({ closeKind, closeReason, protocol, }) {
408
+ const normalizedCloseReason = normalizeCloseReason(closeReason);
409
+ this._closeKind = closeKind;
410
+ this._closeReason = normalizedCloseReason;
411
+ if (closeKind === ProtocolCloseKind.ERROR) {
412
+ this._setTerminalState({
413
+ state: this._unexpectedCloseTerminalState({ closeReason: normalizedCloseReason }),
414
+ });
415
+ }
416
+ else if (closeKind === ProtocolCloseKind.CLIENT) {
417
+ this._setTerminalState({ state: this._clientClosedTerminalState() });
418
+ }
419
+ else {
420
+ this._setTerminalState({ state: this._protocolTerminalState({ protocol }) });
421
+ }
422
+ if (!this._ready.completed) {
423
+ this._ready.completeError(this._startupException({
424
+ closeKind,
425
+ closeReason: normalizedCloseReason,
426
+ protocol,
427
+ }));
428
+ }
429
+ this._completeRoomClosed();
430
+ }
431
+ _setTerminalState({ state }) {
432
+ if (this._terminalState == null) {
433
+ this._terminalState = state;
434
+ }
435
+ return this._terminalState;
436
+ }
437
+ _raiseIfTerminal() {
438
+ if (this._terminalState != null) {
439
+ throw this._terminalState.requestError();
440
+ }
441
+ }
442
+ _raiseIfTerminalForMessages() {
443
+ if (this._terminalState != null) {
444
+ throw this._terminalState.messageSendError();
445
+ }
446
+ }
447
+ _disconnectedError({ baseMessage }) {
448
+ return new RoomServerException(this._formatClosedMessage({
449
+ baseMessage,
450
+ }));
451
+ }
452
+ _messageDisconnectedError({ baseMessage, }) {
453
+ return new RoomServerException(this._formatClosedMessage({
454
+ baseMessage,
455
+ }));
456
+ }
457
+ _startupException({ closeKind, closeReason, protocol, }) {
458
+ const baseMessage = closeKind === ProtocolCloseKind.ERROR
459
+ ? "room connection unexpectedly closed before the room became ready"
460
+ : closeKind === ProtocolCloseKind.CLIENT
461
+ ? "room client was closed before the room became ready"
462
+ : "room connection closed before the room became ready";
463
+ return new RoomServerException(this._formatClosedMessage({
464
+ baseMessage,
465
+ protocol,
466
+ closeReason,
467
+ }));
468
+ }
469
+ _finalizeInitialStartupRetryFailure({ retryResult, }) {
470
+ const closeKind = retryResult.closeKind ?? null;
471
+ if (closeKind == null) {
472
+ throw new Error("initial startup retry failure requires a close kind");
473
+ }
474
+ this._setStartupTerminalState({
475
+ closeKind,
476
+ closeReason: retryResult.closeReason ?? null,
477
+ protocol: this._protocolInstance,
478
+ });
479
+ throw this._startupException({
480
+ closeKind,
481
+ closeReason: retryResult.closeReason ?? null,
482
+ protocol: this._protocolInstance,
483
+ });
484
+ }
485
+ _coerceMessageSendError(error) {
486
+ if (this._terminalState == null) {
487
+ return error;
488
+ }
489
+ if (error.message === this._terminalState.requestMessage ||
490
+ error.message === this._terminalState.toolCallMessage) {
491
+ return this._terminalState.messageSendError();
492
+ }
493
+ return error;
494
+ }
495
+ _messageStopError() {
496
+ if (this._closing && this._terminalState != null) {
497
+ return this._terminalState.messageSendError();
498
+ }
499
+ return new RoomServerException("Cannot send messages because messaging has been stopped");
500
+ }
501
+ _failPendingRequests(error) {
502
+ if (this._pendingRequests.size === 0) {
503
+ return;
504
+ }
505
+ const pending = [...this._pendingRequests.values()];
506
+ this._pendingRequests.clear();
507
+ for (const request of pending) {
508
+ if (!request.completed) {
509
+ request.completeError(error);
510
+ }
63
511
  }
512
+ }
513
+ async _failToolCallStreams({ error, }) {
514
+ if (this._toolCallStreams.size === 0) {
515
+ return;
516
+ }
517
+ const streams = [...this._toolCallStreams.values()];
64
518
  this._toolCallStreams.clear();
65
- this.protocol.dispose();
519
+ for (const stream of streams) {
520
+ stream.add(new ErrorContent({ text: error.message }));
521
+ stream.close();
522
+ }
523
+ }
524
+ async _failPendingWork({ state, }) {
525
+ this._failPendingRequests(state.requestError());
526
+ await this._failToolCallStreams({ error: state.toolCallError() });
527
+ }
528
+ async _openProtocol({ initial }) {
529
+ const protocol = this._protocolInstance;
530
+ this._connectionReady = new Completer();
531
+ this._localParticipantReady = new Completer();
532
+ protocol.start({
533
+ onDone: () => {
534
+ const error = roomClosedBeforeReadyError(protocol);
535
+ if (!this._connectionReady.completed) {
536
+ this._connectionReady.completeError(error);
537
+ }
538
+ if (!this._localParticipantReady.completed) {
539
+ this._localParticipantReady.completeError(error);
540
+ }
541
+ if (!initial && !this._ready.completed) {
542
+ this._ready.completeError(error);
543
+ }
544
+ },
545
+ onError: (error) => {
546
+ const wrapped = wrapRoomConnectionError(error);
547
+ if (!this._connectionReady.completed) {
548
+ this._connectionReady.completeError(wrapped);
549
+ }
550
+ if (!this._localParticipantReady.completed) {
551
+ this._localParticipantReady.completeError(wrapped);
552
+ }
553
+ if (!initial && !this._ready.completed) {
554
+ this._ready.completeError(wrapped);
555
+ }
556
+ },
557
+ });
558
+ try {
559
+ await Promise.all([this._connectionReady.fut, this._localParticipantReady.fut]);
560
+ }
561
+ catch (error) {
562
+ const kind = protocol.closeKind ?? ProtocolCloseKind.ERROR;
563
+ if (!initial && kind !== ProtocolCloseKind.ERROR) {
564
+ throw new ProtocolStartupFailure({
565
+ kind,
566
+ reason: normalizeCloseReason(protocol.closeReason),
567
+ });
568
+ }
569
+ throw error;
570
+ }
571
+ }
572
+ async start({ onDone, onError, } = {}) {
573
+ if (this._entered) {
574
+ throw new RoomServerException("room client already started");
575
+ }
576
+ this._doneHandler = onDone;
577
+ this._errorHandler = onError;
578
+ try {
579
+ try {
580
+ await this._openProtocol({ initial: true });
581
+ }
582
+ catch (error) {
583
+ if (error instanceof ProtocolStartupFailure) {
584
+ if (error.kind !== ProtocolCloseKind.ERROR || this._reconnectTimeout === 0) {
585
+ this._setStartupTerminalState({
586
+ closeKind: error.kind,
587
+ closeReason: error.reason,
588
+ protocol: this._protocolInstance,
589
+ });
590
+ throw this._startupException({
591
+ closeKind: error.kind,
592
+ closeReason: error.reason,
593
+ protocol: this._protocolInstance,
594
+ });
595
+ }
596
+ await this._closeProtocol(this._protocolInstance);
597
+ const retryResult = await this._retryProtocolConnection({
598
+ disconnectReason: error.reason,
599
+ protocolFactoryFailureLogMessage: "unable to create replacement room protocol during initial startup",
600
+ attemptFailureLogMessage: "room startup attempt failed",
601
+ attempt: this._attemptInitialProtocolStartup.bind(this),
602
+ });
603
+ if (!retryResult.connected) {
604
+ this._finalizeInitialStartupRetryFailure({ retryResult });
605
+ }
606
+ }
607
+ else {
608
+ const nonRetryableCloseReason = nonRetryableConnectFailureReason(error);
609
+ if (nonRetryableCloseReason != null) {
610
+ this._finalizeInitialStartupRetryFailure({
611
+ retryResult: {
612
+ connected: false,
613
+ closeKind: ProtocolCloseKind.ERROR,
614
+ closeReason: nonRetryableCloseReason,
615
+ },
616
+ });
617
+ }
618
+ const closeKind = this._protocolInstance.closeKind;
619
+ const protocolCloseReason = normalizeCloseReason(this._protocolInstance.closeReason);
620
+ if (closeKind != null && closeKind !== ProtocolCloseKind.ERROR) {
621
+ this._setStartupTerminalState({
622
+ closeKind,
623
+ closeReason: protocolCloseReason,
624
+ protocol: this._protocolInstance,
625
+ });
626
+ throw this._startupException({
627
+ closeKind,
628
+ closeReason: protocolCloseReason,
629
+ protocol: this._protocolInstance,
630
+ });
631
+ }
632
+ const closeReason = this._connectionFailureReason(error);
633
+ if (this._reconnectTimeout === 0) {
634
+ this._setStartupTerminalState({
635
+ closeKind: ProtocolCloseKind.ERROR,
636
+ closeReason,
637
+ protocol: this._protocolInstance,
638
+ });
639
+ throw this._startupException({
640
+ closeKind: ProtocolCloseKind.ERROR,
641
+ closeReason,
642
+ protocol: this._protocolInstance,
643
+ });
644
+ }
645
+ console.debug("room startup attempt failed", error);
646
+ await this._closeProtocol(this._protocolInstance);
647
+ const retryResult = await this._retryProtocolConnection({
648
+ disconnectReason: closeReason,
649
+ protocolFactoryFailureLogMessage: "unable to create replacement room protocol during initial startup",
650
+ attemptFailureLogMessage: "room startup attempt failed",
651
+ attempt: this._attemptInitialProtocolStartup.bind(this),
652
+ });
653
+ if (!retryResult.connected) {
654
+ this._finalizeInitialStartupRetryFailure({ retryResult });
655
+ }
656
+ }
657
+ }
658
+ this.sync.start();
659
+ this.messaging.start();
660
+ this._entered = true;
661
+ this._markConnected();
662
+ this.messaging._onRoomReconnect();
663
+ this._lifecycleTask = this._connectionLifecycle();
664
+ }
665
+ catch (error) {
666
+ this.sync.dispose();
667
+ void this.messaging.stop();
668
+ this._protocolInstance.dispose();
669
+ throw error;
670
+ }
671
+ await this.ready;
672
+ }
673
+ async _completeReconnect() {
674
+ await this._openProtocol({ initial: false });
675
+ this._allowDisconnectedRequests = true;
676
+ try {
677
+ this._resendLocalAttributesNowait();
678
+ await this.sync._onRoomReconnect();
679
+ this.messaging._onRoomReconnect();
680
+ this._markConnected();
681
+ }
682
+ finally {
683
+ this._allowDisconnectedRequests = false;
684
+ }
685
+ }
686
+ _replaceProtocol(nextProtocol) {
687
+ const currentProtocol = this._protocolInstance;
688
+ this.protocol._unbind(currentProtocol);
689
+ this._protocolInstance = nextProtocol;
690
+ this.protocol._bind(nextProtocol);
691
+ }
692
+ _remainingReconnectTimeout(deadline) {
693
+ if (deadline == null) {
694
+ return null;
695
+ }
696
+ const remaining = deadline - Date.now();
697
+ return remaining <= 0 ? 0 : remaining;
698
+ }
699
+ async _attemptInitialProtocolStartup({ protocol, remaining, }) {
700
+ void protocol;
701
+ if (remaining == null) {
702
+ await this._openProtocol({ initial: false });
703
+ return;
704
+ }
705
+ await Promise.race([
706
+ this._openProtocol({ initial: false }),
707
+ new Promise((_resolve, reject) => {
708
+ setTimeout(() => reject(new Error("timeout")), remaining);
709
+ }),
710
+ ]);
711
+ }
712
+ async _attemptReconnect({ protocol, remaining, }) {
713
+ try {
714
+ if (remaining == null) {
715
+ await this._completeReconnect();
716
+ }
717
+ else {
718
+ await Promise.race([
719
+ this._completeReconnect(),
720
+ new Promise((_resolve, reject) => {
721
+ setTimeout(() => reject(new Error("timeout")), remaining);
722
+ }),
723
+ ]);
724
+ }
725
+ }
726
+ catch (error) {
727
+ if (error instanceof ProtocolStartupFailure) {
728
+ throw error;
729
+ }
730
+ if (error instanceof Error && error.message === "timeout") {
731
+ this._allowDisconnectedRequests = false;
732
+ await this.sync._onRoomDisconnect();
733
+ this.messaging._onRoomDisconnect({ reason: normalizeCloseReason(protocol.closeReason) });
734
+ throw error;
735
+ }
736
+ this._allowDisconnectedRequests = false;
737
+ await this.sync._onRoomDisconnect();
738
+ this.messaging._onRoomDisconnect({ reason: normalizeCloseReason(protocol.closeReason) });
739
+ throw error;
740
+ }
741
+ }
742
+ _formatDuration(milliseconds) {
743
+ if (milliseconds % 1000 === 0) {
744
+ return `${milliseconds / 1000}s`;
745
+ }
746
+ return `${milliseconds / 1000}s`;
747
+ }
748
+ _reconnectTimeoutReason({ disconnectReason, }) {
749
+ if (this._reconnectTimeout == null) {
750
+ throw new Error("reconnect timeout reason requires a configured timeout");
751
+ }
752
+ const timeoutDisplay = this._formatDuration(this._reconnectTimeout);
753
+ if (disconnectReason == null) {
754
+ return `room reconnect timed out after ${timeoutDisplay}`;
755
+ }
756
+ return `room reconnect timed out after ${timeoutDisplay} (${disconnectReason})`;
757
+ }
758
+ _timedOutRetryResult({ disconnectReason, }) {
759
+ if (this._reconnectTimeout == null) {
760
+ throw new Error("timed out retry result requires a configured timeout");
761
+ }
762
+ return {
763
+ connected: false,
764
+ closeKind: ProtocolCloseKind.ERROR,
765
+ closeReason: this._reconnectTimeoutReason({ disconnectReason }),
766
+ };
767
+ }
768
+ async _closeAfterUnexpectedDisconnect({ closeReason, }) {
769
+ const normalized = normalizeCloseReason(closeReason);
770
+ const state = this._unexpectedCloseTerminalState({ closeReason: normalized });
771
+ this._closeKind = ProtocolCloseKind.ERROR;
772
+ this._closeReason = normalized;
773
+ this._setTerminalState({ state });
774
+ this._completeRoomClosed();
775
+ this._invokeTerminalCallbacks({
776
+ useErrorCallback: true,
777
+ error: state.requestError(),
778
+ });
779
+ }
780
+ async _closeProtocol(protocol) {
781
+ protocol.dispose();
782
+ await protocol.waitForClose();
783
+ }
784
+ async _retryProtocolConnection({ disconnectReason, protocolFactoryFailureLogMessage, attemptFailureLogMessage, attempt, }) {
785
+ let failureReason = normalizeCloseReason(disconnectReason);
786
+ const recordFailureReason = (reason) => {
787
+ const normalizedReason = normalizeCloseReason(reason);
788
+ if (failureReason == null && normalizedReason != null) {
789
+ failureReason = normalizedReason;
790
+ }
791
+ };
792
+ const deadline = this._reconnectTimeout == null ? null : Date.now() + this._reconnectTimeout;
793
+ let firstAttempt = true;
794
+ while (!this._closing) {
795
+ if (firstAttempt) {
796
+ firstAttempt = false;
797
+ if (this._reconnectTimeout == null) {
798
+ await new Promise((resolve) => setTimeout(resolve, RoomClient.RECONNECT_RETRY_INTERVAL_MS));
799
+ }
800
+ }
801
+ else {
802
+ const remaining = this._remainingReconnectTimeout(deadline);
803
+ if (remaining != null && remaining === 0) {
804
+ return this._timedOutRetryResult({ disconnectReason: failureReason });
805
+ }
806
+ const delay = remaining == null
807
+ ? RoomClient.RECONNECT_RETRY_INTERVAL_MS
808
+ : Math.min(remaining, RoomClient.RECONNECT_RETRY_INTERVAL_MS);
809
+ if (delay > 0) {
810
+ await new Promise((resolve) => setTimeout(resolve, delay));
811
+ }
812
+ }
813
+ const remaining = this._remainingReconnectTimeout(deadline);
814
+ if (remaining != null && remaining === 0) {
815
+ return this._timedOutRetryResult({ disconnectReason: failureReason });
816
+ }
817
+ let nextProtocol;
818
+ try {
819
+ nextProtocol = this._protocolFactory();
820
+ }
821
+ catch (error) {
822
+ if (error instanceof ProtocolReconnectUnsupportedException) {
823
+ return {
824
+ connected: false,
825
+ closeKind: ProtocolCloseKind.ERROR,
826
+ closeReason: failureReason,
827
+ };
828
+ }
829
+ recordFailureReason(String(error));
830
+ console.debug(protocolFactoryFailureLogMessage, error);
831
+ continue;
832
+ }
833
+ this._replaceProtocol(nextProtocol);
834
+ try {
835
+ await attempt({ protocol: nextProtocol, remaining });
836
+ }
837
+ catch (error) {
838
+ if (error instanceof Error && error.message === "timeout") {
839
+ recordFailureReason(normalizeCloseReason(nextProtocol.closeReason));
840
+ await this._closeProtocol(nextProtocol);
841
+ return this._timedOutRetryResult({ disconnectReason: failureReason });
842
+ }
843
+ if (error instanceof ProtocolStartupFailure) {
844
+ recordFailureReason(error.reason);
845
+ await this._closeProtocol(nextProtocol);
846
+ if (error.kind !== ProtocolCloseKind.ERROR) {
847
+ return {
848
+ connected: false,
849
+ closeKind: error.kind,
850
+ closeReason: error.reason,
851
+ };
852
+ }
853
+ continue;
854
+ }
855
+ const nonRetryableCloseReason = nonRetryableConnectFailureReason(error);
856
+ if (nonRetryableCloseReason != null) {
857
+ await this._closeProtocol(nextProtocol);
858
+ return {
859
+ connected: false,
860
+ closeKind: ProtocolCloseKind.ERROR,
861
+ closeReason: nonRetryableCloseReason,
862
+ };
863
+ }
864
+ recordFailureReason(this._connectionFailureReason(error));
865
+ console.debug(attemptFailureLogMessage, error);
866
+ await this._closeProtocol(nextProtocol);
867
+ continue;
868
+ }
869
+ return { connected: true };
870
+ }
871
+ return {
872
+ connected: false,
873
+ closeKind: ProtocolCloseKind.CLIENT,
874
+ closeReason: this.closeReason,
875
+ };
876
+ }
877
+ async _reconnect({ disconnectReason, }) {
878
+ const retryResult = await this._retryProtocolConnection({
879
+ disconnectReason,
880
+ protocolFactoryFailureLogMessage: "unable to create replacement room protocol",
881
+ attemptFailureLogMessage: "room reconnect attempt failed",
882
+ attempt: this._attemptReconnect.bind(this),
883
+ });
884
+ if (retryResult.connected) {
885
+ this._emitStatus({
886
+ status: "reconnected",
887
+ message: "room connection restored",
888
+ });
889
+ return true;
890
+ }
891
+ const closeKind = retryResult.closeKind ?? null;
892
+ if (closeKind === ProtocolCloseKind.ERROR) {
893
+ const closeReason = retryResult.closeReason ?? null;
894
+ if (closeReason != null && closeReason.startsWith("room reconnect timed out after")) {
895
+ console.warn(`${closeReason}; closing room client`);
896
+ }
897
+ await this._closeAfterUnexpectedDisconnect({ closeReason });
898
+ return false;
899
+ }
900
+ if (closeKind == null) {
901
+ throw new Error("reconnect failure requires a close kind");
902
+ }
903
+ const state = this._protocolTerminalState({ protocol: this._protocolInstance });
904
+ this._setTerminalState({ state });
905
+ this._closeKind = closeKind;
906
+ this._closeReason = normalizeCloseReason(retryResult.closeReason ?? null);
907
+ this._completeRoomClosed();
908
+ this._invokeTerminalCallbacks({ useErrorCallback: false });
909
+ return false;
910
+ }
911
+ async _connectionLifecycle() {
912
+ while (true) {
913
+ const protocol = this._protocolInstance;
914
+ await protocol.done;
915
+ const closeKind = protocol.closeKind ?? ProtocolCloseKind.ERROR;
916
+ const closeReason = normalizeCloseReason(protocol.closeReason);
917
+ const state = this._protocolTerminalState({ protocol });
918
+ if (this._closing) {
919
+ this._completeRoomClosed();
920
+ return;
921
+ }
922
+ if (closeKind !== ProtocolCloseKind.ERROR) {
923
+ this._setTerminalState({ state });
924
+ }
925
+ this._markDisconnected({ reason: closeReason, kind: closeKind });
926
+ this._emitStatus({
927
+ status: "disconnected",
928
+ message: closeReason ?? "room connection lost",
929
+ });
930
+ await this.sync._onRoomDisconnect();
931
+ this.messaging._onRoomDisconnect({ reason: closeReason });
932
+ await this._failPendingWork({ state });
933
+ await this._closeProtocol(protocol);
934
+ if (closeKind === ProtocolCloseKind.ERROR) {
935
+ if (this._reconnectTimeout === 0) {
936
+ if (closeReason == null) {
937
+ console.warn("room connection lost; automatic reconnect disabled");
938
+ }
939
+ else {
940
+ console.warn(`room connection lost (${closeReason}); automatic reconnect disabled`);
941
+ }
942
+ await this._closeAfterUnexpectedDisconnect({ closeReason });
943
+ return;
944
+ }
945
+ if (closeReason == null) {
946
+ console.warn("room connection lost; automatically attempting to reconnect");
947
+ }
948
+ else {
949
+ console.warn(`room connection lost (${closeReason}); automatically attempting to reconnect`);
950
+ }
951
+ if (await this._reconnect({ disconnectReason: closeReason })) {
952
+ continue;
953
+ }
954
+ return;
955
+ }
956
+ this._closeKind = closeKind;
957
+ this._closeReason = closeReason;
958
+ this._completeRoomClosed();
959
+ this._invokeTerminalCallbacks({ useErrorCallback: false });
960
+ return;
961
+ }
962
+ }
963
+ dispose() {
964
+ this._closing = true;
965
+ this._markDisconnected({
966
+ reason: this.closeReason,
967
+ kind: this.closeKind ?? ProtocolCloseKind.CLIENT,
968
+ });
969
+ const closingState = this._clientClosedTerminalState();
970
+ this._setTerminalState({ state: closingState });
971
+ this._failPendingRequests(closingState.requestError());
972
+ void this._failToolCallStreams({ error: closingState.toolCallError() });
973
+ this.sync.dispose();
974
+ void this.messaging.stop();
975
+ this._protocolInstance.dispose();
976
+ this._entered = false;
977
+ this._closeKind = ProtocolCloseKind.CLIENT;
978
+ this._completeRoomClosed();
979
+ this._invokeTerminalCallbacks({ useErrorCallback: false });
66
980
  this._localParticipant = null;
67
981
  }
982
+ _sendProtocolNowait({ type, data, label, messageId, expectResponse = false, }) {
983
+ try {
984
+ this._raiseIfTerminal();
985
+ }
986
+ catch (error) {
987
+ console.debug(`skipping ${label} because the room is closed`, error);
988
+ return null;
989
+ }
990
+ if (this._entered && !this._connected && !this._allowDisconnectedRequests) {
991
+ console.debug(`skipping ${label} while room is disconnected`);
992
+ return null;
993
+ }
994
+ const protocol = this._protocolInstance;
995
+ const resolvedMessageId = messageId ?? protocol.getNextMessageId();
996
+ if (expectResponse) {
997
+ this._ignoredResponseLabels.set(resolvedMessageId, label);
998
+ }
999
+ try {
1000
+ protocol.sendNowait(type, data, { id: resolvedMessageId });
1001
+ }
1002
+ catch (error) {
1003
+ this._ignoredResponseLabels.delete(resolvedMessageId);
1004
+ if (this.isClosed) {
1005
+ console.debug(`skipping ${label} because the room is closed`, error);
1006
+ }
1007
+ else {
1008
+ console.warn(`unable to queue ${label}`, error);
1009
+ }
1010
+ return null;
1011
+ }
1012
+ return resolvedMessageId;
1013
+ }
1014
+ _sendRoomRequestNowait(type, request, { data, label, expectResponse = false, }) {
1015
+ return this._sendProtocolNowait({
1016
+ type,
1017
+ data: packMessage(request, data),
1018
+ label,
1019
+ expectResponse,
1020
+ });
1021
+ }
1022
+ invokeNowait({ toolkit, tool, input, participantId, onBehalfOfId, callerContext, }) {
1023
+ const resolvedInput = input ?? new EmptyContent();
1024
+ const packedInput = unpackMessage(resolvedInput.pack());
1025
+ const request = {
1026
+ toolkit,
1027
+ tool,
1028
+ participant_id: participantId,
1029
+ on_behalf_of_id: onBehalfOfId,
1030
+ caller_context: callerContext,
1031
+ tool_call_id: `${Date.now()}-${Math.random().toString(16).slice(2)}`,
1032
+ arguments: packedInput[0],
1033
+ };
1034
+ this._sendRoomRequestNowait("room.invoke_tool", request, {
1035
+ data: packedInput[1].length > 0 ? packedInput[1] : undefined,
1036
+ label: `${toolkit}.${tool}`,
1037
+ expectResponse: true,
1038
+ });
1039
+ }
1040
+ _sendLocalAttributesNowait(attributes) {
1041
+ this._sendProtocolNowait({
1042
+ type: "set_attributes",
1043
+ data: packMessage(attributes),
1044
+ label: "local participant attribute update",
1045
+ });
1046
+ }
1047
+ _resendLocalAttributesNowait() {
1048
+ const localParticipant = this._localParticipant;
1049
+ if (localParticipant == null) {
1050
+ return;
1051
+ }
1052
+ const attributes = localParticipant._attributesSnapshot();
1053
+ if (Object.keys(attributes).length === 0) {
1054
+ return;
1055
+ }
1056
+ this._sendLocalAttributesNowait(attributes);
1057
+ }
68
1058
  async sendRequest(type, request, data) {
69
- const requestId = this.protocol.getNextMessageId();
70
- const pr = new Completer();
71
- this._pendingRequests.set(requestId, pr);
72
- const message = packMessage(request, data);
73
- await this.protocol.send(type, message, requestId);
74
- return await pr.fut;
1059
+ this._raiseIfTerminal();
1060
+ if (this._entered && !this._connected && !this._allowDisconnectedRequests) {
1061
+ throw this._disconnectedError({ baseMessage: "room connection is disconnected" });
1062
+ }
1063
+ const requestId = this._protocolInstance.getNextMessageId();
1064
+ const completer = new Completer();
1065
+ this._pendingRequests.set(requestId, completer);
1066
+ try {
1067
+ await this._protocolInstance.send(type, packMessage(request, data), requestId);
1068
+ return await completer.fut;
1069
+ }
1070
+ catch (error) {
1071
+ this._pendingRequests.delete(requestId);
1072
+ throw error;
1073
+ }
75
1074
  }
76
1075
  async call(params) {
77
1076
  await this.sendRequest("room.call", params);
@@ -146,7 +1145,7 @@ export class RoomClient {
146
1145
  return await this.sendRequest("room.invoke_tool", request, requestData);
147
1146
  }
148
1147
  async invokeWithStreamInput(params) {
149
- const toolCallId = `${Date.now()}-${this.protocol.getNextMessageId()}-${Math.random().toString(16).slice(2)}`;
1148
+ const toolCallId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
150
1149
  const request = {
151
1150
  toolkit: params.toolkit,
152
1151
  tool: params.tool,
@@ -177,7 +1176,7 @@ export class RoomClient {
177
1176
  }
178
1177
  }
179
1178
  async invokeStream(params) {
180
- const toolCallId = `${Date.now()}-${this.protocol.getNextMessageId()}-${Math.random().toString(16).slice(2)}`;
1179
+ const toolCallId = `${Date.now()}-${Math.random().toString(16).slice(2)}`;
181
1180
  const controller = new StreamController();
182
1181
  const responseIterator = controller.stream[Symbol.asyncIterator]();
183
1182
  this._toolCallStreams.set(toolCallId, controller);
@@ -199,7 +1198,7 @@ export class RoomClient {
199
1198
  const requestTask = this._streamInvokeToolRequestChunks(toolCallId, params.input);
200
1199
  void requestTask.catch((error) => {
201
1200
  const stream = this._toolCallStreams.get(toolCallId);
202
- if (!stream) {
1201
+ if (stream == null) {
203
1202
  return;
204
1203
  }
205
1204
  stream.add(new ErrorContent({ text: `request stream failed: ${String(error)}` }));
@@ -249,8 +1248,8 @@ export class RoomClient {
249
1248
  }
250
1249
  return new JsonContent({ json: { chunk } });
251
1250
  }
252
- async _handleToolCallResponseChunk(protocol, messageId, type, data) {
253
- if (!data) {
1251
+ async _handleToolCallResponseChunk(protocol, _messageId, _type, data) {
1252
+ if (!this.isActiveProtocol(protocol)) {
254
1253
  return;
255
1254
  }
256
1255
  const [header, payload] = unpackMessage(data);
@@ -259,7 +1258,7 @@ export class RoomClient {
259
1258
  return;
260
1259
  }
261
1260
  const stream = this._toolCallStreams.get(toolCallId);
262
- if (!stream) {
1261
+ if (stream == null) {
263
1262
  return;
264
1263
  }
265
1264
  const content = this._decodeToolCallContent({ header, payload });
@@ -269,51 +1268,91 @@ export class RoomClient {
269
1268
  this._toolCallStreams.delete(toolCallId);
270
1269
  }
271
1270
  }
272
- async _handleResponse(protocol, messageId, type, data) {
273
- if (!data) {
274
- console.error("No data in response");
1271
+ async _handleResponse(protocol, messageId, _type, data) {
1272
+ if (!this.isActiveProtocol(protocol)) {
275
1273
  return;
276
1274
  }
277
1275
  const response = unpackContent(data);
278
- console.log("GOT RESPONSE", response);
279
- if (!response) {
280
- console.error("No response");
281
- return;
282
- }
283
- if (this._pendingRequests.has(messageId)) {
284
- const pr = this._pendingRequests.get(messageId);
1276
+ const pending = this._pendingRequests.get(messageId);
1277
+ if (pending != null) {
285
1278
  this._pendingRequests.delete(messageId);
286
1279
  if (response instanceof ErrorContent) {
287
- pr.reject(new RoomServerException(response.text, response.code));
1280
+ pending.completeError(new RoomServerException(response.text, response.code));
288
1281
  }
289
1282
  else {
290
- pr.resolve(response);
1283
+ pending.complete(response);
291
1284
  }
1285
+ return;
292
1286
  }
293
- else {
294
- console.warn(`Received a response for a request that is not pending ${messageId}`);
1287
+ const ignoredLabel = this._ignoredResponseLabels.get(messageId);
1288
+ if (ignoredLabel != null) {
1289
+ this._ignoredResponseLabels.delete(messageId);
1290
+ if (response instanceof ErrorContent) {
1291
+ console.warn(`one-way room request failed for ${ignoredLabel}: ${response.text}`);
1292
+ }
295
1293
  }
296
1294
  }
297
- async _handleRoomReady(protocol, messageId, type, data) {
298
- const [message, _] = unpackMessage(data);
299
- this._ready.complete(message["room_name"]);
1295
+ async _handleRoomStatus(protocol, _messageId, _type, data) {
1296
+ if (!this.isActiveProtocol(protocol)) {
1297
+ return;
1298
+ }
1299
+ const [payload] = unpackMessage(data);
1300
+ this.emit(RoomStatusEvent.fromJson(payload));
1301
+ }
1302
+ async _handleRoomReady(protocol, _messageId, _type, data) {
1303
+ if (!this.isActiveProtocol(protocol)) {
1304
+ return;
1305
+ }
1306
+ const [message] = unpackMessage(data);
1307
+ this._roomName = typeof message["room_name"] === "string" ? message["room_name"] : null;
1308
+ this._roomUrl = typeof message["room_url"] === "string" ? message["room_url"] : null;
1309
+ this._sessionId = typeof message["session_id"] === "string" ? message["session_id"] : null;
1310
+ if (!this._ready.completed) {
1311
+ this._ready.complete();
1312
+ }
1313
+ if (!this._connectionReady.completed) {
1314
+ this._connectionReady.complete();
1315
+ }
300
1316
  }
301
1317
  _onParticipantInit(participantId, attributes) {
302
- this._localParticipant = new LocalParticipant(this, participantId);
303
- for (const k in attributes) {
304
- this._localParticipant.setAttribute(k, attributes[k]);
1318
+ if (this._localParticipant == null) {
1319
+ this._localParticipant = new LocalParticipant(this, participantId);
1320
+ this._localParticipant._setAttributes(attributes);
1321
+ }
1322
+ else {
1323
+ const merged = { ...attributes, ...this._localParticipant._attributesSnapshot() };
1324
+ this._localParticipant._replaceIdentity({
1325
+ participantId,
1326
+ attributes: merged,
1327
+ });
1328
+ }
1329
+ if (!this._localParticipantReady.completed) {
1330
+ this._localParticipantReady.complete();
305
1331
  }
306
1332
  }
307
- async _handleParticipant(protocol, messageId, type, data) {
308
- const [message, _] = unpackMessage(data);
1333
+ async _handleParticipant(protocol, _messageId, _type, data) {
1334
+ if (!this.isActiveProtocol(protocol)) {
1335
+ return;
1336
+ }
1337
+ const [message] = unpackMessage(data);
309
1338
  switch (message["type"]) {
310
- case "init": this._onParticipantInit(message["participantId"], message["attributes"]);
1339
+ case "init": {
1340
+ const participantId = message["participantId"];
1341
+ const attributes = message["attributes"];
1342
+ if (typeof participantId === "string" &&
1343
+ typeof attributes === "object" &&
1344
+ attributes !== null &&
1345
+ !Array.isArray(attributes)) {
1346
+ this._onParticipantInit(participantId, attributes);
1347
+ }
1348
+ break;
1349
+ }
1350
+ default:
1351
+ break;
311
1352
  }
312
1353
  }
313
- emit(event) {
314
- this._eventsController.add(event);
315
- }
316
- listen() {
317
- return this._eventsController.stream;
1354
+ _emitStatus({ status, message, }) {
1355
+ this.emit(new RoomStatusEvent({ status, message }));
318
1356
  }
319
1357
  }
1358
+ RoomClient.RECONNECT_RETRY_INTERVAL_MS = 1000;