@meshagent/meshagent-agents 0.43.3 → 0.44.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,15 @@
1
1
  import { RemoteParticipant } from "@meshagent/meshagent";
2
2
  import type { RoomClient, Content } from "@meshagent/meshagent";
3
- import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, AgentConnectionStatus } from "./agent-messages.js";
3
+ import { AgentMessage, AgentModelChanged, AgentThreadStatus, ModelsResponse, ToolChoice, AgentConnectionStatus } from "./agent-messages.js";
4
4
  import type { AgentPayload, JsonMap, ClientToolkitDescription } from "./agent-messages.js";
5
5
  import { ChangeEmitter } from "./change-emitter.js";
6
6
  export declare class AgentMessageEvent {
7
7
  readonly message: AgentMessage;
8
+ readonly createdAt: Date;
8
9
  readonly attachment?: Uint8Array;
9
- constructor({ message, attachment }: {
10
+ constructor({ message, createdAt, attachment }: {
10
11
  message: AgentMessage;
12
+ createdAt?: Date;
11
13
  attachment?: Uint8Array;
12
14
  });
13
15
  get payload(): AgentPayload;
@@ -96,7 +98,7 @@ export declare abstract class BaseChatClient extends ChangeEmitter {
96
98
  drainPendingEvents?: boolean;
97
99
  }): ChatThreadSession;
98
100
  private openSession;
99
- startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, omitContent, }: {
101
+ startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, toolChoice, omitContent, }: {
100
102
  messageId?: string;
101
103
  message: string;
102
104
  attachments: string[];
@@ -109,14 +111,17 @@ export declare abstract class BaseChatClient extends ChangeEmitter {
109
111
  realtimeProtocol?: string;
110
112
  senderName?: string;
111
113
  clientToolkits?: ClientToolkitDescription[];
114
+ toolChoice?: ToolChoice;
112
115
  omitContent?: boolean;
113
116
  }): Promise<ChatThreadStartResult>;
117
+ private resolvedStartThreadMessage;
114
118
  abstract sendAgentMessage(message: AgentMessage, options?: {
115
119
  attachment?: Uint8Array;
116
120
  ignoreOffline?: boolean;
117
121
  }): Promise<void>;
118
122
  private drainPendingSessionEvents;
119
- handleAgentMessage(message: AgentMessage, { attachment }?: {
123
+ handleAgentMessage(message: AgentMessage, { createdAt, attachment }?: {
124
+ createdAt?: Date;
120
125
  attachment?: Uint8Array;
121
126
  }): void;
122
127
  emitConnectionStatus({ status, message, reason, retryDelay, }: {
@@ -132,6 +137,11 @@ export declare class MessagingChatClient extends BaseChatClient {
132
137
  readonly agentName?: string;
133
138
  private readonly pendingParticipantWaits;
134
139
  private started;
140
+ private agentParticipantId?;
141
+ private hasConnected;
142
+ private waitingForParticipant;
143
+ private roomAbortController?;
144
+ private messagingListenersAttached;
135
145
  constructor({ room, agentName }: {
136
146
  room: RoomClient;
137
147
  agentName?: string;
@@ -139,6 +149,8 @@ export declare class MessagingChatClient extends BaseChatClient {
139
149
  localParticipantName(): string | undefined;
140
150
  start(): Promise<void>;
141
151
  stop(): Promise<void>;
152
+ private attachMessagingListeners;
153
+ private detachMessagingListeners;
142
154
  agentParticipant(): RemoteParticipant | null;
143
155
  waitForAgentParticipant({ waitKey }?: {
144
156
  waitKey?: string;
@@ -148,6 +160,10 @@ export declare class MessagingChatClient extends BaseChatClient {
148
160
  ignoreOffline?: boolean;
149
161
  }): Promise<void>;
150
162
  private readonly onMessagingChanged;
163
+ private readonly onMessagingParticipantChanged;
164
+ private readonly onMessagingParticipantRemoved;
165
+ private startRoomStatusListener;
166
+ private reopenOpenSessions;
151
167
  private readonly onMessagingMessage;
152
168
  }
153
169
  export declare class ChatThreadSession extends ChangeEmitter {
@@ -186,6 +202,7 @@ export declare class ChatThreadSession extends ChangeEmitter {
186
202
  load?: boolean;
187
203
  sinceTurn?: string;
188
204
  }): Promise<void>;
205
+ private resetReplayState;
189
206
  close(): Promise<void>;
190
207
  requestModels({ ignoreOffline }?: {
191
208
  ignoreOffline?: boolean;
@@ -202,7 +219,7 @@ export declare class ChatThreadSession extends ChangeEmitter {
202
219
  requestId: string;
203
220
  response: Content;
204
221
  }): Promise<void>;
205
- sendText({ messageId, text, attachments, steer, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, }: {
222
+ sendText({ messageId, text, attachments, steer, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, toolChoice, }: {
206
223
  messageId?: string;
207
224
  text: string;
208
225
  attachments: string[];
@@ -215,6 +232,7 @@ export declare class ChatThreadSession extends ChangeEmitter {
215
232
  outputModalities?: string[];
216
233
  senderName?: string;
217
234
  clientToolkits?: ClientToolkitDescription[];
235
+ toolChoice?: ToolChoice;
218
236
  }): Promise<string>;
219
237
  sendRealtimeAudioChunk({ chunk, format }: {
220
238
  chunk: Uint8Array;
@@ -1,14 +1,15 @@
1
1
  import { Completer, } from "@meshagent/meshagent";
2
2
  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, agentThreadLoadedType, agentConnectionStatusType, agentThreadStatusType, agentThreadStartType, agentThreadStartRejectedType, agentThreadStartedType, agentToolCallArgumentsDeltaType, agentToolCallLogDeltaType, agentTurnEndedType, agentTurnStartAcceptedType, agentTurnStartRejectedType, agentTurnStartType, agentTurnStartedType, agentTurnSteerAcceptedType, agentTurnSteerRejectedType, agentTurnSteerType, agentTurnSteeredType, } from "./agent-messages.js";
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";
4
4
  import { ChangeEmitter } from "./change-emitter.js";
5
5
  export class AgentMessageEvent {
6
- constructor({ message, attachment }) {
6
+ constructor({ message, createdAt, attachment }) {
7
7
  this.message = message;
8
+ this.createdAt = createdAt ?? dateOrNull(message.createdAt) ?? new Date();
8
9
  this.attachment = attachment;
9
10
  }
10
11
  get payload() {
11
- return this.message.toJson();
12
+ return { ...this.message.toJson(), created_at: this.createdAt.toISOString() };
12
13
  }
13
14
  }
14
15
  function agentRoomMessageFromEvent(event) {
@@ -196,7 +197,7 @@ export class BaseChatClient extends ChangeEmitter {
196
197
  this.notifyListeners();
197
198
  });
198
199
  }
199
- async startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, omitContent = false, }) {
200
+ async startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, toolChoice, omitContent = false, }) {
200
201
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
201
202
  const resolvedSenderName = this.cleanParticipantName(senderName) ?? this.localParticipantName();
202
203
  const payload = new StartThread({
@@ -211,6 +212,7 @@ export class BaseChatClient extends ChangeEmitter {
211
212
  realtimeProtocol: nonEmptyTrimmed(realtimeProtocol),
212
213
  senderName: resolvedSenderName,
213
214
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
215
+ toolChoice: toolChoice?.toJson(),
214
216
  });
215
217
  const completer = new Completer();
216
218
  this.pendingStartRequests.set(resolvedMessageId, completer);
@@ -225,7 +227,10 @@ export class BaseChatClient extends ChangeEmitter {
225
227
  }
226
228
  const session = this.openThread(threadPath, { load: false, drainPendingEvents: false });
227
229
  if (!omitContent) {
228
- session.addAgentMessage(new AgentMessageEvent({ message: payload }));
230
+ session.addAgentMessage(new AgentMessageEvent({
231
+ message: this.resolvedStartThreadMessage({ payload, threadPath }),
232
+ createdAt: dateOrNull(payload.createdAt) ?? undefined,
233
+ }));
229
234
  }
230
235
  this.drainPendingSessionEvents(threadPath, session);
231
236
  return new ChatThreadStartResult({
@@ -240,6 +245,23 @@ export class BaseChatClient extends ChangeEmitter {
240
245
  this.pendingStartRequests.delete(resolvedMessageId);
241
246
  }
242
247
  }
248
+ resolvedStartThreadMessage({ payload, threadPath, }) {
249
+ return new TurnStart({
250
+ threadId: threadPath,
251
+ messageId: payload.messageId,
252
+ senderName: payload.senderName,
253
+ content: payload.content,
254
+ backend: payload.backend,
255
+ provider: payload.provider,
256
+ model: payload.model,
257
+ voice: payload.voice,
258
+ outputModalities: payload.outputModalities,
259
+ instructions: payload.instructions,
260
+ mcp: payload.mcp,
261
+ clientToolkits: payload.clientToolkits,
262
+ toolkits: payload.toolkits,
263
+ });
264
+ }
243
265
  drainPendingSessionEvents(threadPath, session) {
244
266
  const pendingEvents = this.pendingSessionEventsByPath.get(threadPath);
245
267
  if (pendingEvents == null) {
@@ -250,16 +272,11 @@ export class BaseChatClient extends ChangeEmitter {
250
272
  session.addAgentMessage(event);
251
273
  }
252
274
  }
253
- handleAgentMessage(message, { attachment } = {}) {
275
+ handleAgentMessage(message, { createdAt, attachment } = {}) {
254
276
  if (message.type === agentConnectionStatusType) {
255
277
  this._connectionStatus = message;
256
- for (const session of this.sessionsByPath.values()) {
257
- if (session.isOpen) {
258
- session.addAgentMessage(new AgentMessageEvent({ message, attachment }));
259
- }
260
- }
261
278
  }
262
- this.eventQueue.add(new AgentMessageEvent({ message, attachment }));
279
+ this.eventQueue.add(new AgentMessageEvent({ message, createdAt, attachment }));
263
280
  const sourceMessageId = sourceMessageIdFor(message);
264
281
  if ((message.type === agentThreadStartedType || message.type === agentThreadStartRejectedType) &&
265
282
  sourceMessageId != null &&
@@ -274,7 +291,7 @@ export class BaseChatClient extends ChangeEmitter {
274
291
  this.notifyListeners();
275
292
  return;
276
293
  }
277
- const event = new AgentMessageEvent({ message, attachment });
294
+ const event = new AgentMessageEvent({ message, createdAt, attachment });
278
295
  const session = this.sessionsByPath.get(threadPath);
279
296
  if (session != null) {
280
297
  session.addAgentMessage(event);
@@ -295,11 +312,6 @@ export class BaseChatClient extends ChangeEmitter {
295
312
  });
296
313
  this._connectionStatus = event;
297
314
  this.eventQueue.add(new AgentMessageEvent({ message: event }));
298
- for (const session of this.sessionsByPath.values()) {
299
- if (session.isOpen) {
300
- session.addAgentMessage(new AgentMessageEvent({ message: event }));
301
- }
302
- }
303
315
  this.notifyListeners();
304
316
  }
305
317
  removeThreadSession(threadPath) {
@@ -317,10 +329,50 @@ export class MessagingChatClient extends BaseChatClient {
317
329
  super();
318
330
  this.pendingParticipantWaits = new Map();
319
331
  this.started = false;
332
+ this.hasConnected = false;
333
+ this.waitingForParticipant = false;
334
+ this.messagingListenersAttached = false;
320
335
  this.onMessagingChanged = () => {
321
336
  const participant = this.agentParticipant();
337
+ const participantId = participant?.id;
338
+ if (participantId !== this.agentParticipantId) {
339
+ if (participantId == null) {
340
+ if (this.agentParticipantId != null) {
341
+ this.waitingForParticipant = true;
342
+ this.emitConnectionStatus({
343
+ status: "disconnected",
344
+ message: "Agent messaging disconnected",
345
+ reason: "participant_disconnected",
346
+ });
347
+ this.emitConnectionStatus({
348
+ status: "reconnecting",
349
+ message: "Waiting for agent messaging",
350
+ reason: "participant_disconnected",
351
+ });
352
+ }
353
+ else if (this.started && this.waitingForParticipant) {
354
+ this.emitConnectionStatus({
355
+ status: "reconnecting",
356
+ message: "Waiting for agent messaging",
357
+ reason: "participant_offline",
358
+ });
359
+ }
360
+ }
361
+ else {
362
+ const status = this.hasConnected ? "reconnected" : "connected";
363
+ this.hasConnected = true;
364
+ this.waitingForParticipant = false;
365
+ this.emitConnectionStatus({
366
+ status,
367
+ message: status === "reconnected" ? "Agent messaging reconnected" : "Agent messaging connected",
368
+ });
369
+ if (status === "reconnected") {
370
+ void this.reopenOpenSessions();
371
+ }
372
+ }
373
+ this.agentParticipantId = participantId;
374
+ }
322
375
  if (participant != null) {
323
- this.emitConnectionStatus({ status: "connected", message: "Agent messaging connected" });
324
376
  for (const wait of this.pendingParticipantWaits.values()) {
325
377
  if (!wait.completed) {
326
378
  wait.complete(participant);
@@ -329,6 +381,12 @@ export class MessagingChatClient extends BaseChatClient {
329
381
  }
330
382
  this.notifyListeners();
331
383
  };
384
+ this.onMessagingParticipantChanged = () => {
385
+ this.onMessagingChanged();
386
+ };
387
+ this.onMessagingParticipantRemoved = () => {
388
+ this.onMessagingChanged();
389
+ };
332
390
  this.onMessagingMessage = (event) => {
333
391
  this.onMessagingChanged();
334
392
  const roomMessage = agentRoomMessageFromEvent(event);
@@ -336,14 +394,19 @@ export class MessagingChatClient extends BaseChatClient {
336
394
  return;
337
395
  }
338
396
  const participant = this.agentParticipant();
339
- if (participant == null || roomMessage.fromParticipantId !== participant.id) {
397
+ const fromSelectedAgent = participant != null && roomMessage.fromParticipantId === participant.id;
398
+ const fromParticipantlessThreadList = roomMessage.fromParticipantId == null && isThreadListMessage(roomMessage.message);
399
+ if (!fromSelectedAgent && !fromParticipantlessThreadList) {
340
400
  return;
341
401
  }
342
- this.handleAgentMessage(AgentMessageFactory.fromJson(roomMessage.message), { attachment: roomMessage.attachment });
402
+ this.handleAgentMessage(AgentMessageFactory.fromJson(roomMessage.message), {
403
+ createdAt: dateOrNull(roomMessage.message.created_at),
404
+ attachment: roomMessage.attachment,
405
+ });
343
406
  };
344
407
  this.room = room;
345
408
  this.agentName = agentName;
346
- this.room.messaging.addListener("message", this.onMessagingMessage);
409
+ this.attachMessagingListeners();
347
410
  }
348
411
  localParticipantName() {
349
412
  const name = this.room.localParticipant?.getAttribute("name");
@@ -354,12 +417,18 @@ export class MessagingChatClient extends BaseChatClient {
354
417
  return;
355
418
  }
356
419
  this.started = true;
420
+ this.attachMessagingListeners();
357
421
  this.room.messaging.start();
358
- this.room.messaging.enable();
422
+ this.startRoomStatusListener();
423
+ await this.room.messaging.enable();
424
+ this.waitingForParticipant = true;
425
+ this.onMessagingChanged();
359
426
  }
360
427
  async stop() {
361
428
  this.started = false;
362
- this.room.messaging.removeListener("message", this.onMessagingMessage);
429
+ this.roomAbortController?.abort();
430
+ this.roomAbortController = undefined;
431
+ this.detachMessagingListeners();
363
432
  for (const wait of this.pendingParticipantWaits.values()) {
364
433
  if (!wait.completed) {
365
434
  wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
@@ -367,12 +436,35 @@ export class MessagingChatClient extends BaseChatClient {
367
436
  }
368
437
  this.pendingParticipantWaits.clear();
369
438
  }
439
+ attachMessagingListeners() {
440
+ if (this.messagingListenersAttached) {
441
+ return;
442
+ }
443
+ this.room.messaging.addListener("message", this.onMessagingMessage);
444
+ this.room.messaging.addListener("participant_added", this.onMessagingParticipantChanged);
445
+ this.room.messaging.addListener("participant_removed", this.onMessagingParticipantRemoved);
446
+ this.room.messaging.addListener("messaging_enabled", this.onMessagingParticipantChanged);
447
+ this.messagingListenersAttached = true;
448
+ }
449
+ detachMessagingListeners() {
450
+ if (!this.messagingListenersAttached) {
451
+ return;
452
+ }
453
+ this.room.messaging.removeListener("messaging_enabled", this.onMessagingParticipantChanged);
454
+ this.room.messaging.removeListener("participant_removed", this.onMessagingParticipantRemoved);
455
+ this.room.messaging.removeListener("participant_added", this.onMessagingParticipantChanged);
456
+ this.room.messaging.removeListener("message", this.onMessagingMessage);
457
+ this.messagingListenersAttached = false;
458
+ }
370
459
  agentParticipant() {
371
460
  const normalizedAgentName = this.agentName?.trim();
372
461
  for (const participant of this.room.messaging.remoteParticipants) {
373
462
  if (normalizedAgentName != null && normalizedAgentName !== "" && participant.getAttribute("name") !== normalizedAgentName) {
374
463
  continue;
375
464
  }
465
+ if (normalizedAgentName != null && normalizedAgentName !== "") {
466
+ return participant;
467
+ }
376
468
  if (participant.getAttribute("supports_agent_messages") === true) {
377
469
  return participant;
378
470
  }
@@ -397,7 +489,13 @@ export class MessagingChatClient extends BaseChatClient {
397
489
  }
398
490
  async sendAgentMessage(message, { attachment, ignoreOffline = false } = {}) {
399
491
  await this.start();
400
- const participant = this.agentParticipant() ?? await this.waitForAgentParticipant({ waitKey: message.messageId });
492
+ let participant = this.agentParticipant();
493
+ if (participant == null) {
494
+ if (ignoreOffline) {
495
+ return;
496
+ }
497
+ participant = await this.waitForAgentParticipant({ waitKey: message.messageId });
498
+ }
401
499
  await this.room.messaging.sendMessage({
402
500
  to: participant,
403
501
  type: agentRoomMessageType,
@@ -406,6 +504,49 @@ export class MessagingChatClient extends BaseChatClient {
406
504
  ignoreOffline,
407
505
  });
408
506
  }
507
+ startRoomStatusListener() {
508
+ if (this.roomAbortController != null) {
509
+ return;
510
+ }
511
+ const controller = new AbortController();
512
+ this.roomAbortController = controller;
513
+ void (async () => {
514
+ for await (const event of this.room.listen({ abortSignal: controller.signal })) {
515
+ if (controller.signal.aborted || event.name !== "room.status") {
516
+ continue;
517
+ }
518
+ const status = String(event.status ?? "").trim().toLowerCase();
519
+ const message = String(event.message ?? "").trim();
520
+ if (status === "connected" || status === "ready" || status === "reconnected") {
521
+ this.waitingForParticipant = true;
522
+ this.onMessagingChanged();
523
+ }
524
+ else if (status === "disconnected" || status === "reconnecting" || status === "connecting") {
525
+ this.agentParticipantId = undefined;
526
+ this.waitingForParticipant = true;
527
+ this.emitConnectionStatus({
528
+ status: status === "disconnected" ? "disconnected" : "reconnecting",
529
+ message: message !== "" ? message : status === "disconnected" ? "Agent messaging disconnected" : "Waiting for agent messaging",
530
+ reason: message !== "" ? message : undefined,
531
+ });
532
+ }
533
+ }
534
+ })().catch(() => undefined);
535
+ }
536
+ async reopenOpenSessions() {
537
+ for (const session of this.sessions) {
538
+ if (!session.isOpen) {
539
+ continue;
540
+ }
541
+ await session.open({ load: true, sinceTurn: session.lastCompletedTurnId });
542
+ }
543
+ }
544
+ }
545
+ function isThreadListMessage(message) {
546
+ return message.type === agentThreadListedType
547
+ || message.type === agentThreadCreatedType
548
+ || message.type === agentThreadUpdatedType
549
+ || message.type === agentThreadDeletedType;
409
550
  }
410
551
  export class ChatThreadSession extends ChangeEmitter {
411
552
  constructor({ client, threadPath }) {
@@ -454,6 +595,9 @@ export class ChatThreadSession extends ChangeEmitter {
454
595
  async open({ load = true, sinceTurn } = {}) {
455
596
  if (this.openState) {
456
597
  if (load) {
598
+ if (sinceTurn == null) {
599
+ this.resetReplayState();
600
+ }
457
601
  const openThread = new OpenThread({ threadId: this.threadPath, load: true, sinceTurn });
458
602
  this.setLoadState(this.loadingStateForOpen(openThread, sinceTurn));
459
603
  try {
@@ -484,6 +628,17 @@ export class ChatThreadSession extends ChangeEmitter {
484
628
  throw error;
485
629
  }
486
630
  }
631
+ resetReplayState() {
632
+ this._messages.length = 0;
633
+ this.messageIndexes.clear();
634
+ this.localAgentMessageIds.clear();
635
+ this.pendingLocalInputMessageIds.clear();
636
+ this.mergedDeltaMessageIds.clear();
637
+ this.pendingInputsById.clear();
638
+ this.lastCompletedTurnIdValue = undefined;
639
+ this.lastAgentMessageType = undefined;
640
+ this.lastAgentMessageAt = undefined;
641
+ }
487
642
  async close() {
488
643
  if (!this.openState) {
489
644
  return;
@@ -515,7 +670,7 @@ export class ChatThreadSession extends ChangeEmitter {
515
670
  response,
516
671
  }));
517
672
  }
518
- async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
673
+ async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, toolChoice, }) {
519
674
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
520
675
  const currentModel = this.currentModelValue;
521
676
  const resolvedBackend = nonEmptyTrimmed(backend) ?? currentModel?.backend;
@@ -544,6 +699,7 @@ export class ChatThreadSession extends ChangeEmitter {
544
699
  voice: resolvedVoice,
545
700
  outputModalities: resolvedOutputModalities,
546
701
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
702
+ toolChoice: toolChoice?.toJson(),
547
703
  });
548
704
  this.localAgentMessageIds.add(resolvedMessageId);
549
705
  this.pendingLocalInputMessageIds.add(resolvedMessageId);
@@ -593,9 +749,9 @@ export class ChatThreadSession extends ChangeEmitter {
593
749
  const message = event.message;
594
750
  const type = message.type;
595
751
  this.lastAgentMessageType = type;
596
- this.lastAgentMessageAt = new Date();
752
+ this.lastAgentMessageAt = event.createdAt;
597
753
  this.updateSessionState(message);
598
- this.updatePendingInputsForMessage(message);
754
+ this.updatePendingInputsForMessage(message, event.createdAt);
599
755
  this.appendRenderableMessage(event);
600
756
  this.notifyListeners();
601
757
  }
@@ -661,7 +817,7 @@ export class ChatThreadSession extends ChangeEmitter {
661
817
  }), { notify: false });
662
818
  }
663
819
  }
664
- updatePendingInputsForMessage(message) {
820
+ updatePendingInputsForMessage(message, createdAt) {
665
821
  const type = message.type;
666
822
  const messageId = message.messageId;
667
823
  const sourceMessageId = sourceMessageIdFor(message);
@@ -675,7 +831,7 @@ export class ChatThreadSession extends ChangeEmitter {
675
831
  messageType: type,
676
832
  threadPath: this.threadPath,
677
833
  payload: message,
678
- createdAt: new Date(),
834
+ createdAt,
679
835
  awaitingAcceptance: true,
680
836
  awaitingApplication: true,
681
837
  }));
@@ -714,6 +870,31 @@ export class ChatThreadSession extends ChangeEmitter {
714
870
  }
715
871
  return;
716
872
  }
873
+ if (message.type === agentToolCallEndedType) {
874
+ const itemId = message.error == null ? undefined : normalizedString(message.itemId);
875
+ if (itemId != null) {
876
+ this.upsertItemMessage(`tool_call:${itemId}`, event);
877
+ }
878
+ else if (message.error != null) {
879
+ this.appendMessage(event);
880
+ }
881
+ return;
882
+ }
883
+ if (message.type === agentToolCallPendingType || message.type === agentToolCallInProgressType || message.type === agentToolCallStartedType) {
884
+ const itemId = normalizedString(message.itemId);
885
+ if (itemId != null) {
886
+ this.upsertItemMessage(`tool_call:${itemId}`, event);
887
+ }
888
+ return;
889
+ }
890
+ if (message.type === agentSecretRequestedType) {
891
+ this.appendMessage(event);
892
+ return;
893
+ }
894
+ if (message.type === agentModelChangedType) {
895
+ this.appendMessage(event);
896
+ return;
897
+ }
717
898
  if (message.type === agentThreadStartType || message.type === agentTurnStartType || message.type === agentTurnSteerType) {
718
899
  if (agentInputText(message.content).trim() !== "") {
719
900
  this.appendMessage(event);
@@ -729,6 +910,18 @@ export class ChatThreadSession extends ChangeEmitter {
729
910
  if (agentInputText(message.content).trim() !== "") {
730
911
  this.appendMessage(event, { beforePendingLocalInputs: true });
731
912
  }
913
+ else {
914
+ this.appendMessage(event);
915
+ }
916
+ return;
917
+ }
918
+ if (message.type === agentTurnStartedType ||
919
+ message.type === agentTurnSteeredType ||
920
+ message.type === agentTurnStartRejectedType ||
921
+ message.type === agentTurnSteerRejectedType ||
922
+ message.type === agentTurnInterruptAcceptedType ||
923
+ message.type === agentTurnInterruptedType) {
924
+ this.appendMessage(event);
732
925
  return;
733
926
  }
734
927
  if (isMergeableDelta(message)) {
@@ -742,6 +935,10 @@ export class ChatThreadSession extends ChangeEmitter {
742
935
  }
743
936
  return;
744
937
  }
938
+ if (message.type === agentUsageUpdatedType) {
939
+ this.appendMessage(event);
940
+ return;
941
+ }
745
942
  if (message instanceof AgentClientToolCallRequested) {
746
943
  this.appendMessage(event);
747
944
  }
@@ -794,7 +991,7 @@ export class ChatThreadSession extends ChangeEmitter {
794
991
  const existing = this._messages[existingIndex].message;
795
992
  const merged = mergeDeltaMessage(existing, message);
796
993
  if (merged != null) {
797
- this._messages[existingIndex] = new AgentMessageEvent({ message: merged, attachment: event.attachment });
994
+ this._messages[existingIndex] = new AgentMessageEvent({ message: merged, createdAt: event.createdAt, attachment: event.attachment });
798
995
  }
799
996
  }
800
997
  indexMessagesFrom(start) {
@@ -958,6 +1155,16 @@ function normalizedString(value) {
958
1155
  const normalized = value?.trim();
959
1156
  return normalized == null || normalized === "" ? undefined : normalized;
960
1157
  }
1158
+ function dateOrNull(value) {
1159
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
1160
+ return value;
1161
+ }
1162
+ if (typeof value !== "string" || value.trim() === "") {
1163
+ return undefined;
1164
+ }
1165
+ const parsed = new Date(value);
1166
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
1167
+ }
961
1168
  function nonEmptyTrimmed(value) {
962
1169
  const normalized = value?.trim();
963
1170
  return normalized == null || normalized === "" ? undefined : normalized;
@@ -93,6 +93,7 @@ export declare class AgentThreadStorageRepository extends ThreadStorageRepositor
93
93
  renameThread(threadPath: string, name: string): Promise<void>;
94
94
  private requestList;
95
95
  private handleEvent;
96
+ private reconnectWatch;
96
97
  private handleThreadsListed;
97
98
  private upsertThread;
98
99
  private entryFromAgentThread;
@@ -1,6 +1,6 @@
1
1
  import { Field, Schema, Utf8, tableFromArrays, } from "apache-arrow";
2
2
  import { ChangeEmitter } from "./change-emitter.js";
3
- import { DeleteThread, ListThreads, RenameThread, } from "./agent-messages.js";
3
+ import { DeleteThread, ListThreads, RenameThread, UnwatchThreads, WatchThreads, } from "./agent-messages.js";
4
4
  export const defaultUntitledThreadName = "New Chat";
5
5
  const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
6
6
  export class ThreadListEntry {
@@ -304,10 +304,12 @@ export class AgentThreadStorageRepository extends ThreadStorageRepository {
304
304
  this.handleEvent(next.value);
305
305
  }
306
306
  })();
307
- await new Promise((resolve) => {
307
+ const ready = new Promise((resolve) => {
308
308
  this.pendingOpen = { resolve };
309
- void this.requestList();
310
309
  });
310
+ await this.chatClient.sendAgentMessage(new WatchThreads(), { ignoreOffline: true });
311
+ await this.requestList();
312
+ await ready;
311
313
  }
312
314
  async close() {
313
315
  this.closed = true;
@@ -316,6 +318,7 @@ export class AgentThreadStorageRepository extends ThreadStorageRepository {
316
318
  this.pendingListMessageId = undefined;
317
319
  this.pendingOpen?.resolve();
318
320
  this.pendingOpen = undefined;
321
+ await this.chatClient.sendAgentMessage(new UnwatchThreads(), { ignoreOffline: true }).catch(() => undefined);
319
322
  }
320
323
  entries() {
321
324
  return [...this.entriesByPath.values()];
@@ -355,6 +358,12 @@ export class AgentThreadStorageRepository extends ThreadStorageRepository {
355
358
  }
356
359
  handleEvent(event) {
357
360
  const message = event.message;
361
+ if (message.type === "meshagent.agent.connection.status") {
362
+ if (String(message.status ?? "").trim().toLowerCase() === "reconnected") {
363
+ void this.reconnectWatch();
364
+ }
365
+ return;
366
+ }
358
367
  if (message.type === "meshagent.agent.thread.listed") {
359
368
  this.handleThreadsListed(message);
360
369
  return;
@@ -374,6 +383,13 @@ export class AgentThreadStorageRepository extends ThreadStorageRepository {
374
383
  }
375
384
  }
376
385
  }
386
+ async reconnectWatch() {
387
+ if (this.closed) {
388
+ return;
389
+ }
390
+ await this.chatClient.sendAgentMessage(new WatchThreads(), { ignoreOffline: true });
391
+ await this.requestList();
392
+ }
377
393
  handleThreadsListed(message) {
378
394
  const nextEntries = new Map();
379
395
  for (const thread of message.threads) {
@@ -19,6 +19,8 @@ export declare const agentParticipantDisconnectType = "meshagent.agent.participa
19
19
  export declare const agentThreadDeleteType = "meshagent.agent.thread.delete";
20
20
  export declare const agentThreadRenameType = "meshagent.agent.thread.rename";
21
21
  export declare const agentThreadListType = "meshagent.agent.thread.list";
22
+ export declare const agentThreadWatchType = "meshagent.agent.thread.watch";
23
+ export declare const agentThreadUnwatchType = "meshagent.agent.thread.unwatch";
22
24
  export declare const agentThreadListedType = "meshagent.agent.thread.listed";
23
25
  export declare const agentThreadCreatedType = "meshagent.agent.thread.created";
24
26
  export declare const agentThreadUpdatedType = "meshagent.agent.thread.updated";
@@ -239,7 +241,8 @@ export declare abstract class AgentMessage {
239
241
  readonly type: string;
240
242
  readonly messageId: string;
241
243
  readonly senderName?: string;
242
- protected constructor({ type, messageId, message_id, senderName, sender_name }: JsonMap);
244
+ readonly createdAt: string;
245
+ protected constructor({ type, messageId, message_id, senderName, sender_name, createdAt, created_at }: JsonMap);
243
246
  toJson(): JsonMap;
244
247
  static fromJson(json: JsonMap): AgentMessage;
245
248
  }
@@ -272,6 +275,8 @@ export declare const ParticipantDisconnect: MessageCtor;
272
275
  export declare const DeleteThread: MessageCtor;
273
276
  export declare const RenameThread: MessageCtor;
274
277
  export declare const ListThreads: MessageCtor;
278
+ export declare const WatchThreads: MessageCtor;
279
+ export declare const UnwatchThreads: MessageCtor;
275
280
  export declare const ThreadsListed: MessageCtor;
276
281
  export declare const ThreadCreated: MessageCtor;
277
282
  export declare const ThreadUpdated: MessageCtor;