@meshagent/meshagent-agents 0.43.2 → 0.44.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.
@@ -6,15 +6,40 @@ const uuid_1 = require("uuid");
6
6
  const agent_messages_js_1 = require("./agent-messages.js");
7
7
  const change_emitter_js_1 = require("./change-emitter.js");
8
8
  class AgentMessageEvent {
9
- constructor({ message, attachment }) {
9
+ constructor({ message, createdAt, attachment }) {
10
10
  this.message = message;
11
+ this.createdAt = createdAt ?? dateOrNull(message.createdAt) ?? new Date();
11
12
  this.attachment = attachment;
12
13
  }
13
14
  get payload() {
14
- return this.message.toJson();
15
+ return { ...this.message.toJson(), created_at: this.createdAt.toISOString() };
15
16
  }
16
17
  }
17
18
  exports.AgentMessageEvent = AgentMessageEvent;
19
+ function agentRoomMessageFromEvent(event) {
20
+ const candidate = event.message;
21
+ if (candidate == null || typeof candidate !== "object" || Array.isArray(candidate)) {
22
+ return undefined;
23
+ }
24
+ const roomMessage = candidate;
25
+ if (roomMessage.type !== agent_messages_js_1.agentRoomMessageType) {
26
+ return undefined;
27
+ }
28
+ if (roomMessage.message == null || typeof roomMessage.message !== "object" || Array.isArray(roomMessage.message)) {
29
+ return undefined;
30
+ }
31
+ const rawMessage = roomMessage.message;
32
+ const payload = typeof rawMessage.type === "string" ? rawMessage : rawMessage.payload;
33
+ if (payload == null || typeof payload !== "object" || Array.isArray(payload)) {
34
+ return undefined;
35
+ }
36
+ return {
37
+ fromParticipantId: typeof roomMessage.fromParticipantId === "string" ? roomMessage.fromParticipantId : undefined,
38
+ message: payload,
39
+ local: roomMessage.local === true,
40
+ attachment: roomMessage.attachment instanceof Uint8Array ? roomMessage.attachment : undefined,
41
+ };
42
+ }
18
43
  class PendingAgentInput {
19
44
  constructor({ messageId, messageType, threadPath, payload, createdAt, awaitingAcceptance = false, awaitingApplication = false, awaitingOnline = false, }) {
20
45
  this.messageId = messageId;
@@ -76,28 +101,52 @@ class ChatThreadStartResult {
76
101
  exports.ChatThreadStartResult = ChatThreadStartResult;
77
102
  class EventQueue {
78
103
  constructor() {
79
- this.items = [];
80
- this.waiters = [];
104
+ this.subscribers = new Set();
81
105
  }
82
106
  add(item) {
83
- const waiter = this.waiters.shift();
84
- if (waiter != null) {
85
- waiter({ value: item, done: false });
86
- return;
107
+ for (const subscriber of this.subscribers) {
108
+ if (subscriber.closed) {
109
+ continue;
110
+ }
111
+ const waiter = subscriber.waiters.shift();
112
+ if (waiter != null) {
113
+ waiter({ value: item, done: false });
114
+ }
115
+ else {
116
+ subscriber.items.push(item);
117
+ }
87
118
  }
88
- this.items.push(item);
89
119
  }
90
120
  get stream() {
91
121
  return {
92
- [Symbol.asyncIterator]: () => ({
93
- next: async () => {
94
- const item = this.items.shift();
95
- if (item !== undefined) {
96
- return { value: item, done: false };
97
- }
98
- return new Promise((resolve) => this.waiters.push(resolve));
99
- },
100
- }),
122
+ [Symbol.asyncIterator]: () => {
123
+ const subscriber = {
124
+ items: [],
125
+ waiters: [],
126
+ closed: false,
127
+ };
128
+ this.subscribers.add(subscriber);
129
+ return {
130
+ next: async () => {
131
+ if (subscriber.closed) {
132
+ return { done: true, value: undefined };
133
+ }
134
+ const item = subscriber.items.shift();
135
+ if (item !== undefined) {
136
+ return { value: item, done: false };
137
+ }
138
+ return new Promise((resolve) => subscriber.waiters.push(resolve));
139
+ },
140
+ return: async () => {
141
+ subscriber.closed = true;
142
+ this.subscribers.delete(subscriber);
143
+ for (const waiter of subscriber.waiters.splice(0)) {
144
+ waiter({ done: true, value: undefined });
145
+ }
146
+ return { done: true, value: undefined };
147
+ },
148
+ };
149
+ },
101
150
  };
102
151
  }
103
152
  }
@@ -106,6 +155,7 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
106
155
  super(...arguments);
107
156
  this.sessionsByPath = new Map();
108
157
  this.pendingStartRequests = new Map();
158
+ this.pendingSessionEventsByPath = new Map();
109
159
  this.eventQueue = new EventQueue();
110
160
  }
111
161
  get events() {
@@ -128,7 +178,7 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
128
178
  cleanParticipantName(value) {
129
179
  return nonEmptyTrimmed(value);
130
180
  }
131
- openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false } = {}) {
181
+ openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false, drainPendingEvents = true, } = {}) {
132
182
  const normalized = threadPath.trim();
133
183
  if (normalized === "") {
134
184
  throw new TypeError("thread path cannot be empty");
@@ -136,23 +186,32 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
136
186
  const existing = this.sessionsByPath.get(normalized);
137
187
  if (existing != null) {
138
188
  if (!existing.isOpen || reloadIfOpen) {
139
- void existing.open({ load, sinceTurn });
189
+ this.openSession(existing, { load, sinceTurn });
140
190
  }
141
191
  return existing;
142
192
  }
143
193
  const created = new ChatThreadSession({ client: this, threadPath: normalized });
144
194
  this.sessionsByPath.set(normalized, created);
145
- void created.open({ load, sinceTurn });
195
+ if (drainPendingEvents) {
196
+ this.drainPendingSessionEvents(normalized, created);
197
+ }
198
+ this.openSession(created, { load, sinceTurn });
146
199
  this.notifyListeners();
147
200
  return created;
148
201
  }
149
- async startThread({ messageId, message, attachments, name, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, omitContent = false, }) {
202
+ openSession(session, options) {
203
+ void session.open(options).catch(() => {
204
+ this.notifyListeners();
205
+ });
206
+ }
207
+ async startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, toolChoice, omitContent = false, }) {
150
208
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? (0, uuid_1.v4)() : messageId.trim();
151
209
  const resolvedSenderName = this.cleanParticipantName(senderName) ?? this.localParticipantName();
152
210
  const payload = new agent_messages_js_1.StartThread({
153
211
  messageId: resolvedMessageId,
154
212
  content: omitContent ? undefined : (0, agent_messages_js_1.agentInputContent)({ text: message, attachments }),
155
213
  name: nonEmptyTrimmed(name),
214
+ backend: nonEmptyTrimmed(backend),
156
215
  provider: nonEmptyTrimmed(provider),
157
216
  model: nonEmptyTrimmed(model),
158
217
  voice: nonEmptyTrimmed(voice),
@@ -160,6 +219,7 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
160
219
  realtimeProtocol: nonEmptyTrimmed(realtimeProtocol),
161
220
  senderName: resolvedSenderName,
162
221
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
222
+ toolChoice: toolChoice?.toJson(),
163
223
  });
164
224
  const completer = new meshagent_1.Completer();
165
225
  this.pendingStartRequests.set(resolvedMessageId, completer);
@@ -172,10 +232,14 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
172
232
  if (threadPath === "") {
173
233
  throw new Error("Agent did not return a thread_id for the new thread.");
174
234
  }
175
- const session = this.openThread(threadPath, { load: false });
235
+ const session = this.openThread(threadPath, { load: false, drainPendingEvents: false });
176
236
  if (!omitContent) {
177
- session.addAgentMessage(new AgentMessageEvent({ message: payload }));
237
+ session.addAgentMessage(new AgentMessageEvent({
238
+ message: this.resolvedStartThreadMessage({ payload, threadPath }),
239
+ createdAt: dateOrNull(payload.createdAt) ?? undefined,
240
+ }));
178
241
  }
242
+ this.drainPendingSessionEvents(threadPath, session);
179
243
  return new ChatThreadStartResult({
180
244
  session,
181
245
  threadPath,
@@ -188,16 +252,43 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
188
252
  this.pendingStartRequests.delete(resolvedMessageId);
189
253
  }
190
254
  }
191
- handleAgentMessage(message, { attachment } = {}) {
255
+ resolvedStartThreadMessage({ payload, threadPath, }) {
256
+ return new agent_messages_js_1.TurnStart({
257
+ threadId: threadPath,
258
+ messageId: payload.messageId,
259
+ senderName: payload.senderName,
260
+ content: payload.content,
261
+ backend: payload.backend,
262
+ provider: payload.provider,
263
+ model: payload.model,
264
+ voice: payload.voice,
265
+ outputModalities: payload.outputModalities,
266
+ instructions: payload.instructions,
267
+ mcp: payload.mcp,
268
+ clientToolkits: payload.clientToolkits,
269
+ toolkits: payload.toolkits,
270
+ });
271
+ }
272
+ drainPendingSessionEvents(threadPath, session) {
273
+ const pendingEvents = this.pendingSessionEventsByPath.get(threadPath);
274
+ if (pendingEvents == null) {
275
+ return;
276
+ }
277
+ this.pendingSessionEventsByPath.delete(threadPath);
278
+ for (const event of pendingEvents) {
279
+ session.addAgentMessage(event);
280
+ }
281
+ }
282
+ handleAgentMessage(message, { createdAt, attachment } = {}) {
192
283
  if (message.type === agent_messages_js_1.agentConnectionStatusType) {
193
284
  this._connectionStatus = message;
194
285
  for (const session of this.sessionsByPath.values()) {
195
286
  if (session.isOpen) {
196
- session.addAgentMessage(new AgentMessageEvent({ message, attachment }));
287
+ session.addAgentMessage(new AgentMessageEvent({ message, createdAt, attachment }));
197
288
  }
198
289
  }
199
290
  }
200
- this.eventQueue.add(new AgentMessageEvent({ message, attachment }));
291
+ this.eventQueue.add(new AgentMessageEvent({ message, createdAt, attachment }));
201
292
  const sourceMessageId = sourceMessageIdFor(message);
202
293
  if ((message.type === agent_messages_js_1.agentThreadStartedType || message.type === agent_messages_js_1.agentThreadStartRejectedType) &&
203
294
  sourceMessageId != null &&
@@ -212,7 +303,16 @@ class BaseChatClient extends change_emitter_js_1.ChangeEmitter {
212
303
  this.notifyListeners();
213
304
  return;
214
305
  }
215
- this.sessionsByPath.get(threadPath)?.addAgentMessage(new AgentMessageEvent({ message, attachment }));
306
+ const event = new AgentMessageEvent({ message, createdAt, attachment });
307
+ const session = this.sessionsByPath.get(threadPath);
308
+ if (session != null) {
309
+ session.addAgentMessage(event);
310
+ }
311
+ else {
312
+ const pending = this.pendingSessionEventsByPath.get(threadPath) ?? [];
313
+ pending.push(event);
314
+ this.pendingSessionEventsByPath.set(threadPath, pending);
315
+ }
216
316
  this.notifyListeners();
217
317
  }
218
318
  emitConnectionStatus({ status, message, reason, retryDelay, }) {
@@ -246,7 +346,6 @@ class MessagingChatClient extends BaseChatClient {
246
346
  constructor({ room, agentName }) {
247
347
  super();
248
348
  this.pendingParticipantWaits = new Map();
249
- this.roomAbortController = new AbortController();
250
349
  this.started = false;
251
350
  this.onMessagingChanged = () => {
252
351
  const participant = this.agentParticipant();
@@ -260,10 +359,26 @@ class MessagingChatClient extends BaseChatClient {
260
359
  }
261
360
  this.notifyListeners();
262
361
  };
362
+ this.onMessagingMessage = (event) => {
363
+ this.onMessagingChanged();
364
+ const roomMessage = agentRoomMessageFromEvent(event);
365
+ if (roomMessage == null || roomMessage.local) {
366
+ return;
367
+ }
368
+ const participant = this.agentParticipant();
369
+ const fromSelectedAgent = participant != null && roomMessage.fromParticipantId === participant.id;
370
+ const fromParticipantlessThreadList = roomMessage.fromParticipantId == null && isThreadListMessage(roomMessage.message);
371
+ if (!fromSelectedAgent && !fromParticipantlessThreadList) {
372
+ return;
373
+ }
374
+ this.handleAgentMessage(agent_messages_js_1.AgentMessage.fromJson(roomMessage.message), {
375
+ createdAt: dateOrNull(roomMessage.message.created_at),
376
+ attachment: roomMessage.attachment,
377
+ });
378
+ };
263
379
  this.room = room;
264
380
  this.agentName = agentName;
265
- this.room.messaging.addListener("message", this.onMessagingChanged);
266
- this.roomPump = this.consumeRoomEvents();
381
+ this.room.messaging.addListener("message", this.onMessagingMessage);
267
382
  }
268
383
  localParticipantName() {
269
384
  const name = this.room.localParticipant?.getAttribute("name");
@@ -279,15 +394,13 @@ class MessagingChatClient extends BaseChatClient {
279
394
  }
280
395
  async stop() {
281
396
  this.started = false;
282
- this.roomAbortController.abort();
283
- this.room.messaging.removeListener("message", this.onMessagingChanged);
397
+ this.room.messaging.removeListener("message", this.onMessagingMessage);
284
398
  for (const wait of this.pendingParticipantWaits.values()) {
285
399
  if (!wait.completed) {
286
400
  wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
287
401
  }
288
402
  }
289
403
  this.pendingParticipantWaits.clear();
290
- await this.roomPump?.catch(() => undefined);
291
404
  }
292
405
  agentParticipant() {
293
406
  const normalizedAgentName = this.agentName?.trim();
@@ -328,38 +441,22 @@ class MessagingChatClient extends BaseChatClient {
328
441
  ignoreOffline,
329
442
  });
330
443
  }
331
- async consumeRoomEvents() {
332
- try {
333
- for await (const event of this.room.listen({ abortSignal: this.roomAbortController.signal })) {
334
- this.onRoomEvent(event);
335
- }
336
- }
337
- catch {
338
- if (!this.roomAbortController.signal.aborted) {
339
- throw new Error("chat client room event listener stopped unexpectedly");
340
- }
341
- }
342
- }
343
- onRoomEvent(event) {
344
- if (!(event instanceof meshagent_1.RoomMessageEvent) || event.message.type !== agent_messages_js_1.agentRoomMessageType) {
345
- return;
346
- }
347
- const participant = this.agentParticipant();
348
- if (participant == null || event.message.fromParticipantId !== participant.id) {
349
- return;
350
- }
351
- const message = event.message.message;
352
- const rawPayload = typeof message.type === "string" ? message : message.payload;
353
- if (rawPayload != null && typeof rawPayload === "object" && !Array.isArray(rawPayload)) {
354
- this.handleAgentMessage(agent_messages_js_1.AgentMessage.fromJson(rawPayload), { attachment: event.message.attachment });
355
- }
356
- }
357
444
  }
358
445
  exports.MessagingChatClient = MessagingChatClient;
446
+ function isThreadListMessage(message) {
447
+ return message.type === agent_messages_js_1.agentThreadListedType
448
+ || message.type === agent_messages_js_1.agentThreadCreatedType
449
+ || message.type === agent_messages_js_1.agentThreadUpdatedType
450
+ || message.type === agent_messages_js_1.agentThreadDeletedType;
451
+ }
359
452
  class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
360
453
  constructor({ client, threadPath }) {
361
454
  super();
362
455
  this._messages = [];
456
+ this.messageIndexes = new Map();
457
+ this.localAgentMessageIds = new Set();
458
+ this.pendingLocalInputMessageIds = new Set();
459
+ this.mergedDeltaMessageIds = new Set();
363
460
  this.pendingInputsById = new Map();
364
461
  this.openState = false;
365
462
  this.loadStateValue = ChatThreadSessionLoadState.idle();
@@ -384,9 +481,24 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
384
481
  get lastCompletedTurnId() {
385
482
  return this.lastCompletedTurnIdValue;
386
483
  }
484
+ get activeTurnId() {
485
+ return this.activeTurnIdValue;
486
+ }
487
+ get threadStatus() {
488
+ return this.threadStatusValue;
489
+ }
490
+ get currentModel() {
491
+ return this.currentModelValue;
492
+ }
493
+ get modelsResponse() {
494
+ return this.modelsResponseValue;
495
+ }
387
496
  async open({ load = true, sinceTurn } = {}) {
388
497
  if (this.openState) {
389
498
  if (load) {
499
+ if (sinceTurn == null) {
500
+ this.resetReplayState();
501
+ }
390
502
  const openThread = new agent_messages_js_1.OpenThread({ threadId: this.threadPath, load: true, sinceTurn });
391
503
  this.setLoadState(this.loadingStateForOpen(openThread, sinceTurn));
392
504
  try {
@@ -417,6 +529,17 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
417
529
  throw error;
418
530
  }
419
531
  }
532
+ resetReplayState() {
533
+ this._messages.length = 0;
534
+ this.messageIndexes.clear();
535
+ this.localAgentMessageIds.clear();
536
+ this.pendingLocalInputMessageIds.clear();
537
+ this.mergedDeltaMessageIds.clear();
538
+ this.pendingInputsById.clear();
539
+ this.lastCompletedTurnIdValue = undefined;
540
+ this.lastAgentMessageType = undefined;
541
+ this.lastAgentMessageAt = undefined;
542
+ }
420
543
  async close() {
421
544
  if (!this.openState) {
422
545
  return;
@@ -427,10 +550,11 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
427
550
  requestModels({ ignoreOffline = false } = {}) {
428
551
  return this.client.sendAgentMessage(new agent_messages_js_1.ModelsRequest({ messageId: (0, uuid_1.v4)() }), { ignoreOffline });
429
552
  }
430
- changeModel({ provider, model, voice }) {
553
+ changeModel({ backend, provider, model, voice }) {
431
554
  return this.client.sendAgentMessage(new agent_messages_js_1.ChangeModel({
432
555
  threadId: this.threadPath,
433
556
  messageId: (0, uuid_1.v4)(),
557
+ backend: nonEmptyTrimmed(backend),
434
558
  provider,
435
559
  model,
436
560
  voice: nonEmptyTrimmed(voice),
@@ -447,8 +571,16 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
447
571
  response,
448
572
  }));
449
573
  }
450
- async sendText({ messageId, text, attachments, steer = false, turnId, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
574
+ async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, toolChoice, }) {
451
575
  const resolvedMessageId = messageId == null || messageId.trim() === "" ? (0, uuid_1.v4)() : messageId.trim();
576
+ const currentModel = this.currentModelValue;
577
+ const resolvedBackend = nonEmptyTrimmed(backend) ?? currentModel?.backend;
578
+ const resolvedProvider = nonEmptyTrimmed(provider) ?? currentModel?.provider;
579
+ const resolvedModel = nonEmptyTrimmed(model) ?? currentModel?.model;
580
+ const resolvedVoice = nonEmptyTrimmed(voice) ?? currentModel?.voice;
581
+ const resolvedOutputModalities = outputModalities != null && outputModalities.length > 0
582
+ ? outputModalities
583
+ : currentModel?.outputModalities;
452
584
  const payload = steer
453
585
  ? new agent_messages_js_1.TurnSteer({
454
586
  threadId: this.threadPath,
@@ -462,21 +594,16 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
462
594
  messageId: resolvedMessageId,
463
595
  senderName: nonEmptyTrimmed(senderName),
464
596
  content: (0, agent_messages_js_1.agentInputContent)({ text, attachments }),
465
- provider: nonEmptyTrimmed(provider),
466
- model: nonEmptyTrimmed(model),
467
- voice: nonEmptyTrimmed(voice),
468
- outputModalities: outputModalities != null && outputModalities.length > 0 ? outputModalities : undefined,
597
+ backend: resolvedBackend,
598
+ provider: resolvedProvider,
599
+ model: resolvedModel,
600
+ voice: resolvedVoice,
601
+ outputModalities: resolvedOutputModalities,
469
602
  clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
603
+ toolChoice: toolChoice?.toJson(),
470
604
  });
471
- this.markPending(new PendingAgentInput({
472
- messageId: resolvedMessageId,
473
- messageType: payload.type,
474
- threadPath: this.threadPath,
475
- payload,
476
- createdAt: new Date(),
477
- awaitingAcceptance: true,
478
- awaitingApplication: true,
479
- }));
605
+ this.localAgentMessageIds.add(resolvedMessageId);
606
+ this.pendingLocalInputMessageIds.add(resolvedMessageId);
480
607
  this.addAgentMessage(new AgentMessageEvent({ message: payload }));
481
608
  try {
482
609
  await this.client.sendAgentMessage(payload);
@@ -520,58 +647,284 @@ class ChatThreadSession extends change_emitter_js_1.ChangeEmitter {
520
647
  return this.client.sendAgentMessage(new agent_messages_js_1.RenameThread({ threadId: threadPath, name }));
521
648
  }
522
649
  addAgentMessage(event) {
523
- this._messages.push(event);
524
650
  const message = event.message;
525
651
  const type = message.type;
526
652
  this.lastAgentMessageType = type;
527
- this.lastAgentMessageAt = new Date();
653
+ this.lastAgentMessageAt = event.createdAt;
654
+ this.updateSessionState(message);
655
+ this.updatePendingInputsForMessage(message, event.createdAt);
656
+ this.appendRenderableMessage(event);
657
+ this.notifyListeners();
658
+ }
659
+ updateSessionState(message) {
660
+ if (message.type === agent_messages_js_1.agentTurnEndedType) {
661
+ const turnId = normalizedString(threadMessageValue(message, "turnId"));
662
+ if (turnId != null) {
663
+ this.lastCompletedTurnIdValue = turnId;
664
+ if (this.activeTurnIdValue === turnId) {
665
+ this.activeTurnIdValue = undefined;
666
+ }
667
+ }
668
+ const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
669
+ if (statusTurnId == null || statusTurnId === turnId) {
670
+ this.threadStatusValue = undefined;
671
+ }
672
+ return;
673
+ }
674
+ if (message.type === agent_messages_js_1.agentTurnStartAcceptedType || message.type === agent_messages_js_1.agentTurnStartedType) {
675
+ const turnId = normalizedString(threadMessageValue(message, "turnId"));
676
+ if (turnId != null) {
677
+ this.activeTurnIdValue = turnId;
678
+ }
679
+ if (message.type === agent_messages_js_1.agentTurnStartedType) {
680
+ const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
681
+ if (statusTurnId == null || statusTurnId === turnId) {
682
+ this.threadStatusValue = undefined;
683
+ }
684
+ }
685
+ return;
686
+ }
687
+ if (message.type === agent_messages_js_1.agentThreadStatusType && message instanceof agent_messages_js_1.AgentThreadStatus) {
688
+ this.threadStatusValue = message;
689
+ const mode = normalizedString(message.mode);
690
+ const status = normalizedString(message.status);
691
+ const turnId = normalizedString(message.turnId);
692
+ if (status != null && mode === "steerable" && turnId != null) {
693
+ this.activeTurnIdValue = turnId;
694
+ }
695
+ else if (status == null && turnId != null && this.activeTurnIdValue === turnId) {
696
+ this.activeTurnIdValue = undefined;
697
+ }
698
+ return;
699
+ }
700
+ if (message.type === agent_messages_js_1.agentModelChangedType && message instanceof agent_messages_js_1.AgentModelChanged) {
701
+ this.currentModelValue = message;
702
+ return;
703
+ }
704
+ if (message.type === agent_messages_js_1.agentModelsResponseType && message instanceof agent_messages_js_1.ModelsResponse) {
705
+ this.modelsResponseValue = message;
706
+ this.applyActiveModel(message);
707
+ return;
708
+ }
709
+ if (message.type === agent_messages_js_1.agentThreadLoadedType) {
710
+ this.setLoadState(new ChatThreadSessionLoadState({
711
+ phase: ChatThreadSessionLoadPhase.Loaded,
712
+ startedAt: this.loadStateValue.startedAt,
713
+ completedAt: this.lastAgentMessageAt,
714
+ requestMessageId: this.loadStateValue.requestMessageId,
715
+ sinceTurn: this.loadStateValue.sinceTurn,
716
+ lastMessageType: message.type,
717
+ lastMessageAt: this.lastAgentMessageAt,
718
+ }), { notify: false });
719
+ }
720
+ }
721
+ updatePendingInputsForMessage(message, createdAt) {
722
+ const type = message.type;
528
723
  const messageId = message.messageId;
529
724
  const sourceMessageId = sourceMessageIdFor(message);
530
- if (type === agent_messages_js_1.agentTurnStartType || type === agent_messages_js_1.agentTurnSteerType) {
725
+ if (type === agent_messages_js_1.agentThreadStartType || type === agent_messages_js_1.agentTurnStartType || type === agent_messages_js_1.agentTurnSteerType) {
531
726
  const normalizedMessageId = messageId.trim();
532
727
  if (normalizedMessageId !== "") {
728
+ this.localAgentMessageIds.add(normalizedMessageId);
729
+ this.pendingLocalInputMessageIds.add(normalizedMessageId);
533
730
  this.markPending(new PendingAgentInput({
534
731
  messageId: normalizedMessageId,
535
732
  messageType: type,
536
733
  threadPath: this.threadPath,
537
734
  payload: message,
538
- createdAt: new Date(),
735
+ createdAt,
539
736
  awaitingAcceptance: true,
540
737
  awaitingApplication: true,
541
738
  }));
542
739
  }
543
740
  }
741
+ else if (type === agent_messages_js_1.agentThreadStartRejectedType) {
742
+ if (sourceMessageId != null) {
743
+ this.pendingInputsById.delete(sourceMessageId);
744
+ }
745
+ }
544
746
  else if (type === agent_messages_js_1.agentTurnStartAcceptedType || type === agent_messages_js_1.agentTurnSteerAcceptedType) {
545
747
  this.updatePending(sourceMessageId, { awaitingAcceptance: false, awaitingApplication: true });
546
748
  }
547
749
  else if (type === agent_messages_js_1.agentTurnStartedType || type === agent_messages_js_1.agentTurnSteeredType) {
548
750
  if (sourceMessageId != null) {
549
751
  this.pendingInputsById.delete(sourceMessageId);
752
+ this.pendingLocalInputMessageIds.delete(sourceMessageId);
550
753
  }
551
754
  }
552
755
  else if (type === agent_messages_js_1.agentTurnStartRejectedType || type === agent_messages_js_1.agentTurnSteerRejectedType) {
553
756
  if (sourceMessageId != null) {
554
757
  this.pendingInputsById.delete(sourceMessageId);
758
+ this.pendingLocalInputMessageIds.delete(sourceMessageId);
555
759
  }
556
760
  }
557
761
  else if (type === agent_messages_js_1.agentTurnEndedType || type === agent_messages_js_1.agentThreadClearedType) {
558
762
  this.pendingInputsById.clear();
559
- if (message.type === agent_messages_js_1.agentTurnEndedType && "turnId" in message && typeof message.turnId === "string" && message.turnId.trim() !== "") {
560
- this.lastCompletedTurnIdValue = message.turnId.trim();
763
+ this.pendingLocalInputMessageIds.clear();
764
+ }
765
+ }
766
+ appendRenderableMessage(event) {
767
+ const message = event.message;
768
+ if (message.type === agent_messages_js_1.agentTurnEndedType) {
769
+ if (message.error != null) {
770
+ this.appendMessage(event);
561
771
  }
772
+ return;
562
773
  }
563
- else if (type === agent_messages_js_1.agentThreadLoadedType) {
564
- this.setLoadState(new ChatThreadSessionLoadState({
565
- phase: ChatThreadSessionLoadPhase.Loaded,
566
- startedAt: this.loadStateValue.startedAt,
567
- completedAt: this.lastAgentMessageAt,
568
- requestMessageId: this.loadStateValue.requestMessageId,
569
- sinceTurn: this.loadStateValue.sinceTurn,
570
- lastMessageType: type,
571
- lastMessageAt: this.lastAgentMessageAt,
572
- }), { notify: false });
774
+ if (message.type === agent_messages_js_1.agentToolCallEndedType) {
775
+ const itemId = normalizedString(message.itemId);
776
+ if (itemId != null) {
777
+ this.upsertItemMessage(`tool_call:${itemId}`, event);
778
+ }
779
+ else if (message.error != null) {
780
+ this.appendMessage(event);
781
+ }
782
+ return;
783
+ }
784
+ if (message.type === agent_messages_js_1.agentToolCallPendingType || message.type === agent_messages_js_1.agentToolCallInProgressType || message.type === agent_messages_js_1.agentToolCallStartedType) {
785
+ const itemId = normalizedString(message.itemId);
786
+ if (itemId != null) {
787
+ this.upsertItemMessage(`tool_call:${itemId}`, event);
788
+ }
789
+ return;
790
+ }
791
+ if (message.type === agent_messages_js_1.agentSecretRequestedType) {
792
+ this.appendMessage(event);
793
+ return;
794
+ }
795
+ if (message.type === agent_messages_js_1.agentModelChangedType) {
796
+ this.appendMessage(event);
797
+ return;
798
+ }
799
+ if (message.type === agent_messages_js_1.agentThreadStartType || message.type === agent_messages_js_1.agentTurnStartType || message.type === agent_messages_js_1.agentTurnSteerType) {
800
+ if (agentInputText(message.content).trim() !== "") {
801
+ this.appendMessage(event);
802
+ }
803
+ return;
804
+ }
805
+ if (message.type === agent_messages_js_1.agentTurnStartAcceptedType || message.type === agent_messages_js_1.agentTurnSteerAcceptedType) {
806
+ const sourceMessageId = normalizedString(sourceMessageIdFor(message));
807
+ if (sourceMessageId != null && this.localAgentMessageIds.has(sourceMessageId)) {
808
+ this.pendingLocalInputMessageIds.delete(sourceMessageId);
809
+ return;
810
+ }
811
+ if (agentInputText(message.content).trim() !== "") {
812
+ this.appendMessage(event, { beforePendingLocalInputs: true });
813
+ }
814
+ else {
815
+ this.appendMessage(event);
816
+ }
817
+ return;
818
+ }
819
+ if (message.type === agent_messages_js_1.agentTurnStartedType ||
820
+ message.type === agent_messages_js_1.agentTurnSteeredType ||
821
+ message.type === agent_messages_js_1.agentTurnStartRejectedType ||
822
+ message.type === agent_messages_js_1.agentTurnSteerRejectedType ||
823
+ message.type === agent_messages_js_1.agentTurnInterruptAcceptedType ||
824
+ message.type === agent_messages_js_1.agentTurnInterruptedType) {
825
+ this.appendMessage(event);
826
+ return;
827
+ }
828
+ if (isMergeableDelta(message)) {
829
+ this.appendOrMergeDelta(event);
830
+ return;
831
+ }
832
+ if (message.type === agent_messages_js_1.agentImageGenerationPartialType || message.type === agent_messages_js_1.agentImageGenerationCompletedType) {
833
+ const itemId = normalizedString(message.itemId);
834
+ if (itemId != null) {
835
+ this.upsertItemMessage(`image_generation:${itemId}`, event);
836
+ }
837
+ return;
838
+ }
839
+ if (message.type === agent_messages_js_1.agentUsageUpdatedType) {
840
+ this.appendMessage(event);
841
+ return;
842
+ }
843
+ if (message instanceof agent_messages_js_1.AgentClientToolCallRequested) {
844
+ this.appendMessage(event);
845
+ }
846
+ }
847
+ appendMessage(event, { beforePendingLocalInputs = false } = {}) {
848
+ const normalizedMessageId = normalizedString(event.message.messageId);
849
+ if (normalizedMessageId == null || this.messageIndexes.has(normalizedMessageId)) {
850
+ return;
851
+ }
852
+ if (beforePendingLocalInputs) {
853
+ const insertIndex = this._messages.findIndex((existing) => this.pendingLocalInputMessageIds.has(existing.message.messageId));
854
+ if (insertIndex >= 0) {
855
+ this._messages.splice(insertIndex, 0, event);
856
+ this.indexMessagesFrom(insertIndex);
857
+ return;
858
+ }
859
+ }
860
+ this.messageIndexes.set(normalizedMessageId, this._messages.length);
861
+ this._messages.push(event);
862
+ }
863
+ upsertItemMessage(key, event) {
864
+ const existingIndex = this.messageIndexes.get(key);
865
+ if (existingIndex == null) {
866
+ this.messageIndexes.set(key, this._messages.length);
867
+ this._messages.push(event);
868
+ return;
869
+ }
870
+ this._messages[existingIndex] = event;
871
+ }
872
+ appendOrMergeDelta(event) {
873
+ const message = event.message;
874
+ const normalizedMessageId = normalizedString(message.messageId);
875
+ if (normalizedMessageId != null) {
876
+ if (this.mergedDeltaMessageIds.has(normalizedMessageId)) {
877
+ return;
878
+ }
879
+ this.mergedDeltaMessageIds.add(normalizedMessageId);
880
+ }
881
+ const itemId = normalizedString(message.itemId);
882
+ if (itemId == null) {
883
+ return;
884
+ }
885
+ const key = `${message.type}:${itemId}`;
886
+ const existingIndex = this.messageIndexes.get(key);
887
+ if (existingIndex == null) {
888
+ this.messageIndexes.set(key, this._messages.length);
889
+ this._messages.push(event);
890
+ return;
891
+ }
892
+ const existing = this._messages[existingIndex].message;
893
+ const merged = mergeDeltaMessage(existing, message);
894
+ if (merged != null) {
895
+ this._messages[existingIndex] = new AgentMessageEvent({ message: merged, createdAt: event.createdAt, attachment: event.attachment });
896
+ }
897
+ }
898
+ indexMessagesFrom(start) {
899
+ for (let index = Math.max(0, start); index < this._messages.length; index += 1) {
900
+ const key = messageIndexKey(this._messages[index].message);
901
+ if (key != null) {
902
+ this.messageIndexes.set(key, index);
903
+ }
904
+ }
905
+ }
906
+ applyActiveModel(response) {
907
+ for (const provider of response.providers) {
908
+ for (const model of provider.models) {
909
+ if (!model.active) {
910
+ continue;
911
+ }
912
+ this.currentModelValue = new agent_messages_js_1.AgentModelChanged({
913
+ threadId: this.threadPath,
914
+ sourceMessageId: response.sourceMessageId,
915
+ backend: provider.backend,
916
+ provider: provider.name,
917
+ model: model.name,
918
+ voice: model.defaultOutputVoice,
919
+ inputFormat: model.inputFormat,
920
+ outputFormat: model.outputFormat,
921
+ turnDetection: model.turnDetection,
922
+ realtimeProtocols: model.realtimeProtocols,
923
+ outputModalities: model.modalities.length > 0 ? [model.modalities[0]] : ["text"],
924
+ });
925
+ return;
926
+ }
573
927
  }
574
- this.notifyListeners();
575
928
  }
576
929
  markClosed({ notify = true } = {}) {
577
930
  this.openState = false;
@@ -636,6 +989,84 @@ function sourceMessageIdFor(message) {
636
989
  const value = "sourceMessageId" in message ? message.sourceMessageId : undefined;
637
990
  return typeof value === "string" ? value : undefined;
638
991
  }
992
+ function threadMessageValue(message, key) {
993
+ const value = message[key];
994
+ return typeof value === "string" ? value : undefined;
995
+ }
996
+ function agentInputText(content) {
997
+ if (!Array.isArray(content)) {
998
+ return "";
999
+ }
1000
+ return content
1001
+ .filter((entry) => (typeof entry === "object" &&
1002
+ entry !== null &&
1003
+ "type" in entry &&
1004
+ entry.type === "text" &&
1005
+ "text" in entry &&
1006
+ typeof entry.text === "string"))
1007
+ .map((entry) => entry.text)
1008
+ .join("");
1009
+ }
1010
+ function isMergeableDelta(message) {
1011
+ return message.type === agent_messages_js_1.agentAudioGenerationDeltaType ||
1012
+ message.type === agent_messages_js_1.agentAudioTranscriptionDeltaType ||
1013
+ message.type === agent_messages_js_1.agentFileContentDeltaType ||
1014
+ message.type === agent_messages_js_1.agentReasoningContentDeltaType ||
1015
+ message.type === agent_messages_js_1.agentTextContentDeltaType ||
1016
+ message.type === agent_messages_js_1.agentToolCallArgumentsDeltaType ||
1017
+ message.type === agent_messages_js_1.agentToolCallLogDeltaType;
1018
+ }
1019
+ function messageIndexKey(message) {
1020
+ if (isMergeableDelta(message)) {
1021
+ const itemId = normalizedString(message.itemId);
1022
+ return itemId == null ? undefined : `${message.type}:${itemId}`;
1023
+ }
1024
+ return normalizedString(message.messageId);
1025
+ }
1026
+ function mergeDeltaMessage(existing, incoming) {
1027
+ if (existing.type !== incoming.type) {
1028
+ return undefined;
1029
+ }
1030
+ if (existing.type === agent_messages_js_1.agentAudioGenerationDeltaType && existing instanceof agent_messages_js_1.AgentAudioGenerationDelta && incoming instanceof agent_messages_js_1.AgentAudioGenerationDelta) {
1031
+ const data = new Uint8Array(existing.data.length + incoming.data.length);
1032
+ data.set(existing.data, 0);
1033
+ data.set(incoming.data, existing.data.length);
1034
+ return new agent_messages_js_1.AgentAudioGenerationDelta({ ...existing.toJson(), data });
1035
+ }
1036
+ if (existing.type === agent_messages_js_1.agentAudioTranscriptionDeltaType && existing instanceof agent_messages_js_1.AgentAudioTranscriptionDelta && incoming instanceof agent_messages_js_1.AgentAudioTranscriptionDelta) {
1037
+ return new agent_messages_js_1.AgentAudioTranscriptionDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1038
+ }
1039
+ if (existing.type === agent_messages_js_1.agentFileContentDeltaType && incoming instanceof agent_messages_js_1.AgentFileContentDelta) {
1040
+ return incoming;
1041
+ }
1042
+ if (existing.type === agent_messages_js_1.agentReasoningContentDeltaType && existing instanceof agent_messages_js_1.AgentReasoningContentDelta && incoming instanceof agent_messages_js_1.AgentReasoningContentDelta) {
1043
+ return new agent_messages_js_1.AgentReasoningContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1044
+ }
1045
+ if (existing.type === agent_messages_js_1.agentTextContentDeltaType && existing instanceof agent_messages_js_1.AgentTextContentDelta && incoming instanceof agent_messages_js_1.AgentTextContentDelta) {
1046
+ return new agent_messages_js_1.AgentTextContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
1047
+ }
1048
+ if (existing.type === agent_messages_js_1.agentToolCallArgumentsDeltaType && existing instanceof agent_messages_js_1.AgentToolCallArgumentsDelta && incoming instanceof agent_messages_js_1.AgentToolCallArgumentsDelta) {
1049
+ return new agent_messages_js_1.AgentToolCallArgumentsDelta({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
1050
+ }
1051
+ if (existing.type === agent_messages_js_1.agentToolCallLogDeltaType && existing instanceof agent_messages_js_1.AgentToolCallLogDelta && incoming instanceof agent_messages_js_1.AgentToolCallLogDelta) {
1052
+ return new agent_messages_js_1.AgentToolCallLogDelta({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
1053
+ }
1054
+ return undefined;
1055
+ }
1056
+ function normalizedString(value) {
1057
+ const normalized = value?.trim();
1058
+ return normalized == null || normalized === "" ? undefined : normalized;
1059
+ }
1060
+ function dateOrNull(value) {
1061
+ if (value instanceof Date && !Number.isNaN(value.getTime())) {
1062
+ return value;
1063
+ }
1064
+ if (typeof value !== "string" || value.trim() === "") {
1065
+ return undefined;
1066
+ }
1067
+ const parsed = new Date(value);
1068
+ return Number.isNaN(parsed.getTime()) ? undefined : parsed;
1069
+ }
639
1070
  function nonEmptyTrimmed(value) {
640
1071
  const normalized = value?.trim();
641
1072
  return normalized == null || normalized === "" ? undefined : normalized;