@meshagent/meshagent-agents 0.47.0 → 0.48.0

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.
@@ -1,16 +1,34 @@
1
1
  import { Completer, } from "@meshagent/meshagent";
2
+ import { decode, encode } from "@msgpack/msgpack";
3
+ import WebSocket from "isomorphic-ws";
2
4
  import { v4 as uuidv4 } from "uuid";
3
- import { AgentMessage as AgentMessageFactory, AgentAudioGenerationDelta, AgentAudioTranscriptionDelta, AgentRealtimeAudioChunk, AgentRealtimeAudioCommit, AgentFileContentDelta, AgentModelChanged, AgentReasoningContentDelta, AgentTextContentDelta, AgentThreadStatus, AgentToolCallArgumentsDelta, AgentToolCallLogDelta, ChangeModel, CloseThread, DeleteThread, ModelsResponse, ModelsRequest, OpenThread, RenameThread, StartThread, AgentConnectionStatus, TurnInterrupt, TurnStart, TurnSteer, AgentClientToolCallRequested, AgentClientToolCallResponse, agentInputContent, agentAudioGenerationDeltaType, agentAudioTranscriptionDeltaType, agentFileContentDeltaType, agentImageGenerationCompletedType, agentImageGenerationPartialType, agentModelChangedType, agentModelsResponseType, agentReasoningContentDeltaType, agentRoomMessageType, agentTextContentDeltaType, agentThreadClearedType, agentThreadCreatedType, agentThreadDeletedType, agentThreadLoadedType, agentThreadListedType, agentThreadUpdatedType, agentConnectionStatusType, agentThreadStatusType, agentThreadStartType, agentThreadStartRejectedType, agentThreadStartedType, agentToolCallArgumentsDeltaType, agentToolCallLogDeltaType, agentToolCallEndedType, agentToolCallInProgressType, agentToolCallPendingType, agentToolCallStartedType, agentSecretRequestedType, agentTurnEndedType, agentTurnInterruptAcceptedType, agentTurnInterruptedType, agentTurnStartAcceptedType, agentTurnStartRejectedType, agentTurnStartType, agentTurnStartedType, agentTurnSteerAcceptedType, agentTurnSteerRejectedType, agentTurnSteerType, agentTurnSteeredType, agentUsageUpdatedType, } from "./agent-messages.js";
5
+ import { AgentMessage as AgentMessageFactory, AgentThreadMessage, AgentAudioFormat, AgentAudioGenerationDelta, AgentAudioTranscriptionDelta, AgentRealtimeAudioChunk, AgentRealtimeAudioCommit, AgentFileContentDelta, AgentImageGenerationCompleted, AgentImageGenerationPartial, AgentReasoningContentDelta, AgentTextContentDelta, AgentToolCallArgumentsDelta, AgentToolCallLogDelta, ChangeModel, CloseThread, DeleteThread, InjectMessages, ModelsRequest, OpenThread, RenameThread, StartThread, ThreadCreated, ThreadStarted, AgentConnectionStatus, TurnEnded, TurnInterrupt, TurnStart, TurnStartAccepted, TurnSteer, UnwatchThreads, WatchThreads, AgentClientToolCallRequested, AgentClientToolCallResponse, agentInputContent, agentAudioGenerationDeltaType, agentAudioTranscriptionDeltaType, agentFileContentDeltaType, agentReasoningContentDeltaType, agentRoomMessageType, agentTextContentDeltaType, agentThreadClearedType, agentThreadCreatedType, agentThreadDeletedType, agentThreadLoadedType, agentThreadListedType, agentThreadUpdatedType, agentConnectionStatusType, agentThreadStartType, agentThreadStartRejectedType, agentThreadStartedType, agentToolCallArgumentsDeltaType, agentToolCallLogDeltaType, agentTurnEndedType, agentTurnStartAcceptedType, agentTurnStartRejectedType, agentTurnStartType, agentTurnStartedType, agentTurnSteerAcceptedType, agentTurnSteerRejectedType, agentTurnSteerType, agentTurnSteeredType, } from "./agent-messages.js";
4
6
  import { ChangeEmitter } from "./change-emitter.js";
5
7
  export class AgentMessageEvent {
6
8
  constructor({ message, createdAt, attachment }) {
7
- this.message = message;
8
- this.createdAt = createdAt ?? dateOrNull(message.createdAt) ?? new Date();
9
+ this.listeners = new Set();
10
+ this.messageValue = message;
11
+ this.createdAt = createdAt ?? message.createdAtUtc ?? new Date();
9
12
  this.attachment = attachment;
10
13
  }
14
+ get message() {
15
+ return this.messageValue;
16
+ }
11
17
  get payload() {
12
18
  return { ...this.message.toJson(), created_at: this.createdAt.toISOString() };
13
19
  }
20
+ addEventListener(listener) {
21
+ this.listeners.add(listener);
22
+ }
23
+ removeEventListener(listener) {
24
+ this.listeners.delete(listener);
25
+ }
26
+ replaceMessage(message) {
27
+ this.messageValue = message;
28
+ for (const listener of [...this.listeners]) {
29
+ listener();
30
+ }
31
+ }
14
32
  }
15
33
  function agentRoomMessageFromEvent(event) {
16
34
  const candidate = event.message;
@@ -92,6 +110,12 @@ export class ChatThreadStartResult {
92
110
  this.realtimeConnection = realtimeConnection;
93
111
  }
94
112
  }
113
+ export class PendingThreadStartCandidate {
114
+ constructor({ messageId, senderName }) {
115
+ this.messageId = messageId;
116
+ this.senderName = senderName;
117
+ }
118
+ }
95
119
  class EventQueue {
96
120
  constructor() {
97
121
  this.subscribers = new Set();
@@ -144,12 +168,15 @@ class EventQueue {
144
168
  }
145
169
  }
146
170
  export class BaseChatClient extends ChangeEmitter {
147
- constructor() {
148
- super(...arguments);
171
+ constructor({ threadCreatedPendingStartMatcher, deduplicateClientToolRequests = false, } = {}) {
172
+ super();
149
173
  this.sessionsByPath = new Map();
150
174
  this.pendingStartRequests = new Map();
151
175
  this.pendingSessionEventsByPath = new Map();
152
176
  this.eventQueue = new EventQueue();
177
+ this.claimedClientToolRequests = new Set();
178
+ this.threadCreatedPendingStartMatcher = threadCreatedPendingStartMatcher;
179
+ this.deduplicateClientToolRequests = deduplicateClientToolRequests;
153
180
  }
154
181
  get events() {
155
182
  return this.eventQueue.stream;
@@ -171,14 +198,41 @@ export class BaseChatClient extends ChangeEmitter {
171
198
  localParticipantId() {
172
199
  return undefined;
173
200
  }
174
- isClientToolCallForLocalParticipant(request) {
175
- const targetParticipantId = request.targetParticipantId?.trim();
176
- return targetParticipantId == null || targetParticipantId === "" || targetParticipantId === this.localParticipantId();
201
+ clientToolRequestKey(threadPath, requestId) {
202
+ const normalizedThreadPath = threadPath.trim();
203
+ const normalizedRequestId = requestId.trim();
204
+ if (normalizedThreadPath === "" || normalizedRequestId === "") {
205
+ return undefined;
206
+ }
207
+ return `${normalizedThreadPath}\n${normalizedRequestId}`;
208
+ }
209
+ claimClientToolRequest(threadPath, requestId) {
210
+ if (!this.deduplicateClientToolRequests) {
211
+ return true;
212
+ }
213
+ const key = this.clientToolRequestKey(threadPath, requestId);
214
+ if (key == null) {
215
+ return true;
216
+ }
217
+ if (this.claimedClientToolRequests.has(key)) {
218
+ return false;
219
+ }
220
+ this.claimedClientToolRequests.add(key);
221
+ return true;
222
+ }
223
+ finishClientToolRequest(threadPath, requestId, { responseSent }) {
224
+ if (!this.deduplicateClientToolRequests || responseSent) {
225
+ return;
226
+ }
227
+ const key = this.clientToolRequestKey(threadPath, requestId);
228
+ if (key != null) {
229
+ this.claimedClientToolRequests.delete(key);
230
+ }
177
231
  }
178
232
  cleanParticipantName(value) {
179
233
  return nonEmptyTrimmed(value);
180
234
  }
181
- openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false, drainPendingEvents = true, } = {}) {
235
+ openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false, } = {}) {
182
236
  const normalized = threadPath.trim();
183
237
  if (normalized === "") {
184
238
  throw new TypeError("thread path cannot be empty");
@@ -190,11 +244,8 @@ export class BaseChatClient extends ChangeEmitter {
190
244
  }
191
245
  return existing;
192
246
  }
193
- const created = new ChatThreadSession({ client: this, threadPath: normalized });
247
+ const created = ChatThreadSession.create({ client: this, threadPath: normalized });
194
248
  this.sessionsByPath.set(normalized, created);
195
- if (drainPendingEvents) {
196
- this.drainPendingSessionEvents(normalized, created);
197
- }
198
249
  this.openSession(created, { load, sinceTurn });
199
250
  this.notifyListeners();
200
251
  return created;
@@ -204,7 +255,7 @@ export class BaseChatClient extends ChangeEmitter {
204
255
  this.notifyListeners();
205
256
  });
206
257
  }
207
- async startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, toolChoice, omitContent = false, }) {
258
+ async startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, omitContent = false, }) {
208
259
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
209
260
  const resolvedSenderName = this.cleanParticipantName(senderName) ?? this.localParticipantName();
210
261
  const payload = new StartThread({
@@ -219,10 +270,9 @@ export class BaseChatClient extends ChangeEmitter {
219
270
  realtimeProtocol: nonEmptyTrimmed(realtimeProtocol),
220
271
  senderName: resolvedSenderName,
221
272
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
222
- toolChoice: toolChoice?.toJson(),
223
273
  });
224
274
  const completer = new Completer();
225
- this.pendingStartRequests.set(resolvedMessageId, completer);
275
+ this.pendingStartRequests.set(resolvedMessageId, { request: payload, completer });
226
276
  try {
227
277
  await this.sendAgentMessage(payload);
228
278
  const response = await completer.fut;
@@ -232,18 +282,18 @@ export class BaseChatClient extends ChangeEmitter {
232
282
  if (threadPath === "") {
233
283
  throw new Error("Agent did not return a thread_id for the new thread.");
234
284
  }
235
- const session = this.openThread(threadPath, { load: false, drainPendingEvents: false });
285
+ const session = this.openThread(threadPath, { load: false });
236
286
  if (!omitContent) {
237
287
  session.addAgentMessage(new AgentMessageEvent({
238
288
  message: this.resolvedStartThreadMessage({ payload, threadPath }),
239
- createdAt: dateOrNull(payload.createdAt) ?? undefined,
289
+ createdAt: payload.createdAtUtc,
240
290
  }));
241
291
  }
242
292
  this.drainPendingSessionEvents(threadPath, session);
243
293
  return new ChatThreadStartResult({
244
294
  session,
245
295
  threadPath,
246
- realtimeConnection: response.type === agentThreadStartedType && response.realtimeConnection != null
296
+ realtimeConnection: response instanceof ThreadStarted && response.realtimeConnection != null
247
297
  ? response.realtimeConnection.toJson()
248
298
  : undefined,
249
299
  });
@@ -267,6 +317,7 @@ export class BaseChatClient extends ChangeEmitter {
267
317
  mcp: payload.mcp,
268
318
  clientToolkits: payload.clientToolkits,
269
319
  toolkits: payload.toolkits,
320
+ toolChoice: payload.toolChoice,
270
321
  });
271
322
  }
272
323
  drainPendingSessionEvents(threadPath, session) {
@@ -280,9 +331,13 @@ export class BaseChatClient extends ChangeEmitter {
280
331
  }
281
332
  }
282
333
  handleAgentMessage(message, { createdAt, attachment } = {}) {
283
- if (message instanceof AgentClientToolCallRequested &&
284
- !this.isClientToolCallForLocalParticipant(message)) {
285
- return;
334
+ if (message instanceof AgentClientToolCallRequested) {
335
+ const targetParticipantId = message.targetParticipantId?.trim();
336
+ if (targetParticipantId != null &&
337
+ targetParticipantId !== "" &&
338
+ targetParticipantId !== this.localParticipantId()) {
339
+ return;
340
+ }
286
341
  }
287
342
  if (message.type === agentConnectionStatusType) {
288
343
  this._connectionStatus = message;
@@ -293,10 +348,13 @@ export class BaseChatClient extends ChangeEmitter {
293
348
  sourceMessageId != null &&
294
349
  sourceMessageId.trim() !== "") {
295
350
  const pending = this.pendingStartRequests.get(sourceMessageId.trim());
296
- if (pending != null && !pending.completed) {
297
- pending.complete(message);
351
+ if (pending != null && !pending.completer.completed) {
352
+ pending.completer.complete(message);
298
353
  }
299
354
  }
355
+ if (message instanceof ThreadCreated) {
356
+ this.acknowledgePendingStartFromThreadCreated(message);
357
+ }
300
358
  const threadPath = "threadId" in message ? String(message.threadId).trim() : "";
301
359
  if (threadPath === "") {
302
360
  this.notifyListeners();
@@ -314,6 +372,40 @@ export class BaseChatClient extends ChangeEmitter {
314
372
  }
315
373
  this.notifyListeners();
316
374
  }
375
+ acknowledgePendingStartFromThreadCreated(message) {
376
+ const matcher = this.threadCreatedPendingStartMatcher;
377
+ const threadPath = String(message.thread?.path ?? "").trim();
378
+ if (matcher == null || threadPath === "") {
379
+ return;
380
+ }
381
+ const candidates = [...this.pendingStartRequests.entries()]
382
+ .filter(([, pending]) => !pending.completer.completed)
383
+ .map(([messageId, pending]) => new PendingThreadStartCandidate({
384
+ messageId,
385
+ senderName: pending.request.senderName,
386
+ }));
387
+ if (candidates.length === 0) {
388
+ return;
389
+ }
390
+ let matchedMessageId;
391
+ try {
392
+ matchedMessageId = matcher(message, candidates)?.trim();
393
+ }
394
+ catch {
395
+ return;
396
+ }
397
+ if (matchedMessageId == null || matchedMessageId === "") {
398
+ return;
399
+ }
400
+ const pending = this.pendingStartRequests.get(matchedMessageId);
401
+ if (pending == null || pending.completer.completed) {
402
+ return;
403
+ }
404
+ pending.completer.complete(new ThreadStarted({
405
+ sourceMessageId: matchedMessageId,
406
+ threadId: threadPath,
407
+ }));
408
+ }
317
409
  emitConnectionStatus({ status, message, reason, retryDelay, }) {
318
410
  const event = new AgentConnectionStatus({
319
411
  status,
@@ -335,9 +427,49 @@ export class BaseChatClient extends ChangeEmitter {
335
427
  }
336
428
  }
337
429
  }
338
- export class MessagingChatClient extends BaseChatClient {
339
- constructor({ room, agentName }) {
340
- super();
430
+ export const messagingChatThreadSubscribeType = "meshagent.chat.thread.subscribe";
431
+ export const messagingChatThreadListSubscribeType = "meshagent.chat.thread_list.subscribe";
432
+ export class ThreadSubscribe {
433
+ constructor({ threadId, stream }) {
434
+ this.threadId = threadId;
435
+ this.stream = stream;
436
+ }
437
+ get closed() {
438
+ return this.stream.closed;
439
+ }
440
+ async send(message, { attachment } = {}) {
441
+ await this.stream.sendMessage({
442
+ type: agentRoomMessageType,
443
+ message: message.toJson(),
444
+ attachment,
445
+ });
446
+ }
447
+ async close() {
448
+ await this.stream.close();
449
+ }
450
+ }
451
+ export class ThreadListSubscribe {
452
+ constructor({ stream }) {
453
+ this.stream = stream;
454
+ }
455
+ get closed() {
456
+ return this.stream.closed;
457
+ }
458
+ async close() {
459
+ await this.stream.close();
460
+ }
461
+ }
462
+ function participantWaitAbortedError(reason) {
463
+ if (reason instanceof Error) {
464
+ return reason;
465
+ }
466
+ const error = new Error("Agent participant wait was cancelled.");
467
+ error.name = "AbortError";
468
+ return error;
469
+ }
470
+ export class BaseMessagingChatClient extends BaseChatClient {
471
+ constructor({ room, agentName, threadCreatedPendingStartMatcher, deduplicateClientToolRequests = true, }) {
472
+ super({ threadCreatedPendingStartMatcher, deduplicateClientToolRequests });
341
473
  this.pendingParticipantWaits = new Map();
342
474
  this.started = false;
343
475
  this.hasConnected = false;
@@ -434,7 +566,7 @@ export class MessagingChatClient extends BaseChatClient {
434
566
  this.attachMessagingListeners();
435
567
  this.room.messaging.start();
436
568
  this.startRoomStatusListener();
437
- await this.room.messaging.enable();
569
+ this.room.messaging.enable();
438
570
  this.waitingForParticipant = true;
439
571
  this.onMessagingChanged();
440
572
  }
@@ -485,7 +617,13 @@ export class MessagingChatClient extends BaseChatClient {
485
617
  }
486
618
  return null;
487
619
  }
488
- async waitForAgentParticipant({ waitKey } = {}) {
620
+ async waitForAgentParticipant({ waitKey, signal, timeoutMs, } = {}) {
621
+ if (signal?.aborted) {
622
+ throw participantWaitAbortedError(signal.reason);
623
+ }
624
+ if (timeoutMs != null && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
625
+ throw new RangeError("Agent participant wait timeout must be a non-negative finite number.");
626
+ }
489
627
  const existing = this.agentParticipant();
490
628
  if (existing != null) {
491
629
  return existing;
@@ -493,12 +631,34 @@ export class MessagingChatClient extends BaseChatClient {
493
631
  const key = waitKey == null || waitKey.trim() === "" ? uuidv4() : waitKey.trim();
494
632
  const completer = new Completer();
495
633
  this.pendingParticipantWaits.set(key, completer);
634
+ const handleAbort = () => {
635
+ if (!completer.completed) {
636
+ completer.completeError(participantWaitAbortedError(signal?.reason));
637
+ }
638
+ };
639
+ signal?.addEventListener("abort", handleAbort, { once: true });
640
+ if (signal?.aborted) {
641
+ handleAbort();
642
+ }
643
+ const timeout = timeoutMs == null
644
+ ? undefined
645
+ : setTimeout(() => {
646
+ if (!completer.completed) {
647
+ completer.completeError(new Error(`Agent messaging participant was not available within ${timeoutMs}ms.`));
648
+ }
649
+ }, timeoutMs);
496
650
  this.onMessagingChanged();
497
651
  try {
498
652
  return await completer.fut;
499
653
  }
500
654
  finally {
501
- this.pendingParticipantWaits.delete(key);
655
+ signal?.removeEventListener("abort", handleAbort);
656
+ if (timeout != null) {
657
+ clearTimeout(timeout);
658
+ }
659
+ if (this.pendingParticipantWaits.get(key) === completer) {
660
+ this.pendingParticipantWaits.delete(key);
661
+ }
502
662
  }
503
663
  }
504
664
  async sendAgentMessage(message, { attachment } = {}) {
@@ -552,6 +712,370 @@ export class MessagingChatClient extends BaseChatClient {
552
712
  }
553
713
  }
554
714
  }
715
+ export class WebSocketChatClient extends BaseChatClient {
716
+ constructor({ url, token, participantName, protocols = ["meshagent-msgpack"], reconnect = true, reconnectInitialDelay = 1000, reconnectMaxDelay = 10000, }) {
717
+ super({ deduplicateClientToolRequests: true });
718
+ this.started = false;
719
+ this.stopping = false;
720
+ this.connecting = false;
721
+ this.reconnectAttempts = 0;
722
+ this.onMessage = (event) => {
723
+ try {
724
+ const bytes = webSocketMessageBytes(event.data);
725
+ if (bytes == null) {
726
+ return;
727
+ }
728
+ const decoded = decode(bytes);
729
+ if (!isJsonMap(decoded)) {
730
+ throw new Error("chat websocket received a non-object message");
731
+ }
732
+ const message = AgentMessageFactory.fromJson(decoded);
733
+ if (message instanceof AgentConnectionStatus) {
734
+ const participantId = message.participantId?.trim();
735
+ if (participantId != null && participantId !== "") {
736
+ this.assignedParticipantId = participantId;
737
+ }
738
+ }
739
+ this.handleAgentMessage(message);
740
+ }
741
+ catch (error) {
742
+ this.receiveError = error;
743
+ this.emitConnectionStatus({
744
+ status: "disconnected",
745
+ message: "Chat websocket message could not be decoded",
746
+ reason: describeError(error),
747
+ });
748
+ this.webSocket?.close(1003);
749
+ }
750
+ };
751
+ this.onSocketError = (event) => {
752
+ this.receiveError = event.error ?? new Error("chat websocket error");
753
+ };
754
+ this.onSocketClose = (event) => {
755
+ const webSocket = this.webSocket;
756
+ if (webSocket == null) {
757
+ return;
758
+ }
759
+ this.closeCode = event.code;
760
+ this.closeReason = event.reason;
761
+ this.detachSocketListeners(webSocket);
762
+ this.webSocket = undefined;
763
+ this.assignedParticipantId = undefined;
764
+ if (this.stopping || !this.started) {
765
+ this.notifyListeners();
766
+ return;
767
+ }
768
+ const reason = event.reason !== "" ? `code=${event.code}: ${event.reason}` : `code=${event.code}`;
769
+ if (this.reconnect) {
770
+ this.scheduleReconnect({ reason });
771
+ }
772
+ else {
773
+ this.emitConnectionStatus({
774
+ status: "disconnected",
775
+ message: "Chat websocket disconnected",
776
+ reason,
777
+ });
778
+ }
779
+ this.notifyListeners();
780
+ };
781
+ this.url = url;
782
+ this.token = token;
783
+ this.participantName = participantName;
784
+ this.protocols = protocols;
785
+ this.reconnect = reconnect;
786
+ this.reconnectInitialDelay = reconnectInitialDelay;
787
+ this.reconnectMaxDelay = reconnectMaxDelay;
788
+ }
789
+ get isConnected() {
790
+ return this.webSocket != null && this.webSocket.readyState === WebSocket.OPEN;
791
+ }
792
+ localParticipantName() {
793
+ return this.cleanParticipantName(this.participantName);
794
+ }
795
+ localParticipantId() {
796
+ return this.assignedParticipantId;
797
+ }
798
+ async start() {
799
+ this.started = true;
800
+ this.stopping = false;
801
+ if (this.webSocket != null || this.connecting) {
802
+ return;
803
+ }
804
+ await this.connect({ isReconnect: false });
805
+ }
806
+ async connect({ isReconnect }) {
807
+ if (this.webSocket != null || this.connecting || !this.started || this.stopping) {
808
+ return;
809
+ }
810
+ this.connecting = true;
811
+ const webSocket = new WebSocket(this.url.toString(), this.resolvedProtocols());
812
+ this.webSocket = webSocket;
813
+ this.assignedParticipantId = undefined;
814
+ webSocket.binaryType = "arraybuffer";
815
+ try {
816
+ await new Promise((resolve, reject) => {
817
+ const onOpen = () => {
818
+ cleanup();
819
+ resolve();
820
+ };
821
+ const onError = (event) => {
822
+ cleanup();
823
+ reject(event.error ?? new Error("chat websocket connection failed"));
824
+ };
825
+ const cleanup = () => {
826
+ webSocket.removeEventListener("open", onOpen);
827
+ webSocket.removeEventListener("error", onError);
828
+ };
829
+ webSocket.addEventListener("open", onOpen);
830
+ webSocket.addEventListener("error", onError);
831
+ });
832
+ }
833
+ catch (error) {
834
+ if (this.webSocket === webSocket) {
835
+ this.webSocket = undefined;
836
+ }
837
+ this.receiveError = error;
838
+ this.connecting = false;
839
+ if (isReconnect && this.reconnect && !this.stopping) {
840
+ this.scheduleReconnect({ reason: describeError(error) });
841
+ return;
842
+ }
843
+ this.emitConnectionStatus({
844
+ status: "disconnected",
845
+ message: "Chat websocket connection failed",
846
+ reason: describeError(error),
847
+ });
848
+ throw error;
849
+ }
850
+ webSocket.addEventListener("message", this.onMessage);
851
+ webSocket.addEventListener("error", this.onSocketError);
852
+ webSocket.addEventListener("close", this.onSocketClose);
853
+ this.connecting = false;
854
+ this.receiveError = undefined;
855
+ this.closeCode = undefined;
856
+ this.closeReason = undefined;
857
+ const status = isReconnect ? "reconnected" : "connected";
858
+ this.emitConnectionStatus({
859
+ status,
860
+ message: isReconnect ? "Chat websocket reconnected" : "Chat websocket connected",
861
+ });
862
+ if (isReconnect) {
863
+ await this.reopenOpenSessions();
864
+ }
865
+ this.reconnectAttempts = 0;
866
+ this.notifyListeners();
867
+ }
868
+ async stop() {
869
+ this.started = false;
870
+ this.stopping = true;
871
+ if (this.reconnectTimer != null) {
872
+ clearTimeout(this.reconnectTimer);
873
+ this.reconnectTimer = undefined;
874
+ }
875
+ const webSocket = this.webSocket;
876
+ this.webSocket = undefined;
877
+ this.assignedParticipantId = undefined;
878
+ if (webSocket != null) {
879
+ this.detachSocketListeners(webSocket);
880
+ webSocket.close(1000);
881
+ }
882
+ this.emitConnectionStatus({
883
+ status: "disconnected",
884
+ message: "Chat websocket disconnected",
885
+ reason: "client_stopped",
886
+ });
887
+ this.stopping = false;
888
+ this.notifyListeners();
889
+ }
890
+ async sendAgentMessage(message, { attachment } = {}) {
891
+ if (attachment != null) {
892
+ throw new Error("WebSocketChatClient does not support binary attachments yet.");
893
+ }
894
+ const webSocket = this.webSocket;
895
+ if (webSocket == null || webSocket.readyState !== WebSocket.OPEN) {
896
+ throw new Error(this.closedMessage());
897
+ }
898
+ webSocket.send(encode(message.toJson()));
899
+ }
900
+ detachSocketListeners(webSocket) {
901
+ webSocket.removeEventListener("message", this.onMessage);
902
+ webSocket.removeEventListener("error", this.onSocketError);
903
+ webSocket.removeEventListener("close", this.onSocketClose);
904
+ }
905
+ scheduleReconnect({ reason }) {
906
+ if (this.reconnectTimer != null || this.stopping || !this.started) {
907
+ return;
908
+ }
909
+ const retryDelay = this.nextReconnectDelay();
910
+ this.emitConnectionStatus({
911
+ status: "reconnecting",
912
+ message: "Chat websocket disconnected; reconnecting",
913
+ reason,
914
+ retryDelay,
915
+ });
916
+ this.reconnectTimer = setTimeout(() => {
917
+ this.reconnectTimer = undefined;
918
+ void this.connect({ isReconnect: true });
919
+ }, retryDelay);
920
+ }
921
+ nextReconnectDelay() {
922
+ const initialDelay = Math.max(1, this.reconnectInitialDelay);
923
+ const maxDelay = Math.max(initialDelay, this.reconnectMaxDelay);
924
+ const factor = 2 ** Math.min(this.reconnectAttempts, 6);
925
+ this.reconnectAttempts += 1;
926
+ return Math.min(initialDelay * factor, maxDelay);
927
+ }
928
+ async reopenOpenSessions() {
929
+ for (const session of this.sessions) {
930
+ if (!session.isOpen) {
931
+ continue;
932
+ }
933
+ await session.open({ load: true, sinceTurn: session.lastCompletedTurnId });
934
+ }
935
+ }
936
+ resolvedProtocols() {
937
+ const resolved = [];
938
+ for (const protocol of this.protocols) {
939
+ const normalized = protocol.trim();
940
+ if (normalized !== "" && !resolved.includes(normalized)) {
941
+ resolved.push(normalized);
942
+ }
943
+ }
944
+ const normalizedToken = this.token.trim();
945
+ if (normalizedToken !== "") {
946
+ resolved.push(`meshagent-agent.${normalizedToken}`);
947
+ }
948
+ return resolved;
949
+ }
950
+ closedMessage() {
951
+ if (this.receiveError != null) {
952
+ return `chat websocket is closed: ${describeError(this.receiveError)}`;
953
+ }
954
+ if (this.closeCode != null) {
955
+ return `chat websocket is closed (code=${this.closeCode})`;
956
+ }
957
+ if (this.closeReason != null && this.closeReason.trim() !== "") {
958
+ return `chat websocket is closed: ${this.closeReason}`;
959
+ }
960
+ return "chat websocket is closed";
961
+ }
962
+ }
963
+ export class MessagingChatClient extends BaseMessagingChatClient {
964
+ constructor() {
965
+ super(...arguments);
966
+ this.threadSubscriptions = new Map();
967
+ this.streamTasks = new Set();
968
+ }
969
+ async stop() {
970
+ const subscriptions = [
971
+ ...this.threadSubscriptions.values(),
972
+ ];
973
+ if (this.threadListSubscription != null) {
974
+ subscriptions.push(this.threadListSubscription);
975
+ }
976
+ this.threadSubscriptions.clear();
977
+ this.threadListSubscription = undefined;
978
+ await Promise.all(subscriptions.map(async (subscription) => subscription.close()));
979
+ await Promise.all(this.streamTasks);
980
+ await super.stop();
981
+ }
982
+ supportsThreadSubscriptions(participant) {
983
+ return participant.getAttribute("supports_messaging_streams") === true;
984
+ }
985
+ trackStream(subscription) {
986
+ const task = (async () => {
987
+ try {
988
+ for await (const event of subscription.stream) {
989
+ if (event.kind !== "message" || event.message.type !== agentRoomMessageType) {
990
+ continue;
991
+ }
992
+ const raw = event.message.message;
993
+ if (typeof raw["type"] !== "string") {
994
+ continue;
995
+ }
996
+ this.handleAgentMessage(AgentMessageFactory.fromJson(raw), {
997
+ createdAt: dateOrNull(raw["created_at"]),
998
+ attachment: event.message.attachment,
999
+ });
1000
+ }
1001
+ }
1002
+ finally {
1003
+ if (subscription instanceof ThreadSubscribe) {
1004
+ if (this.threadSubscriptions.get(subscription.threadId) === subscription) {
1005
+ this.threadSubscriptions.delete(subscription.threadId);
1006
+ }
1007
+ }
1008
+ else if (this.threadListSubscription === subscription) {
1009
+ this.threadListSubscription = undefined;
1010
+ }
1011
+ }
1012
+ })();
1013
+ this.streamTasks.add(task);
1014
+ void task.finally(() => this.streamTasks.delete(task));
1015
+ }
1016
+ async sendAgentMessage(message, { attachment } = {}) {
1017
+ await this.start();
1018
+ const participant = this.agentParticipant();
1019
+ if (participant == null) {
1020
+ throw new Error("Agent messaging participant is not available.");
1021
+ }
1022
+ if (!this.supportsThreadSubscriptions(participant)) {
1023
+ await super.sendAgentMessage(message, { attachment });
1024
+ return;
1025
+ }
1026
+ if (message instanceof OpenThread) {
1027
+ const previous = this.threadSubscriptions.get(message.threadId);
1028
+ if (previous != null) {
1029
+ await previous.close();
1030
+ }
1031
+ const stream = await this.room.messaging.stream({
1032
+ to: participant,
1033
+ type: messagingChatThreadSubscribeType,
1034
+ message: message.toJson(),
1035
+ attachment,
1036
+ });
1037
+ const subscription = new ThreadSubscribe({ threadId: message.threadId, stream });
1038
+ this.threadSubscriptions.set(message.threadId, subscription);
1039
+ this.trackStream(subscription);
1040
+ return;
1041
+ }
1042
+ if (message instanceof CloseThread) {
1043
+ const subscription = this.threadSubscriptions.get(message.threadId);
1044
+ if (subscription != null) {
1045
+ this.threadSubscriptions.delete(message.threadId);
1046
+ await subscription.close();
1047
+ return;
1048
+ }
1049
+ }
1050
+ if (message instanceof WatchThreads) {
1051
+ await this.threadListSubscription?.close();
1052
+ const stream = await this.room.messaging.stream({
1053
+ to: participant,
1054
+ type: messagingChatThreadListSubscribeType,
1055
+ message: message.toJson(),
1056
+ attachment,
1057
+ });
1058
+ const subscription = new ThreadListSubscribe({ stream });
1059
+ this.threadListSubscription = subscription;
1060
+ this.trackStream(subscription);
1061
+ return;
1062
+ }
1063
+ if (message instanceof UnwatchThreads && this.threadListSubscription != null) {
1064
+ const subscription = this.threadListSubscription;
1065
+ this.threadListSubscription = undefined;
1066
+ await subscription.close();
1067
+ return;
1068
+ }
1069
+ if ("threadId" in message && typeof message.threadId === "string") {
1070
+ const subscription = this.threadSubscriptions.get(message.threadId);
1071
+ if (subscription != null) {
1072
+ await subscription.send(message, { attachment });
1073
+ return;
1074
+ }
1075
+ }
1076
+ await super.sendAgentMessage(message, { attachment });
1077
+ }
1078
+ }
555
1079
  function isThreadListMessage(message) {
556
1080
  return message.type === agentThreadListedType
557
1081
  || message.type === agentThreadCreatedType
@@ -574,6 +1098,9 @@ export class ChatThreadSession extends ChangeEmitter {
574
1098
  this.client = client;
575
1099
  this.threadPath = threadPath;
576
1100
  }
1101
+ static create({ client, threadPath }) {
1102
+ return new ChatThreadSession({ client, threadPath });
1103
+ }
577
1104
  get messages() {
578
1105
  return [...this._messages];
579
1106
  }
@@ -592,18 +1119,6 @@ export class ChatThreadSession extends ChangeEmitter {
592
1119
  get lastCompletedTurnId() {
593
1120
  return this.lastCompletedTurnIdValue;
594
1121
  }
595
- get activeTurnId() {
596
- return this.activeTurnIdValue;
597
- }
598
- get threadStatus() {
599
- return this.threadStatusValue;
600
- }
601
- get currentModel() {
602
- return this.currentModelValue;
603
- }
604
- get modelsResponse() {
605
- return this.modelsResponseValue;
606
- }
607
1122
  async open({ load = true, sinceTurn } = {}) {
608
1123
  if (this.openState) {
609
1124
  if (load) {
@@ -618,7 +1133,6 @@ export class ChatThreadSession extends ChangeEmitter {
618
1133
  }
619
1134
  catch (error) {
620
1135
  this.setLoadState(this.failedLoadState(error));
621
- throw error;
622
1136
  }
623
1137
  }
624
1138
  return;
@@ -637,7 +1151,6 @@ export class ChatThreadSession extends ChangeEmitter {
637
1151
  }
638
1152
  catch (error) {
639
1153
  this.setLoadState(this.failedLoadState(error));
640
- throw error;
641
1154
  }
642
1155
  }
643
1156
  resetReplayState() {
@@ -684,16 +1197,20 @@ export class ChatThreadSession extends ChangeEmitter {
684
1197
  response,
685
1198
  }));
686
1199
  }
687
- async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, toolChoice, }) {
1200
+ claimClientToolCall(requestId) {
1201
+ return this.client.claimClientToolRequest(this.threadPath, requestId);
1202
+ }
1203
+ finishClientToolCall(requestId, { responseSent }) {
1204
+ this.client.finishClientToolRequest(this.threadPath, requestId, { responseSent });
1205
+ }
1206
+ injectMessages(messages) {
1207
+ return this.client.sendAgentMessage(new InjectMessages({
1208
+ threadId: this.threadPath,
1209
+ messages: [...messages],
1210
+ }));
1211
+ }
1212
+ async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
688
1213
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
689
- const currentModel = this.currentModelValue;
690
- const resolvedBackend = nonEmptyTrimmed(backend) ?? currentModel?.backend;
691
- const resolvedProvider = nonEmptyTrimmed(provider) ?? currentModel?.provider;
692
- const resolvedModel = nonEmptyTrimmed(model) ?? currentModel?.model;
693
- const resolvedVoice = nonEmptyTrimmed(voice) ?? currentModel?.voice;
694
- const resolvedOutputModalities = outputModalities != null && outputModalities.length > 0
695
- ? outputModalities
696
- : currentModel?.outputModalities;
697
1214
  const payload = steer
698
1215
  ? new TurnSteer({
699
1216
  threadId: this.threadPath,
@@ -707,13 +1224,12 @@ export class ChatThreadSession extends ChangeEmitter {
707
1224
  messageId: resolvedMessageId,
708
1225
  senderName: nonEmptyTrimmed(senderName),
709
1226
  content: agentInputContent({ text, attachments }),
710
- backend: resolvedBackend,
711
- provider: resolvedProvider,
712
- model: resolvedModel,
713
- voice: resolvedVoice,
714
- outputModalities: resolvedOutputModalities,
1227
+ backend: nonEmptyTrimmed(backend),
1228
+ provider: nonEmptyTrimmed(provider),
1229
+ model: nonEmptyTrimmed(model),
1230
+ voice: nonEmptyTrimmed(voice),
1231
+ outputModalities: outputModalities != null && outputModalities.length > 0 ? outputModalities : undefined,
715
1232
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
716
- toolChoice: toolChoice?.toJson(),
717
1233
  });
718
1234
  this.localAgentMessageIds.add(resolvedMessageId);
719
1235
  this.pendingLocalInputMessageIds.add(resolvedMessageId);
@@ -732,10 +1248,10 @@ export class ChatThreadSession extends ChangeEmitter {
732
1248
  await this.client.sendAgentMessage(new AgentRealtimeAudioChunk({
733
1249
  threadId: this.threadPath,
734
1250
  messageId: uuidv4(),
735
- format,
1251
+ format: new AgentAudioFormat(format),
736
1252
  }), { attachment: chunk });
737
1253
  }
738
- async commitRealtimeAudio({ turnId, provider, model, voice, outputModalities, }) {
1254
+ async commitRealtimeAudio({ turnId, backend, provider, model, voice, outputModalities, }) {
739
1255
  const messageId = uuidv4();
740
1256
  await this.client.sendAgentMessage(new AgentRealtimeAudioCommit({
741
1257
  threadId: this.threadPath,
@@ -746,6 +1262,7 @@ export class ChatThreadSession extends ChangeEmitter {
746
1262
  threadId: this.threadPath,
747
1263
  messageId: uuidv4(),
748
1264
  turnId,
1265
+ backend: nonEmptyTrimmed(backend),
749
1266
  provider: nonEmptyTrimmed(provider),
750
1267
  model: nonEmptyTrimmed(model),
751
1268
  voice: nonEmptyTrimmed(voice),
@@ -774,56 +1291,9 @@ export class ChatThreadSession extends ChangeEmitter {
774
1291
  const turnId = normalizedString(threadMessageValue(message, "turnId"));
775
1292
  if (turnId != null) {
776
1293
  this.lastCompletedTurnIdValue = turnId;
777
- if (this.activeTurnIdValue === turnId) {
778
- this.activeTurnIdValue = undefined;
779
- }
780
- }
781
- const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
782
- if (statusTurnId == null || statusTurnId === turnId) {
783
- this.threadStatusValue = undefined;
784
1294
  }
785
- return;
786
1295
  }
787
- if (message.type === agentTurnStartAcceptedType || message.type === agentTurnStartedType) {
788
- const turnId = normalizedString(threadMessageValue(message, "turnId"));
789
- if (turnId != null) {
790
- this.activeTurnIdValue = turnId;
791
- }
792
- if (message.type === agentTurnStartedType) {
793
- const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
794
- if (statusTurnId == null || statusTurnId === turnId) {
795
- this.threadStatusValue = undefined;
796
- }
797
- }
798
- return;
799
- }
800
- if (message.type === agentThreadStatusType && message instanceof AgentThreadStatus) {
801
- const mode = normalizedString(message.mode);
802
- const status = normalizedString(message.status);
803
- const turnId = normalizedString(message.turnId);
804
- if (status == null) {
805
- this.threadStatusValue = undefined;
806
- if (turnId != null && this.activeTurnIdValue === turnId) {
807
- this.activeTurnIdValue = undefined;
808
- }
809
- return;
810
- }
811
- this.threadStatusValue = message;
812
- if (mode === "steerable" && turnId != null) {
813
- this.activeTurnIdValue = turnId;
814
- }
815
- return;
816
- }
817
- if (message.type === agentModelChangedType && message instanceof AgentModelChanged) {
818
- this.currentModelValue = message;
819
- return;
820
- }
821
- if (message.type === agentModelsResponseType && message instanceof ModelsResponse) {
822
- this.modelsResponseValue = message;
823
- this.applyActiveModel(message);
824
- return;
825
- }
826
- if (message.type === agentThreadLoadedType) {
1296
+ else if (message.type === agentThreadLoadedType) {
827
1297
  this.setLoadState(new ChatThreadSessionLoadState({
828
1298
  phase: ChatThreadSessionLoadPhase.Loaded,
829
1299
  startedAt: this.loadStateValue.startedAt,
@@ -901,45 +1371,21 @@ export class ChatThreadSession extends ChangeEmitter {
901
1371
  }
902
1372
  appendRenderableMessage(event) {
903
1373
  const message = event.message;
904
- if (message.type === agentTurnEndedType) {
905
- if (message.error != null) {
906
- this.appendMessage(event);
907
- }
908
- return;
909
- }
910
- if (message.type === agentToolCallEndedType) {
911
- const itemId = message.error == null ? undefined : normalizedString(message.itemId);
912
- if (itemId != null) {
913
- this.upsertItemMessage(`tool_call:${itemId}`, event);
914
- }
915
- else if (message.error != null) {
916
- this.appendMessage(event);
917
- }
918
- return;
919
- }
920
- if (message.type === agentToolCallPendingType || message.type === agentToolCallInProgressType || message.type === agentToolCallStartedType) {
921
- const itemId = normalizedString(message.itemId);
922
- if (itemId != null) {
923
- this.upsertItemMessage(`tool_call:${itemId}`, event);
924
- }
925
- return;
926
- }
927
- if (message.type === agentSecretRequestedType) {
928
- this.appendMessage(event);
929
- return;
930
- }
931
- if (message.type === agentModelChangedType) {
1374
+ if (message instanceof TurnEnded) {
932
1375
  this.appendMessage(event);
933
1376
  return;
934
1377
  }
935
- if (message.type === agentThreadStartType || message.type === agentTurnStartType || message.type === agentTurnSteerType) {
936
- if (agentInputText(message.content).trim() !== "") {
1378
+ if (message instanceof StartThread || message instanceof TurnStart || message instanceof TurnSteer) {
1379
+ if (agentInputText(messageValue(message, "content")).trim() !== "") {
937
1380
  this.appendMessage(event);
1381
+ const messageId = normalizedString(message.messageId);
1382
+ if (messageId != null)
1383
+ this.pendingLocalInputMessageIds.add(messageId);
938
1384
  }
939
1385
  return;
940
1386
  }
941
- if (message.type === agentTurnStartAcceptedType || message.type === agentTurnSteerAcceptedType) {
942
- const sourceMessageId = normalizedString(sourceMessageIdFor(message));
1387
+ if (message instanceof TurnStartAccepted) {
1388
+ const sourceMessageId = normalizedString(message.sourceMessageId);
943
1389
  if (sourceMessageId != null && this.localAgentMessageIds.has(sourceMessageId)) {
944
1390
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
945
1391
  return;
@@ -947,36 +1393,21 @@ export class ChatThreadSession extends ChangeEmitter {
947
1393
  if (agentInputText(message.content).trim() !== "") {
948
1394
  this.appendMessage(event, { beforePendingLocalInputs: true });
949
1395
  }
950
- else {
951
- this.appendMessage(event);
952
- }
953
- return;
954
- }
955
- if (message.type === agentTurnStartedType ||
956
- message.type === agentTurnSteeredType ||
957
- message.type === agentTurnStartRejectedType ||
958
- message.type === agentTurnSteerRejectedType ||
959
- message.type === agentTurnInterruptAcceptedType ||
960
- message.type === agentTurnInterruptedType) {
961
- this.appendMessage(event);
962
1396
  return;
963
1397
  }
964
1398
  if (isMergeableDelta(message)) {
965
1399
  this.appendOrMergeDelta(event);
966
1400
  return;
967
1401
  }
968
- if (message.type === agentImageGenerationPartialType || message.type === agentImageGenerationCompletedType) {
969
- const itemId = normalizedString(message.itemId);
970
- if (itemId != null) {
971
- this.upsertItemMessage(`image_generation:${itemId}`, event);
972
- }
1402
+ if (message instanceof AgentImageGenerationPartial || message instanceof AgentImageGenerationCompleted) {
1403
+ this.appendMessage(event);
973
1404
  return;
974
1405
  }
975
- if (message.type === agentUsageUpdatedType) {
1406
+ if (message instanceof AgentClientToolCallRequested) {
976
1407
  this.appendMessage(event);
977
1408
  return;
978
1409
  }
979
- if (message instanceof AgentClientToolCallRequested) {
1410
+ if (message instanceof AgentConnectionStatus || message instanceof AgentThreadMessage) {
980
1411
  this.appendMessage(event);
981
1412
  }
982
1413
  }
@@ -996,15 +1427,6 @@ export class ChatThreadSession extends ChangeEmitter {
996
1427
  this.messageIndexes.set(normalizedMessageId, this._messages.length);
997
1428
  this._messages.push(event);
998
1429
  }
999
- upsertItemMessage(key, event) {
1000
- const existingIndex = this.messageIndexes.get(key);
1001
- if (existingIndex == null) {
1002
- this.messageIndexes.set(key, this._messages.length);
1003
- this._messages.push(event);
1004
- return;
1005
- }
1006
- this._messages[existingIndex] = event;
1007
- }
1008
1430
  appendOrMergeDelta(event) {
1009
1431
  const message = event.message;
1010
1432
  const normalizedMessageId = normalizedString(message.messageId);
@@ -1014,7 +1436,7 @@ export class ChatThreadSession extends ChangeEmitter {
1014
1436
  }
1015
1437
  this.mergedDeltaMessageIds.add(normalizedMessageId);
1016
1438
  }
1017
- const itemId = normalizedString(message.itemId);
1439
+ const itemId = normalizedString(messageValue(message, "itemId"));
1018
1440
  if (itemId == null) {
1019
1441
  return;
1020
1442
  }
@@ -1028,7 +1450,7 @@ export class ChatThreadSession extends ChangeEmitter {
1028
1450
  const existing = this._messages[existingIndex].message;
1029
1451
  const merged = mergeDeltaMessage(existing, message);
1030
1452
  if (merged != null) {
1031
- this._messages[existingIndex] = new AgentMessageEvent({ message: merged, createdAt: event.createdAt, attachment: event.attachment });
1453
+ this._messages[existingIndex].replaceMessage(merged);
1032
1454
  }
1033
1455
  }
1034
1456
  indexMessagesFrom(start) {
@@ -1039,29 +1461,6 @@ export class ChatThreadSession extends ChangeEmitter {
1039
1461
  }
1040
1462
  }
1041
1463
  }
1042
- applyActiveModel(response) {
1043
- for (const provider of response.providers) {
1044
- for (const model of provider.models) {
1045
- if (!model.active) {
1046
- continue;
1047
- }
1048
- this.currentModelValue = new AgentModelChanged({
1049
- threadId: this.threadPath,
1050
- sourceMessageId: response.sourceMessageId,
1051
- backend: provider.backend,
1052
- provider: provider.name,
1053
- model: model.name,
1054
- voice: model.defaultOutputVoice,
1055
- inputFormat: model.inputFormat,
1056
- outputFormat: model.outputFormat,
1057
- turnDetection: model.turnDetection,
1058
- realtimeProtocols: model.realtimeProtocols,
1059
- outputModalities: model.modalities.length > 0 ? [model.modalities[0]] : ["text"],
1060
- });
1061
- return;
1062
- }
1063
- }
1064
- }
1065
1464
  markClosed({ notify = true } = {}) {
1066
1465
  this.openState = false;
1067
1466
  this.loadStateValue = new ChatThreadSessionLoadState({
@@ -1120,12 +1519,33 @@ export class ChatThreadSession extends ChangeEmitter {
1120
1519
  }));
1121
1520
  }
1122
1521
  }
1522
+ function webSocketMessageBytes(value) {
1523
+ if (value instanceof Uint8Array) {
1524
+ return value;
1525
+ }
1526
+ if (value instanceof ArrayBuffer) {
1527
+ return new Uint8Array(value);
1528
+ }
1529
+ if (ArrayBuffer.isView(value)) {
1530
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
1531
+ }
1532
+ return undefined;
1533
+ }
1534
+ function isJsonMap(value) {
1535
+ return typeof value === "object" && value !== null && !Array.isArray(value);
1536
+ }
1537
+ function describeError(error) {
1538
+ return error instanceof Error ? error.message : String(error);
1539
+ }
1123
1540
  function sourceMessageIdFor(message) {
1124
1541
  const value = "sourceMessageId" in message ? message.sourceMessageId : undefined;
1125
1542
  return typeof value === "string" ? value : undefined;
1126
1543
  }
1544
+ function messageValue(message, key) {
1545
+ return message[key];
1546
+ }
1127
1547
  function threadMessageValue(message, key) {
1128
- const value = message[key];
1548
+ const value = messageValue(message, key);
1129
1549
  return typeof value === "string" ? value : undefined;
1130
1550
  }
1131
1551
  function agentInputText(content) {
@@ -1153,7 +1573,7 @@ function isMergeableDelta(message) {
1153
1573
  }
1154
1574
  function messageIndexKey(message) {
1155
1575
  if (isMergeableDelta(message)) {
1156
- const itemId = normalizedString(message.itemId);
1576
+ const itemId = normalizedString(messageValue(message, "itemId"));
1157
1577
  return itemId == null ? undefined : `${message.type}:${itemId}`;
1158
1578
  }
1159
1579
  return normalizedString(message.messageId);
@@ -1166,30 +1586,30 @@ function mergeDeltaMessage(existing, incoming) {
1166
1586
  const data = new Uint8Array(existing.data.length + incoming.data.length);
1167
1587
  data.set(existing.data, 0);
1168
1588
  data.set(incoming.data, existing.data.length);
1169
- return new AgentAudioGenerationDelta({ ...existing.toJson(), data });
1589
+ return AgentAudioGenerationDelta.fromJson({ ...existing.toJson(), data });
1170
1590
  }
1171
1591
  if (existing.type === agentAudioTranscriptionDeltaType && existing instanceof AgentAudioTranscriptionDelta && incoming instanceof AgentAudioTranscriptionDelta) {
1172
- return new AgentAudioTranscriptionDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1592
+ return AgentAudioTranscriptionDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1173
1593
  }
1174
1594
  if (existing.type === agentFileContentDeltaType && incoming instanceof AgentFileContentDelta) {
1175
1595
  return incoming;
1176
1596
  }
1177
1597
  if (existing.type === agentReasoningContentDeltaType && existing instanceof AgentReasoningContentDelta && incoming instanceof AgentReasoningContentDelta) {
1178
- return new AgentReasoningContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1598
+ return AgentReasoningContentDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1179
1599
  }
1180
1600
  if (existing.type === agentTextContentDeltaType && existing instanceof AgentTextContentDelta && incoming instanceof AgentTextContentDelta) {
1181
- return new AgentTextContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1601
+ return AgentTextContentDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1182
1602
  }
1183
1603
  if (existing.type === agentToolCallArgumentsDeltaType && existing instanceof AgentToolCallArgumentsDelta && incoming instanceof AgentToolCallArgumentsDelta) {
1184
- return new AgentToolCallArgumentsDelta({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
1604
+ return AgentToolCallArgumentsDelta.fromJson({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
1185
1605
  }
1186
1606
  if (existing.type === agentToolCallLogDeltaType && existing instanceof AgentToolCallLogDelta && incoming instanceof AgentToolCallLogDelta) {
1187
- return new AgentToolCallLogDelta({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
1607
+ return AgentToolCallLogDelta.fromJson({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
1188
1608
  }
1189
1609
  return undefined;
1190
1610
  }
1191
1611
  function normalizedString(value) {
1192
- const normalized = value?.trim();
1612
+ const normalized = typeof value === "string" ? value.trim() : undefined;
1193
1613
  return normalized == null || normalized === "" ? undefined : normalized;
1194
1614
  }
1195
1615
  function dateOrNull(value) {