@meshagent/meshagent-agents 0.47.1 → 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, agentThreadCloseType, agentThreadOpenType, agentThreadUnwatchType, agentThreadUpdatedType, agentThreadWatchType, 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,
@@ -367,9 +459,17 @@ export class ThreadListSubscribe {
367
459
  await this.stream.close();
368
460
  }
369
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
+ }
370
470
  export class BaseMessagingChatClient extends BaseChatClient {
371
- constructor({ room, agentName }) {
372
- super();
471
+ constructor({ room, agentName, threadCreatedPendingStartMatcher, deduplicateClientToolRequests = true, }) {
472
+ super({ threadCreatedPendingStartMatcher, deduplicateClientToolRequests });
373
473
  this.pendingParticipantWaits = new Map();
374
474
  this.started = false;
375
475
  this.hasConnected = false;
@@ -466,7 +566,7 @@ export class BaseMessagingChatClient extends BaseChatClient {
466
566
  this.attachMessagingListeners();
467
567
  this.room.messaging.start();
468
568
  this.startRoomStatusListener();
469
- await this.room.messaging.enable();
569
+ this.room.messaging.enable();
470
570
  this.waitingForParticipant = true;
471
571
  this.onMessagingChanged();
472
572
  }
@@ -517,7 +617,13 @@ export class BaseMessagingChatClient extends BaseChatClient {
517
617
  }
518
618
  return null;
519
619
  }
520
- 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
+ }
521
627
  const existing = this.agentParticipant();
522
628
  if (existing != null) {
523
629
  return existing;
@@ -525,12 +631,34 @@ export class BaseMessagingChatClient extends BaseChatClient {
525
631
  const key = waitKey == null || waitKey.trim() === "" ? uuidv4() : waitKey.trim();
526
632
  const completer = new Completer();
527
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);
528
650
  this.onMessagingChanged();
529
651
  try {
530
652
  return await completer.fut;
531
653
  }
532
654
  finally {
533
- 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
+ }
534
662
  }
535
663
  }
536
664
  async sendAgentMessage(message, { attachment } = {}) {
@@ -584,6 +712,254 @@ export class BaseMessagingChatClient extends BaseChatClient {
584
712
  }
585
713
  }
586
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
+ }
587
963
  export class MessagingChatClient extends BaseMessagingChatClient {
588
964
  constructor() {
589
965
  super(...arguments);
@@ -647,7 +1023,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
647
1023
  await super.sendAgentMessage(message, { attachment });
648
1024
  return;
649
1025
  }
650
- if (message.type === agentThreadOpenType) {
1026
+ if (message instanceof OpenThread) {
651
1027
  const previous = this.threadSubscriptions.get(message.threadId);
652
1028
  if (previous != null) {
653
1029
  await previous.close();
@@ -663,7 +1039,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
663
1039
  this.trackStream(subscription);
664
1040
  return;
665
1041
  }
666
- if (message.type === agentThreadCloseType) {
1042
+ if (message instanceof CloseThread) {
667
1043
  const subscription = this.threadSubscriptions.get(message.threadId);
668
1044
  if (subscription != null) {
669
1045
  this.threadSubscriptions.delete(message.threadId);
@@ -671,7 +1047,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
671
1047
  return;
672
1048
  }
673
1049
  }
674
- if (message.type === agentThreadWatchType) {
1050
+ if (message instanceof WatchThreads) {
675
1051
  await this.threadListSubscription?.close();
676
1052
  const stream = await this.room.messaging.stream({
677
1053
  to: participant,
@@ -684,7 +1060,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
684
1060
  this.trackStream(subscription);
685
1061
  return;
686
1062
  }
687
- if (message.type === agentThreadUnwatchType && this.threadListSubscription != null) {
1063
+ if (message instanceof UnwatchThreads && this.threadListSubscription != null) {
688
1064
  const subscription = this.threadListSubscription;
689
1065
  this.threadListSubscription = undefined;
690
1066
  await subscription.close();
@@ -722,6 +1098,9 @@ export class ChatThreadSession extends ChangeEmitter {
722
1098
  this.client = client;
723
1099
  this.threadPath = threadPath;
724
1100
  }
1101
+ static create({ client, threadPath }) {
1102
+ return new ChatThreadSession({ client, threadPath });
1103
+ }
725
1104
  get messages() {
726
1105
  return [...this._messages];
727
1106
  }
@@ -740,18 +1119,6 @@ export class ChatThreadSession extends ChangeEmitter {
740
1119
  get lastCompletedTurnId() {
741
1120
  return this.lastCompletedTurnIdValue;
742
1121
  }
743
- get activeTurnId() {
744
- return this.activeTurnIdValue;
745
- }
746
- get threadStatus() {
747
- return this.threadStatusValue;
748
- }
749
- get currentModel() {
750
- return this.currentModelValue;
751
- }
752
- get modelsResponse() {
753
- return this.modelsResponseValue;
754
- }
755
1122
  async open({ load = true, sinceTurn } = {}) {
756
1123
  if (this.openState) {
757
1124
  if (load) {
@@ -766,7 +1133,6 @@ export class ChatThreadSession extends ChangeEmitter {
766
1133
  }
767
1134
  catch (error) {
768
1135
  this.setLoadState(this.failedLoadState(error));
769
- throw error;
770
1136
  }
771
1137
  }
772
1138
  return;
@@ -785,7 +1151,6 @@ export class ChatThreadSession extends ChangeEmitter {
785
1151
  }
786
1152
  catch (error) {
787
1153
  this.setLoadState(this.failedLoadState(error));
788
- throw error;
789
1154
  }
790
1155
  }
791
1156
  resetReplayState() {
@@ -832,16 +1197,20 @@ export class ChatThreadSession extends ChangeEmitter {
832
1197
  response,
833
1198
  }));
834
1199
  }
835
- 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, }) {
836
1213
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
837
- const currentModel = this.currentModelValue;
838
- const resolvedBackend = nonEmptyTrimmed(backend) ?? currentModel?.backend;
839
- const resolvedProvider = nonEmptyTrimmed(provider) ?? currentModel?.provider;
840
- const resolvedModel = nonEmptyTrimmed(model) ?? currentModel?.model;
841
- const resolvedVoice = nonEmptyTrimmed(voice) ?? currentModel?.voice;
842
- const resolvedOutputModalities = outputModalities != null && outputModalities.length > 0
843
- ? outputModalities
844
- : currentModel?.outputModalities;
845
1214
  const payload = steer
846
1215
  ? new TurnSteer({
847
1216
  threadId: this.threadPath,
@@ -855,13 +1224,12 @@ export class ChatThreadSession extends ChangeEmitter {
855
1224
  messageId: resolvedMessageId,
856
1225
  senderName: nonEmptyTrimmed(senderName),
857
1226
  content: agentInputContent({ text, attachments }),
858
- backend: resolvedBackend,
859
- provider: resolvedProvider,
860
- model: resolvedModel,
861
- voice: resolvedVoice,
862
- 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,
863
1232
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
864
- toolChoice: toolChoice?.toJson(),
865
1233
  });
866
1234
  this.localAgentMessageIds.add(resolvedMessageId);
867
1235
  this.pendingLocalInputMessageIds.add(resolvedMessageId);
@@ -880,10 +1248,10 @@ export class ChatThreadSession extends ChangeEmitter {
880
1248
  await this.client.sendAgentMessage(new AgentRealtimeAudioChunk({
881
1249
  threadId: this.threadPath,
882
1250
  messageId: uuidv4(),
883
- format,
1251
+ format: new AgentAudioFormat(format),
884
1252
  }), { attachment: chunk });
885
1253
  }
886
- async commitRealtimeAudio({ turnId, provider, model, voice, outputModalities, }) {
1254
+ async commitRealtimeAudio({ turnId, backend, provider, model, voice, outputModalities, }) {
887
1255
  const messageId = uuidv4();
888
1256
  await this.client.sendAgentMessage(new AgentRealtimeAudioCommit({
889
1257
  threadId: this.threadPath,
@@ -894,6 +1262,7 @@ export class ChatThreadSession extends ChangeEmitter {
894
1262
  threadId: this.threadPath,
895
1263
  messageId: uuidv4(),
896
1264
  turnId,
1265
+ backend: nonEmptyTrimmed(backend),
897
1266
  provider: nonEmptyTrimmed(provider),
898
1267
  model: nonEmptyTrimmed(model),
899
1268
  voice: nonEmptyTrimmed(voice),
@@ -922,56 +1291,9 @@ export class ChatThreadSession extends ChangeEmitter {
922
1291
  const turnId = normalizedString(threadMessageValue(message, "turnId"));
923
1292
  if (turnId != null) {
924
1293
  this.lastCompletedTurnIdValue = turnId;
925
- if (this.activeTurnIdValue === turnId) {
926
- this.activeTurnIdValue = undefined;
927
- }
928
- }
929
- const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
930
- if (statusTurnId == null || statusTurnId === turnId) {
931
- this.threadStatusValue = undefined;
932
1294
  }
933
- return;
934
- }
935
- if (message.type === agentTurnStartAcceptedType || message.type === agentTurnStartedType) {
936
- const turnId = normalizedString(threadMessageValue(message, "turnId"));
937
- if (turnId != null) {
938
- this.activeTurnIdValue = turnId;
939
- }
940
- if (message.type === agentTurnStartedType) {
941
- const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
942
- if (statusTurnId == null || statusTurnId === turnId) {
943
- this.threadStatusValue = undefined;
944
- }
945
- }
946
- return;
947
- }
948
- if (message.type === agentThreadStatusType && message instanceof AgentThreadStatus) {
949
- const mode = normalizedString(message.mode);
950
- const status = normalizedString(message.status);
951
- const turnId = normalizedString(message.turnId);
952
- if (status == null) {
953
- this.threadStatusValue = undefined;
954
- if (turnId != null && this.activeTurnIdValue === turnId) {
955
- this.activeTurnIdValue = undefined;
956
- }
957
- return;
958
- }
959
- this.threadStatusValue = message;
960
- if (mode === "steerable" && turnId != null) {
961
- this.activeTurnIdValue = turnId;
962
- }
963
- return;
964
1295
  }
965
- if (message.type === agentModelChangedType && message instanceof AgentModelChanged) {
966
- this.currentModelValue = message;
967
- return;
968
- }
969
- if (message.type === agentModelsResponseType && message instanceof ModelsResponse) {
970
- this.modelsResponseValue = message;
971
- this.applyActiveModel(message);
972
- return;
973
- }
974
- if (message.type === agentThreadLoadedType) {
1296
+ else if (message.type === agentThreadLoadedType) {
975
1297
  this.setLoadState(new ChatThreadSessionLoadState({
976
1298
  phase: ChatThreadSessionLoadPhase.Loaded,
977
1299
  startedAt: this.loadStateValue.startedAt,
@@ -1049,45 +1371,21 @@ export class ChatThreadSession extends ChangeEmitter {
1049
1371
  }
1050
1372
  appendRenderableMessage(event) {
1051
1373
  const message = event.message;
1052
- if (message.type === agentTurnEndedType) {
1053
- if (message.error != null) {
1054
- this.appendMessage(event);
1055
- }
1056
- return;
1057
- }
1058
- if (message.type === agentToolCallEndedType) {
1059
- const itemId = message.error == null ? undefined : normalizedString(message.itemId);
1060
- if (itemId != null) {
1061
- this.upsertItemMessage(`tool_call:${itemId}`, event);
1062
- }
1063
- else if (message.error != null) {
1064
- this.appendMessage(event);
1065
- }
1066
- return;
1067
- }
1068
- if (message.type === agentToolCallPendingType || message.type === agentToolCallInProgressType || message.type === agentToolCallStartedType) {
1069
- const itemId = normalizedString(message.itemId);
1070
- if (itemId != null) {
1071
- this.upsertItemMessage(`tool_call:${itemId}`, event);
1072
- }
1073
- return;
1074
- }
1075
- if (message.type === agentSecretRequestedType) {
1374
+ if (message instanceof TurnEnded) {
1076
1375
  this.appendMessage(event);
1077
1376
  return;
1078
1377
  }
1079
- if (message.type === agentModelChangedType) {
1080
- this.appendMessage(event);
1081
- return;
1082
- }
1083
- if (message.type === agentThreadStartType || message.type === agentTurnStartType || message.type === agentTurnSteerType) {
1084
- if (agentInputText(message.content).trim() !== "") {
1378
+ if (message instanceof StartThread || message instanceof TurnStart || message instanceof TurnSteer) {
1379
+ if (agentInputText(messageValue(message, "content")).trim() !== "") {
1085
1380
  this.appendMessage(event);
1381
+ const messageId = normalizedString(message.messageId);
1382
+ if (messageId != null)
1383
+ this.pendingLocalInputMessageIds.add(messageId);
1086
1384
  }
1087
1385
  return;
1088
1386
  }
1089
- if (message.type === agentTurnStartAcceptedType || message.type === agentTurnSteerAcceptedType) {
1090
- const sourceMessageId = normalizedString(sourceMessageIdFor(message));
1387
+ if (message instanceof TurnStartAccepted) {
1388
+ const sourceMessageId = normalizedString(message.sourceMessageId);
1091
1389
  if (sourceMessageId != null && this.localAgentMessageIds.has(sourceMessageId)) {
1092
1390
  this.pendingLocalInputMessageIds.delete(sourceMessageId);
1093
1391
  return;
@@ -1095,36 +1393,21 @@ export class ChatThreadSession extends ChangeEmitter {
1095
1393
  if (agentInputText(message.content).trim() !== "") {
1096
1394
  this.appendMessage(event, { beforePendingLocalInputs: true });
1097
1395
  }
1098
- else {
1099
- this.appendMessage(event);
1100
- }
1101
- return;
1102
- }
1103
- if (message.type === agentTurnStartedType ||
1104
- message.type === agentTurnSteeredType ||
1105
- message.type === agentTurnStartRejectedType ||
1106
- message.type === agentTurnSteerRejectedType ||
1107
- message.type === agentTurnInterruptAcceptedType ||
1108
- message.type === agentTurnInterruptedType) {
1109
- this.appendMessage(event);
1110
1396
  return;
1111
1397
  }
1112
1398
  if (isMergeableDelta(message)) {
1113
1399
  this.appendOrMergeDelta(event);
1114
1400
  return;
1115
1401
  }
1116
- if (message.type === agentImageGenerationPartialType || message.type === agentImageGenerationCompletedType) {
1117
- const itemId = normalizedString(message.itemId);
1118
- if (itemId != null) {
1119
- this.upsertItemMessage(`image_generation:${itemId}`, event);
1120
- }
1402
+ if (message instanceof AgentImageGenerationPartial || message instanceof AgentImageGenerationCompleted) {
1403
+ this.appendMessage(event);
1121
1404
  return;
1122
1405
  }
1123
- if (message.type === agentUsageUpdatedType) {
1406
+ if (message instanceof AgentClientToolCallRequested) {
1124
1407
  this.appendMessage(event);
1125
1408
  return;
1126
1409
  }
1127
- if (message instanceof AgentClientToolCallRequested) {
1410
+ if (message instanceof AgentConnectionStatus || message instanceof AgentThreadMessage) {
1128
1411
  this.appendMessage(event);
1129
1412
  }
1130
1413
  }
@@ -1144,15 +1427,6 @@ export class ChatThreadSession extends ChangeEmitter {
1144
1427
  this.messageIndexes.set(normalizedMessageId, this._messages.length);
1145
1428
  this._messages.push(event);
1146
1429
  }
1147
- upsertItemMessage(key, event) {
1148
- const existingIndex = this.messageIndexes.get(key);
1149
- if (existingIndex == null) {
1150
- this.messageIndexes.set(key, this._messages.length);
1151
- this._messages.push(event);
1152
- return;
1153
- }
1154
- this._messages[existingIndex] = event;
1155
- }
1156
1430
  appendOrMergeDelta(event) {
1157
1431
  const message = event.message;
1158
1432
  const normalizedMessageId = normalizedString(message.messageId);
@@ -1162,7 +1436,7 @@ export class ChatThreadSession extends ChangeEmitter {
1162
1436
  }
1163
1437
  this.mergedDeltaMessageIds.add(normalizedMessageId);
1164
1438
  }
1165
- const itemId = normalizedString(message.itemId);
1439
+ const itemId = normalizedString(messageValue(message, "itemId"));
1166
1440
  if (itemId == null) {
1167
1441
  return;
1168
1442
  }
@@ -1176,7 +1450,7 @@ export class ChatThreadSession extends ChangeEmitter {
1176
1450
  const existing = this._messages[existingIndex].message;
1177
1451
  const merged = mergeDeltaMessage(existing, message);
1178
1452
  if (merged != null) {
1179
- this._messages[existingIndex] = new AgentMessageEvent({ message: merged, createdAt: event.createdAt, attachment: event.attachment });
1453
+ this._messages[existingIndex].replaceMessage(merged);
1180
1454
  }
1181
1455
  }
1182
1456
  indexMessagesFrom(start) {
@@ -1187,29 +1461,6 @@ export class ChatThreadSession extends ChangeEmitter {
1187
1461
  }
1188
1462
  }
1189
1463
  }
1190
- applyActiveModel(response) {
1191
- for (const provider of response.providers) {
1192
- for (const model of provider.models) {
1193
- if (!model.active) {
1194
- continue;
1195
- }
1196
- this.currentModelValue = new AgentModelChanged({
1197
- threadId: this.threadPath,
1198
- sourceMessageId: response.sourceMessageId,
1199
- backend: provider.backend,
1200
- provider: provider.name,
1201
- model: model.name,
1202
- voice: model.defaultOutputVoice,
1203
- inputFormat: model.inputFormat,
1204
- outputFormat: model.outputFormat,
1205
- turnDetection: model.turnDetection,
1206
- realtimeProtocols: model.realtimeProtocols,
1207
- outputModalities: model.modalities.length > 0 ? [model.modalities[0]] : ["text"],
1208
- });
1209
- return;
1210
- }
1211
- }
1212
- }
1213
1464
  markClosed({ notify = true } = {}) {
1214
1465
  this.openState = false;
1215
1466
  this.loadStateValue = new ChatThreadSessionLoadState({
@@ -1268,12 +1519,33 @@ export class ChatThreadSession extends ChangeEmitter {
1268
1519
  }));
1269
1520
  }
1270
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
+ }
1271
1540
  function sourceMessageIdFor(message) {
1272
1541
  const value = "sourceMessageId" in message ? message.sourceMessageId : undefined;
1273
1542
  return typeof value === "string" ? value : undefined;
1274
1543
  }
1544
+ function messageValue(message, key) {
1545
+ return message[key];
1546
+ }
1275
1547
  function threadMessageValue(message, key) {
1276
- const value = message[key];
1548
+ const value = messageValue(message, key);
1277
1549
  return typeof value === "string" ? value : undefined;
1278
1550
  }
1279
1551
  function agentInputText(content) {
@@ -1301,7 +1573,7 @@ function isMergeableDelta(message) {
1301
1573
  }
1302
1574
  function messageIndexKey(message) {
1303
1575
  if (isMergeableDelta(message)) {
1304
- const itemId = normalizedString(message.itemId);
1576
+ const itemId = normalizedString(messageValue(message, "itemId"));
1305
1577
  return itemId == null ? undefined : `${message.type}:${itemId}`;
1306
1578
  }
1307
1579
  return normalizedString(message.messageId);
@@ -1314,30 +1586,30 @@ function mergeDeltaMessage(existing, incoming) {
1314
1586
  const data = new Uint8Array(existing.data.length + incoming.data.length);
1315
1587
  data.set(existing.data, 0);
1316
1588
  data.set(incoming.data, existing.data.length);
1317
- return new AgentAudioGenerationDelta({ ...existing.toJson(), data });
1589
+ return AgentAudioGenerationDelta.fromJson({ ...existing.toJson(), data });
1318
1590
  }
1319
1591
  if (existing.type === agentAudioTranscriptionDeltaType && existing instanceof AgentAudioTranscriptionDelta && incoming instanceof AgentAudioTranscriptionDelta) {
1320
- return new AgentAudioTranscriptionDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1592
+ return AgentAudioTranscriptionDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1321
1593
  }
1322
1594
  if (existing.type === agentFileContentDeltaType && incoming instanceof AgentFileContentDelta) {
1323
1595
  return incoming;
1324
1596
  }
1325
1597
  if (existing.type === agentReasoningContentDeltaType && existing instanceof AgentReasoningContentDelta && incoming instanceof AgentReasoningContentDelta) {
1326
- return new AgentReasoningContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1598
+ return AgentReasoningContentDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1327
1599
  }
1328
1600
  if (existing.type === agentTextContentDeltaType && existing instanceof AgentTextContentDelta && incoming instanceof AgentTextContentDelta) {
1329
- return new AgentTextContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1601
+ return AgentTextContentDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1330
1602
  }
1331
1603
  if (existing.type === agentToolCallArgumentsDeltaType && existing instanceof AgentToolCallArgumentsDelta && incoming instanceof AgentToolCallArgumentsDelta) {
1332
- return new AgentToolCallArgumentsDelta({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
1604
+ return AgentToolCallArgumentsDelta.fromJson({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
1333
1605
  }
1334
1606
  if (existing.type === agentToolCallLogDeltaType && existing instanceof AgentToolCallLogDelta && incoming instanceof AgentToolCallLogDelta) {
1335
- return new AgentToolCallLogDelta({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
1607
+ return AgentToolCallLogDelta.fromJson({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
1336
1608
  }
1337
1609
  return undefined;
1338
1610
  }
1339
1611
  function normalizedString(value) {
1340
- const normalized = value?.trim();
1612
+ const normalized = typeof value === "string" ? value.trim() : undefined;
1341
1613
  return normalized == null || normalized === "" ? undefined : normalized;
1342
1614
  }
1343
1615
  function dateOrNull(value) {