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