@meshagent/meshagent-agents 0.43.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/LICENSE +1 -0
  3. package/README.md +206 -0
  4. package/dist/browser/agent-messages.d.ts +343 -0
  5. package/dist/browser/agent-messages.js +1015 -0
  6. package/dist/browser/change-emitter.d.ts +8 -0
  7. package/dist/browser/change-emitter.js +20 -0
  8. package/dist/browser/chat-client.d.ts +225 -0
  9. package/dist/browser/chat-client.js +642 -0
  10. package/dist/browser/images-dataset.d.ts +83 -0
  11. package/dist/browser/images-dataset.js +282 -0
  12. package/dist/browser/index.d.ts +6 -0
  13. package/dist/browser/index.js +22 -0
  14. package/dist/browser/package.json +1 -0
  15. package/dist/browser/room-shell-toolkit.d.ts +14 -0
  16. package/dist/browser/room-shell-toolkit.js +274 -0
  17. package/dist/browser/room-storage-toolkit.d.ts +11 -0
  18. package/dist/browser/room-storage-toolkit.js +513 -0
  19. package/dist/browser/thread-storage.d.ts +102 -0
  20. package/dist/browser/thread-storage.js +476 -0
  21. package/dist/esm/agent-messages.d.ts +343 -0
  22. package/dist/esm/agent-messages.js +989 -0
  23. package/dist/esm/change-emitter.d.ts +8 -0
  24. package/dist/esm/change-emitter.js +16 -0
  25. package/dist/esm/chat-client.d.ts +225 -0
  26. package/dist/esm/chat-client.js +632 -0
  27. package/dist/esm/images-dataset.d.ts +83 -0
  28. package/dist/esm/images-dataset.js +274 -0
  29. package/dist/esm/index.d.ts +6 -0
  30. package/dist/esm/index.js +6 -0
  31. package/dist/esm/package.json +1 -0
  32. package/dist/esm/room-shell-toolkit.d.ts +14 -0
  33. package/dist/esm/room-shell-toolkit.js +270 -0
  34. package/dist/esm/room-storage-toolkit.d.ts +11 -0
  35. package/dist/esm/room-storage-toolkit.js +509 -0
  36. package/dist/esm/thread-storage.d.ts +102 -0
  37. package/dist/esm/thread-storage.js +465 -0
  38. package/dist/node/agent-messages.d.ts +343 -0
  39. package/dist/node/agent-messages.js +1015 -0
  40. package/dist/node/change-emitter.d.ts +8 -0
  41. package/dist/node/change-emitter.js +20 -0
  42. package/dist/node/chat-client.d.ts +225 -0
  43. package/dist/node/chat-client.js +642 -0
  44. package/dist/node/images-dataset.d.ts +83 -0
  45. package/dist/node/images-dataset.js +282 -0
  46. package/dist/node/index.d.ts +6 -0
  47. package/dist/node/index.js +22 -0
  48. package/dist/node/package.json +1 -0
  49. package/dist/node/room-shell-toolkit.d.ts +14 -0
  50. package/dist/node/room-shell-toolkit.js +274 -0
  51. package/dist/node/room-storage-toolkit.d.ts +11 -0
  52. package/dist/node/room-storage-toolkit.js +513 -0
  53. package/dist/node/thread-storage.d.ts +102 -0
  54. package/dist/node/thread-storage.js +476 -0
  55. package/package.json +57 -0
@@ -0,0 +1,632 @@
1
+ import { Completer, RoomMessageEvent, } from "@meshagent/meshagent";
2
+ import { v4 as uuidv4 } from "uuid";
3
+ import { AgentMessage as AgentMessageFactory, AgentRealtimeAudioChunk, AgentRealtimeAudioCommit, ChangeModel, CloseThread, DeleteThread, ModelsRequest, OpenThread, RenameThread, StartThread, AgentConnectionStatus, TurnInterrupt, TurnStart, TurnSteer, AgentClientToolCallResponse, agentInputContent, agentRoomMessageType, agentThreadClearedType, agentThreadLoadedType, agentConnectionStatusType, agentThreadStartRejectedType, agentThreadStartedType, agentTurnEndedType, agentTurnStartAcceptedType, agentTurnStartRejectedType, agentTurnStartType, agentTurnStartedType, agentTurnSteerAcceptedType, agentTurnSteerRejectedType, agentTurnSteerType, agentTurnSteeredType, } from "./agent-messages.js";
4
+ import { ChangeEmitter } from "./change-emitter.js";
5
+ export class AgentMessageEvent {
6
+ constructor({ message, attachment }) {
7
+ this.message = message;
8
+ this.attachment = attachment;
9
+ }
10
+ get payload() {
11
+ return this.message.toJson();
12
+ }
13
+ }
14
+ export class PendingAgentInput {
15
+ constructor({ messageId, messageType, threadPath, payload, createdAt, awaitingAcceptance = false, awaitingApplication = false, awaitingOnline = false, }) {
16
+ this.messageId = messageId;
17
+ this.messageType = messageType;
18
+ this.threadPath = threadPath;
19
+ this.payload = payload;
20
+ this.createdAt = createdAt;
21
+ this.awaitingAcceptance = awaitingAcceptance;
22
+ this.awaitingApplication = awaitingApplication;
23
+ this.awaitingOnline = awaitingOnline;
24
+ }
25
+ copyWith({ awaitingAcceptance, awaitingApplication, awaitingOnline, }) {
26
+ return new PendingAgentInput({
27
+ messageId: this.messageId,
28
+ messageType: this.messageType,
29
+ threadPath: this.threadPath,
30
+ payload: this.payload,
31
+ createdAt: this.createdAt,
32
+ awaitingAcceptance: awaitingAcceptance ?? this.awaitingAcceptance,
33
+ awaitingApplication: awaitingApplication ?? this.awaitingApplication,
34
+ awaitingOnline: awaitingOnline ?? this.awaitingOnline,
35
+ });
36
+ }
37
+ }
38
+ export var ChatThreadSessionLoadPhase;
39
+ (function (ChatThreadSessionLoadPhase) {
40
+ ChatThreadSessionLoadPhase["Idle"] = "idle";
41
+ ChatThreadSessionLoadPhase["Loading"] = "loading";
42
+ ChatThreadSessionLoadPhase["Loaded"] = "loaded";
43
+ ChatThreadSessionLoadPhase["Failed"] = "failed";
44
+ })(ChatThreadSessionLoadPhase || (ChatThreadSessionLoadPhase = {}));
45
+ export class ChatThreadSessionLoadState {
46
+ constructor({ phase, startedAt, completedAt, requestMessageId, sinceTurn, error, lastMessageType, lastMessageAt, }) {
47
+ this.phase = phase;
48
+ this.startedAt = startedAt;
49
+ this.completedAt = completedAt;
50
+ this.requestMessageId = requestMessageId;
51
+ this.sinceTurn = sinceTurn;
52
+ this.error = error;
53
+ this.lastMessageType = lastMessageType;
54
+ this.lastMessageAt = lastMessageAt;
55
+ }
56
+ static idle() {
57
+ return new ChatThreadSessionLoadState({ phase: ChatThreadSessionLoadPhase.Idle });
58
+ }
59
+ get isLoading() {
60
+ return this.phase === ChatThreadSessionLoadPhase.Loading;
61
+ }
62
+ }
63
+ export class ChatThreadStartResult {
64
+ constructor({ session, threadPath, realtimeConnection }) {
65
+ this.session = session;
66
+ this.threadPath = threadPath;
67
+ this.realtimeConnection = realtimeConnection;
68
+ }
69
+ }
70
+ class EventQueue {
71
+ constructor() {
72
+ this.items = [];
73
+ this.waiters = [];
74
+ }
75
+ add(item) {
76
+ const waiter = this.waiters.shift();
77
+ if (waiter != null) {
78
+ waiter({ value: item, done: false });
79
+ return;
80
+ }
81
+ this.items.push(item);
82
+ }
83
+ get stream() {
84
+ return {
85
+ [Symbol.asyncIterator]: () => ({
86
+ next: async () => {
87
+ const item = this.items.shift();
88
+ if (item !== undefined) {
89
+ return { value: item, done: false };
90
+ }
91
+ return new Promise((resolve) => this.waiters.push(resolve));
92
+ },
93
+ }),
94
+ };
95
+ }
96
+ }
97
+ export class BaseChatClient extends ChangeEmitter {
98
+ constructor() {
99
+ super(...arguments);
100
+ this.sessionsByPath = new Map();
101
+ this.pendingStartRequests = new Map();
102
+ this.eventQueue = new EventQueue();
103
+ }
104
+ get events() {
105
+ return this.eventQueue.stream;
106
+ }
107
+ get connectionStatus() {
108
+ return this._connectionStatus;
109
+ }
110
+ get sessions() {
111
+ return [...this.sessionsByPath.values()];
112
+ }
113
+ async start() { }
114
+ async stop() { }
115
+ agentParticipant() {
116
+ return null;
117
+ }
118
+ localParticipantName() {
119
+ return undefined;
120
+ }
121
+ cleanParticipantName(value) {
122
+ return nonEmptyTrimmed(value);
123
+ }
124
+ openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false } = {}) {
125
+ const normalized = threadPath.trim();
126
+ if (normalized === "") {
127
+ throw new TypeError("thread path cannot be empty");
128
+ }
129
+ const existing = this.sessionsByPath.get(normalized);
130
+ if (existing != null) {
131
+ if (!existing.isOpen || reloadIfOpen) {
132
+ void existing.open({ load, sinceTurn });
133
+ }
134
+ return existing;
135
+ }
136
+ const created = new ChatThreadSession({ client: this, threadPath: normalized });
137
+ this.sessionsByPath.set(normalized, created);
138
+ void created.open({ load, sinceTurn });
139
+ this.notifyListeners();
140
+ return created;
141
+ }
142
+ async startThread({ messageId, message, attachments, name, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, omitContent = false, }) {
143
+ const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
144
+ const resolvedSenderName = this.cleanParticipantName(senderName) ?? this.localParticipantName();
145
+ const payload = new StartThread({
146
+ messageId: resolvedMessageId,
147
+ content: omitContent ? undefined : agentInputContent({ text: message, attachments }),
148
+ name: nonEmptyTrimmed(name),
149
+ provider: nonEmptyTrimmed(provider),
150
+ model: nonEmptyTrimmed(model),
151
+ voice: nonEmptyTrimmed(voice),
152
+ outputModalities: outputModalities != null && outputModalities.length > 0 ? outputModalities : undefined,
153
+ realtimeProtocol: nonEmptyTrimmed(realtimeProtocol),
154
+ senderName: resolvedSenderName,
155
+ clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
156
+ });
157
+ const completer = new Completer();
158
+ this.pendingStartRequests.set(resolvedMessageId, completer);
159
+ try {
160
+ await this.sendAgentMessage(payload);
161
+ const response = await completer.fut;
162
+ const threadPath = "threadId" in response
163
+ ? String(response.threadId ?? "").trim()
164
+ : "";
165
+ if (threadPath === "") {
166
+ throw new Error("Agent did not return a thread_id for the new thread.");
167
+ }
168
+ const session = this.openThread(threadPath, { load: false });
169
+ if (!omitContent) {
170
+ session.addAgentMessage(new AgentMessageEvent({ message: payload }));
171
+ }
172
+ return new ChatThreadStartResult({
173
+ session,
174
+ threadPath,
175
+ realtimeConnection: response.type === agentThreadStartedType && response.realtimeConnection != null
176
+ ? response.realtimeConnection.toJson()
177
+ : undefined,
178
+ });
179
+ }
180
+ finally {
181
+ this.pendingStartRequests.delete(resolvedMessageId);
182
+ }
183
+ }
184
+ handleAgentMessage(message, { attachment } = {}) {
185
+ if (message.type === agentConnectionStatusType) {
186
+ this._connectionStatus = message;
187
+ for (const session of this.sessionsByPath.values()) {
188
+ if (session.isOpen) {
189
+ session.addAgentMessage(new AgentMessageEvent({ message, attachment }));
190
+ }
191
+ }
192
+ }
193
+ this.eventQueue.add(new AgentMessageEvent({ message, attachment }));
194
+ const sourceMessageId = sourceMessageIdFor(message);
195
+ if ((message.type === agentThreadStartedType || message.type === agentThreadStartRejectedType) &&
196
+ sourceMessageId != null &&
197
+ sourceMessageId.trim() !== "") {
198
+ const pending = this.pendingStartRequests.get(sourceMessageId.trim());
199
+ if (pending != null && !pending.completed) {
200
+ pending.complete(message);
201
+ }
202
+ }
203
+ const threadPath = "threadId" in message ? String(message.threadId).trim() : "";
204
+ if (threadPath === "") {
205
+ this.notifyListeners();
206
+ return;
207
+ }
208
+ this.sessionsByPath.get(threadPath)?.addAgentMessage(new AgentMessageEvent({ message, attachment }));
209
+ this.notifyListeners();
210
+ }
211
+ emitConnectionStatus({ status, message, reason, retryDelay, }) {
212
+ const event = new AgentConnectionStatus({
213
+ status,
214
+ message,
215
+ reason,
216
+ retryInSeconds: retryDelay == null ? undefined : retryDelay / 1000,
217
+ });
218
+ this._connectionStatus = event;
219
+ this.eventQueue.add(new AgentMessageEvent({ message: event }));
220
+ for (const session of this.sessionsByPath.values()) {
221
+ if (session.isOpen) {
222
+ session.addAgentMessage(new AgentMessageEvent({ message: event }));
223
+ }
224
+ }
225
+ this.notifyListeners();
226
+ }
227
+ removeThreadSession(threadPath) {
228
+ const normalized = threadPath.trim();
229
+ const removed = this.sessionsByPath.get(normalized);
230
+ this.sessionsByPath.delete(normalized);
231
+ removed?.markClosed({ notify: false });
232
+ if (removed != null) {
233
+ this.notifyListeners();
234
+ }
235
+ }
236
+ }
237
+ export class MessagingChatClient extends BaseChatClient {
238
+ constructor({ room, agentName }) {
239
+ super();
240
+ this.pendingParticipantWaits = new Map();
241
+ this.roomAbortController = new AbortController();
242
+ this.started = false;
243
+ this.onMessagingChanged = () => {
244
+ const participant = this.agentParticipant();
245
+ if (participant != null) {
246
+ this.emitConnectionStatus({ status: "connected", message: "Agent messaging connected" });
247
+ for (const wait of this.pendingParticipantWaits.values()) {
248
+ if (!wait.completed) {
249
+ wait.complete(participant);
250
+ }
251
+ }
252
+ }
253
+ this.notifyListeners();
254
+ };
255
+ this.room = room;
256
+ this.agentName = agentName;
257
+ this.room.messaging.addListener("message", this.onMessagingChanged);
258
+ this.roomPump = this.consumeRoomEvents();
259
+ }
260
+ localParticipantName() {
261
+ const name = this.room.localParticipant?.getAttribute("name");
262
+ return typeof name === "string" ? this.cleanParticipantName(name) : undefined;
263
+ }
264
+ async start() {
265
+ if (this.started) {
266
+ return;
267
+ }
268
+ this.started = true;
269
+ this.room.messaging.start();
270
+ this.room.messaging.enable();
271
+ }
272
+ async stop() {
273
+ this.started = false;
274
+ this.roomAbortController.abort();
275
+ this.room.messaging.removeListener("message", this.onMessagingChanged);
276
+ for (const wait of this.pendingParticipantWaits.values()) {
277
+ if (!wait.completed) {
278
+ wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
279
+ }
280
+ }
281
+ this.pendingParticipantWaits.clear();
282
+ await this.roomPump?.catch(() => undefined);
283
+ }
284
+ agentParticipant() {
285
+ const normalizedAgentName = this.agentName?.trim();
286
+ for (const participant of this.room.messaging.remoteParticipants) {
287
+ if (normalizedAgentName != null && normalizedAgentName !== "" && participant.getAttribute("name") !== normalizedAgentName) {
288
+ continue;
289
+ }
290
+ if (participant.getAttribute("supports_agent_messages") === true) {
291
+ return participant;
292
+ }
293
+ }
294
+ return null;
295
+ }
296
+ async waitForAgentParticipant({ waitKey } = {}) {
297
+ const existing = this.agentParticipant();
298
+ if (existing != null) {
299
+ return existing;
300
+ }
301
+ const key = waitKey == null || waitKey.trim() === "" ? uuidv4() : waitKey.trim();
302
+ const completer = new Completer();
303
+ this.pendingParticipantWaits.set(key, completer);
304
+ this.onMessagingChanged();
305
+ try {
306
+ return await completer.fut;
307
+ }
308
+ finally {
309
+ this.pendingParticipantWaits.delete(key);
310
+ }
311
+ }
312
+ async sendAgentMessage(message, { attachment, ignoreOffline = false } = {}) {
313
+ await this.start();
314
+ const participant = this.agentParticipant() ?? await this.waitForAgentParticipant({ waitKey: message.messageId });
315
+ await this.room.messaging.sendMessage({
316
+ to: participant,
317
+ type: agentRoomMessageType,
318
+ message: message.toJson(),
319
+ attachment,
320
+ ignoreOffline,
321
+ });
322
+ }
323
+ async consumeRoomEvents() {
324
+ try {
325
+ for await (const event of this.room.listen({ abortSignal: this.roomAbortController.signal })) {
326
+ this.onRoomEvent(event);
327
+ }
328
+ }
329
+ catch {
330
+ if (!this.roomAbortController.signal.aborted) {
331
+ throw new Error("chat client room event listener stopped unexpectedly");
332
+ }
333
+ }
334
+ }
335
+ onRoomEvent(event) {
336
+ if (!(event instanceof RoomMessageEvent) || event.message.type !== agentRoomMessageType) {
337
+ return;
338
+ }
339
+ const participant = this.agentParticipant();
340
+ if (participant == null || event.message.fromParticipantId !== participant.id) {
341
+ return;
342
+ }
343
+ const message = event.message.message;
344
+ const rawPayload = typeof message.type === "string" ? message : message.payload;
345
+ if (rawPayload != null && typeof rawPayload === "object" && !Array.isArray(rawPayload)) {
346
+ this.handleAgentMessage(AgentMessageFactory.fromJson(rawPayload), { attachment: event.message.attachment });
347
+ }
348
+ }
349
+ }
350
+ export class ChatThreadSession extends ChangeEmitter {
351
+ constructor({ client, threadPath }) {
352
+ super();
353
+ this._messages = [];
354
+ this.pendingInputsById = new Map();
355
+ this.openState = false;
356
+ this.loadStateValue = ChatThreadSessionLoadState.idle();
357
+ this.client = client;
358
+ this.threadPath = threadPath;
359
+ }
360
+ get messages() {
361
+ return [...this._messages];
362
+ }
363
+ get pendingInputs() {
364
+ return [...this.pendingInputsById.values()];
365
+ }
366
+ get isOpen() {
367
+ return this.openState;
368
+ }
369
+ get loadState() {
370
+ return this.loadStateValue;
371
+ }
372
+ get isLoading() {
373
+ return this.loadStateValue.isLoading;
374
+ }
375
+ get lastCompletedTurnId() {
376
+ return this.lastCompletedTurnIdValue;
377
+ }
378
+ async open({ load = true, sinceTurn } = {}) {
379
+ if (this.openState) {
380
+ if (load) {
381
+ const openThread = new OpenThread({ threadId: this.threadPath, load: true, sinceTurn });
382
+ this.setLoadState(this.loadingStateForOpen(openThread, sinceTurn));
383
+ try {
384
+ await this.client.sendAgentMessage(openThread, { ignoreOffline: true });
385
+ await this.requestModels({ ignoreOffline: true });
386
+ }
387
+ catch (error) {
388
+ this.setLoadState(this.failedLoadState(error));
389
+ throw error;
390
+ }
391
+ }
392
+ return;
393
+ }
394
+ this.openState = true;
395
+ const openThread = new OpenThread({ threadId: this.threadPath, load, sinceTurn: load ? sinceTurn : undefined });
396
+ if (load) {
397
+ this.setLoadState(this.loadingStateForOpen(openThread, sinceTurn));
398
+ }
399
+ else {
400
+ this.notifyListeners();
401
+ }
402
+ try {
403
+ await this.client.sendAgentMessage(openThread, { ignoreOffline: true });
404
+ await this.requestModels({ ignoreOffline: true });
405
+ }
406
+ catch (error) {
407
+ this.setLoadState(this.failedLoadState(error));
408
+ throw error;
409
+ }
410
+ }
411
+ async close() {
412
+ if (!this.openState) {
413
+ return;
414
+ }
415
+ this.markClosed();
416
+ await this.client.sendAgentMessage(new CloseThread({ threadId: this.threadPath }), { ignoreOffline: true });
417
+ }
418
+ requestModels({ ignoreOffline = false } = {}) {
419
+ return this.client.sendAgentMessage(new ModelsRequest({ messageId: uuidv4() }), { ignoreOffline });
420
+ }
421
+ changeModel({ provider, model, voice }) {
422
+ return this.client.sendAgentMessage(new ChangeModel({
423
+ threadId: this.threadPath,
424
+ messageId: uuidv4(),
425
+ provider,
426
+ model,
427
+ voice: nonEmptyTrimmed(voice),
428
+ }));
429
+ }
430
+ interruptTurn(turnId) {
431
+ return this.client.sendAgentMessage(new TurnInterrupt({ threadId: this.threadPath, turnId }));
432
+ }
433
+ respondToClientToolCall({ turnId, requestId, response }) {
434
+ return this.client.sendAgentMessage(new AgentClientToolCallResponse({
435
+ threadId: this.threadPath,
436
+ turnId,
437
+ requestId,
438
+ response,
439
+ }));
440
+ }
441
+ async sendText({ messageId, text, attachments, steer = false, turnId, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
442
+ const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
443
+ const payload = steer
444
+ ? new TurnSteer({
445
+ threadId: this.threadPath,
446
+ messageId: resolvedMessageId,
447
+ senderName: nonEmptyTrimmed(senderName),
448
+ turnId: nonEmptyTrimmed(turnId) ?? resolvedMessageId,
449
+ content: agentInputContent({ text, attachments }),
450
+ })
451
+ : new TurnStart({
452
+ threadId: this.threadPath,
453
+ messageId: resolvedMessageId,
454
+ senderName: nonEmptyTrimmed(senderName),
455
+ content: agentInputContent({ text, attachments }),
456
+ provider: nonEmptyTrimmed(provider),
457
+ model: nonEmptyTrimmed(model),
458
+ voice: nonEmptyTrimmed(voice),
459
+ outputModalities: outputModalities != null && outputModalities.length > 0 ? outputModalities : undefined,
460
+ clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
461
+ });
462
+ this.markPending(new PendingAgentInput({
463
+ messageId: resolvedMessageId,
464
+ messageType: payload.type,
465
+ threadPath: this.threadPath,
466
+ payload,
467
+ createdAt: new Date(),
468
+ awaitingAcceptance: true,
469
+ awaitingApplication: true,
470
+ }));
471
+ this.addAgentMessage(new AgentMessageEvent({ message: payload }));
472
+ try {
473
+ await this.client.sendAgentMessage(payload);
474
+ return resolvedMessageId;
475
+ }
476
+ catch (error) {
477
+ this.pendingInputsById.delete(resolvedMessageId);
478
+ this.notifyListeners();
479
+ throw error;
480
+ }
481
+ }
482
+ async sendRealtimeAudioChunk({ chunk, format }) {
483
+ await this.client.sendAgentMessage(new AgentRealtimeAudioChunk({
484
+ threadId: this.threadPath,
485
+ messageId: uuidv4(),
486
+ format,
487
+ }), { attachment: chunk });
488
+ }
489
+ async commitRealtimeAudio({ turnId, provider, model, voice, outputModalities, }) {
490
+ const messageId = uuidv4();
491
+ await this.client.sendAgentMessage(new AgentRealtimeAudioCommit({
492
+ threadId: this.threadPath,
493
+ messageId,
494
+ turnId,
495
+ }));
496
+ await this.client.sendAgentMessage(new TurnStart({
497
+ threadId: this.threadPath,
498
+ messageId: uuidv4(),
499
+ turnId,
500
+ provider: nonEmptyTrimmed(provider),
501
+ model: nonEmptyTrimmed(model),
502
+ voice: nonEmptyTrimmed(voice),
503
+ outputModalities: outputModalities != null && outputModalities.length > 0 ? outputModalities : undefined,
504
+ }));
505
+ return messageId;
506
+ }
507
+ deleteThread(threadPath) {
508
+ return this.client.sendAgentMessage(new DeleteThread({ threadId: threadPath }));
509
+ }
510
+ renameThread(threadPath, name) {
511
+ return this.client.sendAgentMessage(new RenameThread({ threadId: threadPath, name }));
512
+ }
513
+ addAgentMessage(event) {
514
+ this._messages.push(event);
515
+ const message = event.message;
516
+ const type = message.type;
517
+ this.lastAgentMessageType = type;
518
+ this.lastAgentMessageAt = new Date();
519
+ const messageId = message.messageId;
520
+ const sourceMessageId = sourceMessageIdFor(message);
521
+ if (type === agentTurnStartType || type === agentTurnSteerType) {
522
+ const normalizedMessageId = messageId.trim();
523
+ if (normalizedMessageId !== "") {
524
+ this.markPending(new PendingAgentInput({
525
+ messageId: normalizedMessageId,
526
+ messageType: type,
527
+ threadPath: this.threadPath,
528
+ payload: message,
529
+ createdAt: new Date(),
530
+ awaitingAcceptance: true,
531
+ awaitingApplication: true,
532
+ }));
533
+ }
534
+ }
535
+ else if (type === agentTurnStartAcceptedType || type === agentTurnSteerAcceptedType) {
536
+ this.updatePending(sourceMessageId, { awaitingAcceptance: false, awaitingApplication: true });
537
+ }
538
+ else if (type === agentTurnStartedType || type === agentTurnSteeredType) {
539
+ if (sourceMessageId != null) {
540
+ this.pendingInputsById.delete(sourceMessageId);
541
+ }
542
+ }
543
+ else if (type === agentTurnStartRejectedType || type === agentTurnSteerRejectedType) {
544
+ if (sourceMessageId != null) {
545
+ this.pendingInputsById.delete(sourceMessageId);
546
+ }
547
+ }
548
+ else if (type === agentTurnEndedType || type === agentThreadClearedType) {
549
+ this.pendingInputsById.clear();
550
+ if (message.type === agentTurnEndedType && "turnId" in message && typeof message.turnId === "string" && message.turnId.trim() !== "") {
551
+ this.lastCompletedTurnIdValue = message.turnId.trim();
552
+ }
553
+ }
554
+ else if (type === agentThreadLoadedType) {
555
+ this.setLoadState(new ChatThreadSessionLoadState({
556
+ phase: ChatThreadSessionLoadPhase.Loaded,
557
+ startedAt: this.loadStateValue.startedAt,
558
+ completedAt: this.lastAgentMessageAt,
559
+ requestMessageId: this.loadStateValue.requestMessageId,
560
+ sinceTurn: this.loadStateValue.sinceTurn,
561
+ lastMessageType: type,
562
+ lastMessageAt: this.lastAgentMessageAt,
563
+ }), { notify: false });
564
+ }
565
+ this.notifyListeners();
566
+ }
567
+ markClosed({ notify = true } = {}) {
568
+ this.openState = false;
569
+ this.loadStateValue = new ChatThreadSessionLoadState({
570
+ phase: ChatThreadSessionLoadPhase.Idle,
571
+ lastMessageType: this.lastAgentMessageType,
572
+ lastMessageAt: this.lastAgentMessageAt,
573
+ });
574
+ if (notify) {
575
+ this.notifyListeners();
576
+ }
577
+ }
578
+ loadingStateForOpen(request, sinceTurn) {
579
+ return new ChatThreadSessionLoadState({
580
+ phase: ChatThreadSessionLoadPhase.Loading,
581
+ startedAt: new Date(),
582
+ requestMessageId: request.messageId,
583
+ sinceTurn,
584
+ lastMessageType: this.lastAgentMessageType,
585
+ lastMessageAt: this.lastAgentMessageAt,
586
+ });
587
+ }
588
+ failedLoadState(error) {
589
+ return new ChatThreadSessionLoadState({
590
+ phase: ChatThreadSessionLoadPhase.Failed,
591
+ startedAt: this.loadStateValue.startedAt,
592
+ completedAt: new Date(),
593
+ requestMessageId: this.loadStateValue.requestMessageId,
594
+ sinceTurn: this.loadStateValue.sinceTurn,
595
+ error: String(error),
596
+ lastMessageType: this.lastAgentMessageType,
597
+ lastMessageAt: this.lastAgentMessageAt,
598
+ });
599
+ }
600
+ setLoadState(loadState, { notify = true } = {}) {
601
+ this.loadStateValue = loadState;
602
+ if (notify) {
603
+ this.notifyListeners();
604
+ }
605
+ }
606
+ markPending(pending) {
607
+ this.pendingInputsById.set(pending.messageId, pending);
608
+ }
609
+ updatePending(messageId, { awaitingAcceptance, awaitingApplication, awaitingOnline, }) {
610
+ const normalized = messageId?.trim();
611
+ if (normalized == null || normalized === "") {
612
+ return;
613
+ }
614
+ const existing = this.pendingInputsById.get(normalized);
615
+ if (existing == null) {
616
+ return;
617
+ }
618
+ this.pendingInputsById.set(normalized, existing.copyWith({
619
+ awaitingAcceptance,
620
+ awaitingApplication,
621
+ awaitingOnline,
622
+ }));
623
+ }
624
+ }
625
+ function sourceMessageIdFor(message) {
626
+ const value = "sourceMessageId" in message ? message.sourceMessageId : undefined;
627
+ return typeof value === "string" ? value : undefined;
628
+ }
629
+ function nonEmptyTrimmed(value) {
630
+ const normalized = value?.trim();
631
+ return normalized == null || normalized === "" ? undefined : normalized;
632
+ }