@meshagent/meshagent-agents 0.47.1 → 0.49.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 +18 -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 +93 -33
- package/dist/browser/chat-client.js +483 -206
- 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 +93 -33
- package/dist/esm/chat-client.js +478 -206
- 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 +93 -33
- package/dist/node/chat-client.js +483 -206
- 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,
|
|
@@ -367,9 +459,17 @@ export class ThreadListSubscribe {
|
|
|
367
459
|
await this.stream.close();
|
|
368
460
|
}
|
|
369
461
|
}
|
|
462
|
+
function participantWaitAbortedError(reason) {
|
|
463
|
+
if (reason instanceof Error) {
|
|
464
|
+
return reason;
|
|
465
|
+
}
|
|
466
|
+
const error = new Error("Agent participant wait was cancelled.");
|
|
467
|
+
error.name = "AbortError";
|
|
468
|
+
return error;
|
|
469
|
+
}
|
|
370
470
|
export class BaseMessagingChatClient extends BaseChatClient {
|
|
371
|
-
constructor({ room, agentName }) {
|
|
372
|
-
super();
|
|
471
|
+
constructor({ room, agentName, threadCreatedPendingStartMatcher, deduplicateClientToolRequests = true, }) {
|
|
472
|
+
super({ threadCreatedPendingStartMatcher, deduplicateClientToolRequests });
|
|
373
473
|
this.pendingParticipantWaits = new Map();
|
|
374
474
|
this.started = false;
|
|
375
475
|
this.hasConnected = false;
|
|
@@ -466,7 +566,7 @@ export class BaseMessagingChatClient extends BaseChatClient {
|
|
|
466
566
|
this.attachMessagingListeners();
|
|
467
567
|
this.room.messaging.start();
|
|
468
568
|
this.startRoomStatusListener();
|
|
469
|
-
|
|
569
|
+
this.room.messaging.enable();
|
|
470
570
|
this.waitingForParticipant = true;
|
|
471
571
|
this.onMessagingChanged();
|
|
472
572
|
}
|
|
@@ -517,7 +617,13 @@ export class BaseMessagingChatClient extends BaseChatClient {
|
|
|
517
617
|
}
|
|
518
618
|
return null;
|
|
519
619
|
}
|
|
520
|
-
async waitForAgentParticipant({ waitKey } = {}) {
|
|
620
|
+
async waitForAgentParticipant({ waitKey, signal, timeoutMs, } = {}) {
|
|
621
|
+
if (signal?.aborted) {
|
|
622
|
+
throw participantWaitAbortedError(signal.reason);
|
|
623
|
+
}
|
|
624
|
+
if (timeoutMs != null && (!Number.isFinite(timeoutMs) || timeoutMs < 0)) {
|
|
625
|
+
throw new RangeError("Agent participant wait timeout must be a non-negative finite number.");
|
|
626
|
+
}
|
|
521
627
|
const existing = this.agentParticipant();
|
|
522
628
|
if (existing != null) {
|
|
523
629
|
return existing;
|
|
@@ -525,12 +631,34 @@ export class BaseMessagingChatClient extends BaseChatClient {
|
|
|
525
631
|
const key = waitKey == null || waitKey.trim() === "" ? uuidv4() : waitKey.trim();
|
|
526
632
|
const completer = new Completer();
|
|
527
633
|
this.pendingParticipantWaits.set(key, completer);
|
|
634
|
+
const handleAbort = () => {
|
|
635
|
+
if (!completer.completed) {
|
|
636
|
+
completer.completeError(participantWaitAbortedError(signal?.reason));
|
|
637
|
+
}
|
|
638
|
+
};
|
|
639
|
+
signal?.addEventListener("abort", handleAbort, { once: true });
|
|
640
|
+
if (signal?.aborted) {
|
|
641
|
+
handleAbort();
|
|
642
|
+
}
|
|
643
|
+
const timeout = timeoutMs == null
|
|
644
|
+
? undefined
|
|
645
|
+
: setTimeout(() => {
|
|
646
|
+
if (!completer.completed) {
|
|
647
|
+
completer.completeError(new Error(`Agent messaging participant was not available within ${timeoutMs}ms.`));
|
|
648
|
+
}
|
|
649
|
+
}, timeoutMs);
|
|
528
650
|
this.onMessagingChanged();
|
|
529
651
|
try {
|
|
530
652
|
return await completer.fut;
|
|
531
653
|
}
|
|
532
654
|
finally {
|
|
533
|
-
|
|
655
|
+
signal?.removeEventListener("abort", handleAbort);
|
|
656
|
+
if (timeout != null) {
|
|
657
|
+
clearTimeout(timeout);
|
|
658
|
+
}
|
|
659
|
+
if (this.pendingParticipantWaits.get(key) === completer) {
|
|
660
|
+
this.pendingParticipantWaits.delete(key);
|
|
661
|
+
}
|
|
534
662
|
}
|
|
535
663
|
}
|
|
536
664
|
async sendAgentMessage(message, { attachment } = {}) {
|
|
@@ -584,6 +712,254 @@ export class BaseMessagingChatClient extends BaseChatClient {
|
|
|
584
712
|
}
|
|
585
713
|
}
|
|
586
714
|
}
|
|
715
|
+
export class WebSocketChatClient extends BaseChatClient {
|
|
716
|
+
constructor({ url, token, participantName, protocols = ["meshagent-msgpack"], reconnect = true, reconnectInitialDelay = 1000, reconnectMaxDelay = 10000, }) {
|
|
717
|
+
super({ deduplicateClientToolRequests: true });
|
|
718
|
+
this.started = false;
|
|
719
|
+
this.stopping = false;
|
|
720
|
+
this.connecting = false;
|
|
721
|
+
this.reconnectAttempts = 0;
|
|
722
|
+
this.onMessage = (event) => {
|
|
723
|
+
try {
|
|
724
|
+
const bytes = webSocketMessageBytes(event.data);
|
|
725
|
+
if (bytes == null) {
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
const decoded = decode(bytes);
|
|
729
|
+
if (!isJsonMap(decoded)) {
|
|
730
|
+
throw new Error("chat websocket received a non-object message");
|
|
731
|
+
}
|
|
732
|
+
const message = AgentMessageFactory.fromJson(decoded);
|
|
733
|
+
if (message instanceof AgentConnectionStatus) {
|
|
734
|
+
const participantId = message.participantId?.trim();
|
|
735
|
+
if (participantId != null && participantId !== "") {
|
|
736
|
+
this.assignedParticipantId = participantId;
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
this.handleAgentMessage(message);
|
|
740
|
+
}
|
|
741
|
+
catch (error) {
|
|
742
|
+
this.receiveError = error;
|
|
743
|
+
this.emitConnectionStatus({
|
|
744
|
+
status: "disconnected",
|
|
745
|
+
message: "Chat websocket message could not be decoded",
|
|
746
|
+
reason: describeError(error),
|
|
747
|
+
});
|
|
748
|
+
this.webSocket?.close(1003);
|
|
749
|
+
}
|
|
750
|
+
};
|
|
751
|
+
this.onSocketError = (event) => {
|
|
752
|
+
this.receiveError = event.error ?? new Error("chat websocket error");
|
|
753
|
+
};
|
|
754
|
+
this.onSocketClose = (event) => {
|
|
755
|
+
const webSocket = this.webSocket;
|
|
756
|
+
if (webSocket == null) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
this.closeCode = event.code;
|
|
760
|
+
this.closeReason = event.reason;
|
|
761
|
+
this.detachSocketListeners(webSocket);
|
|
762
|
+
this.webSocket = undefined;
|
|
763
|
+
this.assignedParticipantId = undefined;
|
|
764
|
+
if (this.stopping || !this.started) {
|
|
765
|
+
this.notifyListeners();
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const reason = event.reason !== "" ? `code=${event.code}: ${event.reason}` : `code=${event.code}`;
|
|
769
|
+
if (this.reconnect) {
|
|
770
|
+
this.scheduleReconnect({ reason });
|
|
771
|
+
}
|
|
772
|
+
else {
|
|
773
|
+
this.emitConnectionStatus({
|
|
774
|
+
status: "disconnected",
|
|
775
|
+
message: "Chat websocket disconnected",
|
|
776
|
+
reason,
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
this.notifyListeners();
|
|
780
|
+
};
|
|
781
|
+
this.url = url;
|
|
782
|
+
this.token = token;
|
|
783
|
+
this.participantName = participantName;
|
|
784
|
+
this.protocols = protocols;
|
|
785
|
+
this.reconnect = reconnect;
|
|
786
|
+
this.reconnectInitialDelay = reconnectInitialDelay;
|
|
787
|
+
this.reconnectMaxDelay = reconnectMaxDelay;
|
|
788
|
+
}
|
|
789
|
+
get isConnected() {
|
|
790
|
+
return this.webSocket != null && this.webSocket.readyState === WebSocket.OPEN;
|
|
791
|
+
}
|
|
792
|
+
localParticipantName() {
|
|
793
|
+
return this.cleanParticipantName(this.participantName);
|
|
794
|
+
}
|
|
795
|
+
localParticipantId() {
|
|
796
|
+
return this.assignedParticipantId;
|
|
797
|
+
}
|
|
798
|
+
async start() {
|
|
799
|
+
this.started = true;
|
|
800
|
+
this.stopping = false;
|
|
801
|
+
if (this.webSocket != null || this.connecting) {
|
|
802
|
+
return;
|
|
803
|
+
}
|
|
804
|
+
await this.connect({ isReconnect: false });
|
|
805
|
+
}
|
|
806
|
+
async connect({ isReconnect }) {
|
|
807
|
+
if (this.webSocket != null || this.connecting || !this.started || this.stopping) {
|
|
808
|
+
return;
|
|
809
|
+
}
|
|
810
|
+
this.connecting = true;
|
|
811
|
+
const webSocket = new WebSocket(this.url.toString(), this.resolvedProtocols());
|
|
812
|
+
this.webSocket = webSocket;
|
|
813
|
+
this.assignedParticipantId = undefined;
|
|
814
|
+
webSocket.binaryType = "arraybuffer";
|
|
815
|
+
try {
|
|
816
|
+
await new Promise((resolve, reject) => {
|
|
817
|
+
const onOpen = () => {
|
|
818
|
+
cleanup();
|
|
819
|
+
resolve();
|
|
820
|
+
};
|
|
821
|
+
const onError = (event) => {
|
|
822
|
+
cleanup();
|
|
823
|
+
reject(event.error ?? new Error("chat websocket connection failed"));
|
|
824
|
+
};
|
|
825
|
+
const cleanup = () => {
|
|
826
|
+
webSocket.removeEventListener("open", onOpen);
|
|
827
|
+
webSocket.removeEventListener("error", onError);
|
|
828
|
+
};
|
|
829
|
+
webSocket.addEventListener("open", onOpen);
|
|
830
|
+
webSocket.addEventListener("error", onError);
|
|
831
|
+
});
|
|
832
|
+
}
|
|
833
|
+
catch (error) {
|
|
834
|
+
if (this.webSocket === webSocket) {
|
|
835
|
+
this.webSocket = undefined;
|
|
836
|
+
}
|
|
837
|
+
this.receiveError = error;
|
|
838
|
+
this.connecting = false;
|
|
839
|
+
if (isReconnect && this.reconnect && !this.stopping) {
|
|
840
|
+
this.scheduleReconnect({ reason: describeError(error) });
|
|
841
|
+
return;
|
|
842
|
+
}
|
|
843
|
+
this.emitConnectionStatus({
|
|
844
|
+
status: "disconnected",
|
|
845
|
+
message: "Chat websocket connection failed",
|
|
846
|
+
reason: describeError(error),
|
|
847
|
+
});
|
|
848
|
+
throw error;
|
|
849
|
+
}
|
|
850
|
+
webSocket.addEventListener("message", this.onMessage);
|
|
851
|
+
webSocket.addEventListener("error", this.onSocketError);
|
|
852
|
+
webSocket.addEventListener("close", this.onSocketClose);
|
|
853
|
+
this.connecting = false;
|
|
854
|
+
this.receiveError = undefined;
|
|
855
|
+
this.closeCode = undefined;
|
|
856
|
+
this.closeReason = undefined;
|
|
857
|
+
const status = isReconnect ? "reconnected" : "connected";
|
|
858
|
+
this.emitConnectionStatus({
|
|
859
|
+
status,
|
|
860
|
+
message: isReconnect ? "Chat websocket reconnected" : "Chat websocket connected",
|
|
861
|
+
});
|
|
862
|
+
if (isReconnect) {
|
|
863
|
+
await this.reopenOpenSessions();
|
|
864
|
+
}
|
|
865
|
+
this.reconnectAttempts = 0;
|
|
866
|
+
this.notifyListeners();
|
|
867
|
+
}
|
|
868
|
+
async stop() {
|
|
869
|
+
this.started = false;
|
|
870
|
+
this.stopping = true;
|
|
871
|
+
if (this.reconnectTimer != null) {
|
|
872
|
+
clearTimeout(this.reconnectTimer);
|
|
873
|
+
this.reconnectTimer = undefined;
|
|
874
|
+
}
|
|
875
|
+
const webSocket = this.webSocket;
|
|
876
|
+
this.webSocket = undefined;
|
|
877
|
+
this.assignedParticipantId = undefined;
|
|
878
|
+
if (webSocket != null) {
|
|
879
|
+
this.detachSocketListeners(webSocket);
|
|
880
|
+
webSocket.close(1000);
|
|
881
|
+
}
|
|
882
|
+
this.emitConnectionStatus({
|
|
883
|
+
status: "disconnected",
|
|
884
|
+
message: "Chat websocket disconnected",
|
|
885
|
+
reason: "client_stopped",
|
|
886
|
+
});
|
|
887
|
+
this.stopping = false;
|
|
888
|
+
this.notifyListeners();
|
|
889
|
+
}
|
|
890
|
+
async sendAgentMessage(message, { attachment } = {}) {
|
|
891
|
+
if (attachment != null) {
|
|
892
|
+
throw new Error("WebSocketChatClient does not support binary attachments yet.");
|
|
893
|
+
}
|
|
894
|
+
const webSocket = this.webSocket;
|
|
895
|
+
if (webSocket == null || webSocket.readyState !== WebSocket.OPEN) {
|
|
896
|
+
throw new Error(this.closedMessage());
|
|
897
|
+
}
|
|
898
|
+
webSocket.send(encode(message.toJson()));
|
|
899
|
+
}
|
|
900
|
+
detachSocketListeners(webSocket) {
|
|
901
|
+
webSocket.removeEventListener("message", this.onMessage);
|
|
902
|
+
webSocket.removeEventListener("error", this.onSocketError);
|
|
903
|
+
webSocket.removeEventListener("close", this.onSocketClose);
|
|
904
|
+
}
|
|
905
|
+
scheduleReconnect({ reason }) {
|
|
906
|
+
if (this.reconnectTimer != null || this.stopping || !this.started) {
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const retryDelay = this.nextReconnectDelay();
|
|
910
|
+
this.emitConnectionStatus({
|
|
911
|
+
status: "reconnecting",
|
|
912
|
+
message: "Chat websocket disconnected; reconnecting",
|
|
913
|
+
reason,
|
|
914
|
+
retryDelay,
|
|
915
|
+
});
|
|
916
|
+
this.reconnectTimer = setTimeout(() => {
|
|
917
|
+
this.reconnectTimer = undefined;
|
|
918
|
+
void this.connect({ isReconnect: true });
|
|
919
|
+
}, retryDelay);
|
|
920
|
+
}
|
|
921
|
+
nextReconnectDelay() {
|
|
922
|
+
const initialDelay = Math.max(1, this.reconnectInitialDelay);
|
|
923
|
+
const maxDelay = Math.max(initialDelay, this.reconnectMaxDelay);
|
|
924
|
+
const factor = 2 ** Math.min(this.reconnectAttempts, 6);
|
|
925
|
+
this.reconnectAttempts += 1;
|
|
926
|
+
return Math.min(initialDelay * factor, maxDelay);
|
|
927
|
+
}
|
|
928
|
+
async reopenOpenSessions() {
|
|
929
|
+
for (const session of this.sessions) {
|
|
930
|
+
if (!session.isOpen) {
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
await session.open({ load: true, sinceTurn: session.lastCompletedTurnId });
|
|
934
|
+
}
|
|
935
|
+
}
|
|
936
|
+
resolvedProtocols() {
|
|
937
|
+
const resolved = [];
|
|
938
|
+
for (const protocol of this.protocols) {
|
|
939
|
+
const normalized = protocol.trim();
|
|
940
|
+
if (normalized !== "" && !resolved.includes(normalized)) {
|
|
941
|
+
resolved.push(normalized);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
const normalizedToken = this.token.trim();
|
|
945
|
+
if (normalizedToken !== "") {
|
|
946
|
+
resolved.push(`meshagent-agent.${normalizedToken}`);
|
|
947
|
+
}
|
|
948
|
+
return resolved;
|
|
949
|
+
}
|
|
950
|
+
closedMessage() {
|
|
951
|
+
if (this.receiveError != null) {
|
|
952
|
+
return `chat websocket is closed: ${describeError(this.receiveError)}`;
|
|
953
|
+
}
|
|
954
|
+
if (this.closeCode != null) {
|
|
955
|
+
return `chat websocket is closed (code=${this.closeCode})`;
|
|
956
|
+
}
|
|
957
|
+
if (this.closeReason != null && this.closeReason.trim() !== "") {
|
|
958
|
+
return `chat websocket is closed: ${this.closeReason}`;
|
|
959
|
+
}
|
|
960
|
+
return "chat websocket is closed";
|
|
961
|
+
}
|
|
962
|
+
}
|
|
587
963
|
export class MessagingChatClient extends BaseMessagingChatClient {
|
|
588
964
|
constructor() {
|
|
589
965
|
super(...arguments);
|
|
@@ -647,7 +1023,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
|
|
|
647
1023
|
await super.sendAgentMessage(message, { attachment });
|
|
648
1024
|
return;
|
|
649
1025
|
}
|
|
650
|
-
if (message
|
|
1026
|
+
if (message instanceof OpenThread) {
|
|
651
1027
|
const previous = this.threadSubscriptions.get(message.threadId);
|
|
652
1028
|
if (previous != null) {
|
|
653
1029
|
await previous.close();
|
|
@@ -663,7 +1039,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
|
|
|
663
1039
|
this.trackStream(subscription);
|
|
664
1040
|
return;
|
|
665
1041
|
}
|
|
666
|
-
if (message
|
|
1042
|
+
if (message instanceof CloseThread) {
|
|
667
1043
|
const subscription = this.threadSubscriptions.get(message.threadId);
|
|
668
1044
|
if (subscription != null) {
|
|
669
1045
|
this.threadSubscriptions.delete(message.threadId);
|
|
@@ -671,7 +1047,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
|
|
|
671
1047
|
return;
|
|
672
1048
|
}
|
|
673
1049
|
}
|
|
674
|
-
if (message
|
|
1050
|
+
if (message instanceof WatchThreads) {
|
|
675
1051
|
await this.threadListSubscription?.close();
|
|
676
1052
|
const stream = await this.room.messaging.stream({
|
|
677
1053
|
to: participant,
|
|
@@ -684,7 +1060,7 @@ export class MessagingChatClient extends BaseMessagingChatClient {
|
|
|
684
1060
|
this.trackStream(subscription);
|
|
685
1061
|
return;
|
|
686
1062
|
}
|
|
687
|
-
if (message
|
|
1063
|
+
if (message instanceof UnwatchThreads && this.threadListSubscription != null) {
|
|
688
1064
|
const subscription = this.threadListSubscription;
|
|
689
1065
|
this.threadListSubscription = undefined;
|
|
690
1066
|
await subscription.close();
|
|
@@ -722,6 +1098,9 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
722
1098
|
this.client = client;
|
|
723
1099
|
this.threadPath = threadPath;
|
|
724
1100
|
}
|
|
1101
|
+
static create({ client, threadPath }) {
|
|
1102
|
+
return new ChatThreadSession({ client, threadPath });
|
|
1103
|
+
}
|
|
725
1104
|
get messages() {
|
|
726
1105
|
return [...this._messages];
|
|
727
1106
|
}
|
|
@@ -740,18 +1119,6 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
740
1119
|
get lastCompletedTurnId() {
|
|
741
1120
|
return this.lastCompletedTurnIdValue;
|
|
742
1121
|
}
|
|
743
|
-
get activeTurnId() {
|
|
744
|
-
return this.activeTurnIdValue;
|
|
745
|
-
}
|
|
746
|
-
get threadStatus() {
|
|
747
|
-
return this.threadStatusValue;
|
|
748
|
-
}
|
|
749
|
-
get currentModel() {
|
|
750
|
-
return this.currentModelValue;
|
|
751
|
-
}
|
|
752
|
-
get modelsResponse() {
|
|
753
|
-
return this.modelsResponseValue;
|
|
754
|
-
}
|
|
755
1122
|
async open({ load = true, sinceTurn } = {}) {
|
|
756
1123
|
if (this.openState) {
|
|
757
1124
|
if (load) {
|
|
@@ -766,7 +1133,6 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
766
1133
|
}
|
|
767
1134
|
catch (error) {
|
|
768
1135
|
this.setLoadState(this.failedLoadState(error));
|
|
769
|
-
throw error;
|
|
770
1136
|
}
|
|
771
1137
|
}
|
|
772
1138
|
return;
|
|
@@ -785,7 +1151,6 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
785
1151
|
}
|
|
786
1152
|
catch (error) {
|
|
787
1153
|
this.setLoadState(this.failedLoadState(error));
|
|
788
|
-
throw error;
|
|
789
1154
|
}
|
|
790
1155
|
}
|
|
791
1156
|
resetReplayState() {
|
|
@@ -832,16 +1197,20 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
832
1197
|
response,
|
|
833
1198
|
}));
|
|
834
1199
|
}
|
|
835
|
-
|
|
1200
|
+
claimClientToolCall(requestId) {
|
|
1201
|
+
return this.client.claimClientToolRequest(this.threadPath, requestId);
|
|
1202
|
+
}
|
|
1203
|
+
finishClientToolCall(requestId, { responseSent }) {
|
|
1204
|
+
this.client.finishClientToolRequest(this.threadPath, requestId, { responseSent });
|
|
1205
|
+
}
|
|
1206
|
+
injectMessages(messages) {
|
|
1207
|
+
return this.client.sendAgentMessage(new InjectMessages({
|
|
1208
|
+
threadId: this.threadPath,
|
|
1209
|
+
messages: [...messages],
|
|
1210
|
+
}));
|
|
1211
|
+
}
|
|
1212
|
+
async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
|
|
836
1213
|
const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
|
|
837
|
-
const currentModel = this.currentModelValue;
|
|
838
|
-
const resolvedBackend = nonEmptyTrimmed(backend) ?? currentModel?.backend;
|
|
839
|
-
const resolvedProvider = nonEmptyTrimmed(provider) ?? currentModel?.provider;
|
|
840
|
-
const resolvedModel = nonEmptyTrimmed(model) ?? currentModel?.model;
|
|
841
|
-
const resolvedVoice = nonEmptyTrimmed(voice) ?? currentModel?.voice;
|
|
842
|
-
const resolvedOutputModalities = outputModalities != null && outputModalities.length > 0
|
|
843
|
-
? outputModalities
|
|
844
|
-
: currentModel?.outputModalities;
|
|
845
1214
|
const payload = steer
|
|
846
1215
|
? new TurnSteer({
|
|
847
1216
|
threadId: this.threadPath,
|
|
@@ -855,13 +1224,12 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
855
1224
|
messageId: resolvedMessageId,
|
|
856
1225
|
senderName: nonEmptyTrimmed(senderName),
|
|
857
1226
|
content: agentInputContent({ text, attachments }),
|
|
858
|
-
backend:
|
|
859
|
-
provider:
|
|
860
|
-
model:
|
|
861
|
-
voice:
|
|
862
|
-
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,
|
|
863
1232
|
clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
|
|
864
|
-
toolChoice: toolChoice?.toJson(),
|
|
865
1233
|
});
|
|
866
1234
|
this.localAgentMessageIds.add(resolvedMessageId);
|
|
867
1235
|
this.pendingLocalInputMessageIds.add(resolvedMessageId);
|
|
@@ -880,10 +1248,10 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
880
1248
|
await this.client.sendAgentMessage(new AgentRealtimeAudioChunk({
|
|
881
1249
|
threadId: this.threadPath,
|
|
882
1250
|
messageId: uuidv4(),
|
|
883
|
-
format,
|
|
1251
|
+
format: new AgentAudioFormat(format),
|
|
884
1252
|
}), { attachment: chunk });
|
|
885
1253
|
}
|
|
886
|
-
async commitRealtimeAudio({ turnId, provider, model, voice, outputModalities, }) {
|
|
1254
|
+
async commitRealtimeAudio({ turnId, backend, provider, model, voice, outputModalities, }) {
|
|
887
1255
|
const messageId = uuidv4();
|
|
888
1256
|
await this.client.sendAgentMessage(new AgentRealtimeAudioCommit({
|
|
889
1257
|
threadId: this.threadPath,
|
|
@@ -894,6 +1262,7 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
894
1262
|
threadId: this.threadPath,
|
|
895
1263
|
messageId: uuidv4(),
|
|
896
1264
|
turnId,
|
|
1265
|
+
backend: nonEmptyTrimmed(backend),
|
|
897
1266
|
provider: nonEmptyTrimmed(provider),
|
|
898
1267
|
model: nonEmptyTrimmed(model),
|
|
899
1268
|
voice: nonEmptyTrimmed(voice),
|
|
@@ -922,56 +1291,9 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
922
1291
|
const turnId = normalizedString(threadMessageValue(message, "turnId"));
|
|
923
1292
|
if (turnId != null) {
|
|
924
1293
|
this.lastCompletedTurnIdValue = turnId;
|
|
925
|
-
if (this.activeTurnIdValue === turnId) {
|
|
926
|
-
this.activeTurnIdValue = undefined;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
|
|
930
|
-
if (statusTurnId == null || statusTurnId === turnId) {
|
|
931
|
-
this.threadStatusValue = undefined;
|
|
932
1294
|
}
|
|
933
|
-
return;
|
|
934
|
-
}
|
|
935
|
-
if (message.type === agentTurnStartAcceptedType || message.type === agentTurnStartedType) {
|
|
936
|
-
const turnId = normalizedString(threadMessageValue(message, "turnId"));
|
|
937
|
-
if (turnId != null) {
|
|
938
|
-
this.activeTurnIdValue = turnId;
|
|
939
|
-
}
|
|
940
|
-
if (message.type === agentTurnStartedType) {
|
|
941
|
-
const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
|
|
942
|
-
if (statusTurnId == null || statusTurnId === turnId) {
|
|
943
|
-
this.threadStatusValue = undefined;
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
return;
|
|
947
|
-
}
|
|
948
|
-
if (message.type === agentThreadStatusType && message instanceof AgentThreadStatus) {
|
|
949
|
-
const mode = normalizedString(message.mode);
|
|
950
|
-
const status = normalizedString(message.status);
|
|
951
|
-
const turnId = normalizedString(message.turnId);
|
|
952
|
-
if (status == null) {
|
|
953
|
-
this.threadStatusValue = undefined;
|
|
954
|
-
if (turnId != null && this.activeTurnIdValue === turnId) {
|
|
955
|
-
this.activeTurnIdValue = undefined;
|
|
956
|
-
}
|
|
957
|
-
return;
|
|
958
|
-
}
|
|
959
|
-
this.threadStatusValue = message;
|
|
960
|
-
if (mode === "steerable" && turnId != null) {
|
|
961
|
-
this.activeTurnIdValue = turnId;
|
|
962
|
-
}
|
|
963
|
-
return;
|
|
964
1295
|
}
|
|
965
|
-
if (message.type ===
|
|
966
|
-
this.currentModelValue = message;
|
|
967
|
-
return;
|
|
968
|
-
}
|
|
969
|
-
if (message.type === agentModelsResponseType && message instanceof ModelsResponse) {
|
|
970
|
-
this.modelsResponseValue = message;
|
|
971
|
-
this.applyActiveModel(message);
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
|
-
if (message.type === agentThreadLoadedType) {
|
|
1296
|
+
else if (message.type === agentThreadLoadedType) {
|
|
975
1297
|
this.setLoadState(new ChatThreadSessionLoadState({
|
|
976
1298
|
phase: ChatThreadSessionLoadPhase.Loaded,
|
|
977
1299
|
startedAt: this.loadStateValue.startedAt,
|
|
@@ -1049,45 +1371,21 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1049
1371
|
}
|
|
1050
1372
|
appendRenderableMessage(event) {
|
|
1051
1373
|
const message = event.message;
|
|
1052
|
-
if (message
|
|
1053
|
-
if (message.error != null) {
|
|
1054
|
-
this.appendMessage(event);
|
|
1055
|
-
}
|
|
1056
|
-
return;
|
|
1057
|
-
}
|
|
1058
|
-
if (message.type === agentToolCallEndedType) {
|
|
1059
|
-
const itemId = message.error == null ? undefined : normalizedString(message.itemId);
|
|
1060
|
-
if (itemId != null) {
|
|
1061
|
-
this.upsertItemMessage(`tool_call:${itemId}`, event);
|
|
1062
|
-
}
|
|
1063
|
-
else if (message.error != null) {
|
|
1064
|
-
this.appendMessage(event);
|
|
1065
|
-
}
|
|
1066
|
-
return;
|
|
1067
|
-
}
|
|
1068
|
-
if (message.type === agentToolCallPendingType || message.type === agentToolCallInProgressType || message.type === agentToolCallStartedType) {
|
|
1069
|
-
const itemId = normalizedString(message.itemId);
|
|
1070
|
-
if (itemId != null) {
|
|
1071
|
-
this.upsertItemMessage(`tool_call:${itemId}`, event);
|
|
1072
|
-
}
|
|
1073
|
-
return;
|
|
1074
|
-
}
|
|
1075
|
-
if (message.type === agentSecretRequestedType) {
|
|
1374
|
+
if (message instanceof TurnEnded) {
|
|
1076
1375
|
this.appendMessage(event);
|
|
1077
1376
|
return;
|
|
1078
1377
|
}
|
|
1079
|
-
if (message
|
|
1080
|
-
|
|
1081
|
-
return;
|
|
1082
|
-
}
|
|
1083
|
-
if (message.type === agentThreadStartType || message.type === agentTurnStartType || message.type === agentTurnSteerType) {
|
|
1084
|
-
if (agentInputText(message.content).trim() !== "") {
|
|
1378
|
+
if (message instanceof StartThread || message instanceof TurnStart || message instanceof TurnSteer) {
|
|
1379
|
+
if (agentInputText(messageValue(message, "content")).trim() !== "") {
|
|
1085
1380
|
this.appendMessage(event);
|
|
1381
|
+
const messageId = normalizedString(message.messageId);
|
|
1382
|
+
if (messageId != null)
|
|
1383
|
+
this.pendingLocalInputMessageIds.add(messageId);
|
|
1086
1384
|
}
|
|
1087
1385
|
return;
|
|
1088
1386
|
}
|
|
1089
|
-
if (message
|
|
1090
|
-
const sourceMessageId = normalizedString(
|
|
1387
|
+
if (message instanceof TurnStartAccepted) {
|
|
1388
|
+
const sourceMessageId = normalizedString(message.sourceMessageId);
|
|
1091
1389
|
if (sourceMessageId != null && this.localAgentMessageIds.has(sourceMessageId)) {
|
|
1092
1390
|
this.pendingLocalInputMessageIds.delete(sourceMessageId);
|
|
1093
1391
|
return;
|
|
@@ -1095,36 +1393,21 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1095
1393
|
if (agentInputText(message.content).trim() !== "") {
|
|
1096
1394
|
this.appendMessage(event, { beforePendingLocalInputs: true });
|
|
1097
1395
|
}
|
|
1098
|
-
else {
|
|
1099
|
-
this.appendMessage(event);
|
|
1100
|
-
}
|
|
1101
|
-
return;
|
|
1102
|
-
}
|
|
1103
|
-
if (message.type === agentTurnStartedType ||
|
|
1104
|
-
message.type === agentTurnSteeredType ||
|
|
1105
|
-
message.type === agentTurnStartRejectedType ||
|
|
1106
|
-
message.type === agentTurnSteerRejectedType ||
|
|
1107
|
-
message.type === agentTurnInterruptAcceptedType ||
|
|
1108
|
-
message.type === agentTurnInterruptedType) {
|
|
1109
|
-
this.appendMessage(event);
|
|
1110
1396
|
return;
|
|
1111
1397
|
}
|
|
1112
1398
|
if (isMergeableDelta(message)) {
|
|
1113
1399
|
this.appendOrMergeDelta(event);
|
|
1114
1400
|
return;
|
|
1115
1401
|
}
|
|
1116
|
-
if (message
|
|
1117
|
-
|
|
1118
|
-
if (itemId != null) {
|
|
1119
|
-
this.upsertItemMessage(`image_generation:${itemId}`, event);
|
|
1120
|
-
}
|
|
1402
|
+
if (message instanceof AgentImageGenerationPartial || message instanceof AgentImageGenerationCompleted) {
|
|
1403
|
+
this.appendMessage(event);
|
|
1121
1404
|
return;
|
|
1122
1405
|
}
|
|
1123
|
-
if (message
|
|
1406
|
+
if (message instanceof AgentClientToolCallRequested) {
|
|
1124
1407
|
this.appendMessage(event);
|
|
1125
1408
|
return;
|
|
1126
1409
|
}
|
|
1127
|
-
if (message instanceof
|
|
1410
|
+
if (message instanceof AgentConnectionStatus || message instanceof AgentThreadMessage) {
|
|
1128
1411
|
this.appendMessage(event);
|
|
1129
1412
|
}
|
|
1130
1413
|
}
|
|
@@ -1144,15 +1427,6 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1144
1427
|
this.messageIndexes.set(normalizedMessageId, this._messages.length);
|
|
1145
1428
|
this._messages.push(event);
|
|
1146
1429
|
}
|
|
1147
|
-
upsertItemMessage(key, event) {
|
|
1148
|
-
const existingIndex = this.messageIndexes.get(key);
|
|
1149
|
-
if (existingIndex == null) {
|
|
1150
|
-
this.messageIndexes.set(key, this._messages.length);
|
|
1151
|
-
this._messages.push(event);
|
|
1152
|
-
return;
|
|
1153
|
-
}
|
|
1154
|
-
this._messages[existingIndex] = event;
|
|
1155
|
-
}
|
|
1156
1430
|
appendOrMergeDelta(event) {
|
|
1157
1431
|
const message = event.message;
|
|
1158
1432
|
const normalizedMessageId = normalizedString(message.messageId);
|
|
@@ -1162,7 +1436,7 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1162
1436
|
}
|
|
1163
1437
|
this.mergedDeltaMessageIds.add(normalizedMessageId);
|
|
1164
1438
|
}
|
|
1165
|
-
const itemId = normalizedString(message
|
|
1439
|
+
const itemId = normalizedString(messageValue(message, "itemId"));
|
|
1166
1440
|
if (itemId == null) {
|
|
1167
1441
|
return;
|
|
1168
1442
|
}
|
|
@@ -1176,7 +1450,7 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1176
1450
|
const existing = this._messages[existingIndex].message;
|
|
1177
1451
|
const merged = mergeDeltaMessage(existing, message);
|
|
1178
1452
|
if (merged != null) {
|
|
1179
|
-
this._messages[existingIndex]
|
|
1453
|
+
this._messages[existingIndex].replaceMessage(merged);
|
|
1180
1454
|
}
|
|
1181
1455
|
}
|
|
1182
1456
|
indexMessagesFrom(start) {
|
|
@@ -1187,29 +1461,6 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1187
1461
|
}
|
|
1188
1462
|
}
|
|
1189
1463
|
}
|
|
1190
|
-
applyActiveModel(response) {
|
|
1191
|
-
for (const provider of response.providers) {
|
|
1192
|
-
for (const model of provider.models) {
|
|
1193
|
-
if (!model.active) {
|
|
1194
|
-
continue;
|
|
1195
|
-
}
|
|
1196
|
-
this.currentModelValue = new AgentModelChanged({
|
|
1197
|
-
threadId: this.threadPath,
|
|
1198
|
-
sourceMessageId: response.sourceMessageId,
|
|
1199
|
-
backend: provider.backend,
|
|
1200
|
-
provider: provider.name,
|
|
1201
|
-
model: model.name,
|
|
1202
|
-
voice: model.defaultOutputVoice,
|
|
1203
|
-
inputFormat: model.inputFormat,
|
|
1204
|
-
outputFormat: model.outputFormat,
|
|
1205
|
-
turnDetection: model.turnDetection,
|
|
1206
|
-
realtimeProtocols: model.realtimeProtocols,
|
|
1207
|
-
outputModalities: model.modalities.length > 0 ? [model.modalities[0]] : ["text"],
|
|
1208
|
-
});
|
|
1209
|
-
return;
|
|
1210
|
-
}
|
|
1211
|
-
}
|
|
1212
|
-
}
|
|
1213
1464
|
markClosed({ notify = true } = {}) {
|
|
1214
1465
|
this.openState = false;
|
|
1215
1466
|
this.loadStateValue = new ChatThreadSessionLoadState({
|
|
@@ -1268,12 +1519,33 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
1268
1519
|
}));
|
|
1269
1520
|
}
|
|
1270
1521
|
}
|
|
1522
|
+
function webSocketMessageBytes(value) {
|
|
1523
|
+
if (value instanceof Uint8Array) {
|
|
1524
|
+
return value;
|
|
1525
|
+
}
|
|
1526
|
+
if (value instanceof ArrayBuffer) {
|
|
1527
|
+
return new Uint8Array(value);
|
|
1528
|
+
}
|
|
1529
|
+
if (ArrayBuffer.isView(value)) {
|
|
1530
|
+
return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
|
|
1531
|
+
}
|
|
1532
|
+
return undefined;
|
|
1533
|
+
}
|
|
1534
|
+
function isJsonMap(value) {
|
|
1535
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1536
|
+
}
|
|
1537
|
+
function describeError(error) {
|
|
1538
|
+
return error instanceof Error ? error.message : String(error);
|
|
1539
|
+
}
|
|
1271
1540
|
function sourceMessageIdFor(message) {
|
|
1272
1541
|
const value = "sourceMessageId" in message ? message.sourceMessageId : undefined;
|
|
1273
1542
|
return typeof value === "string" ? value : undefined;
|
|
1274
1543
|
}
|
|
1544
|
+
function messageValue(message, key) {
|
|
1545
|
+
return message[key];
|
|
1546
|
+
}
|
|
1275
1547
|
function threadMessageValue(message, key) {
|
|
1276
|
-
const value = message
|
|
1548
|
+
const value = messageValue(message, key);
|
|
1277
1549
|
return typeof value === "string" ? value : undefined;
|
|
1278
1550
|
}
|
|
1279
1551
|
function agentInputText(content) {
|
|
@@ -1301,7 +1573,7 @@ function isMergeableDelta(message) {
|
|
|
1301
1573
|
}
|
|
1302
1574
|
function messageIndexKey(message) {
|
|
1303
1575
|
if (isMergeableDelta(message)) {
|
|
1304
|
-
const itemId = normalizedString(message
|
|
1576
|
+
const itemId = normalizedString(messageValue(message, "itemId"));
|
|
1305
1577
|
return itemId == null ? undefined : `${message.type}:${itemId}`;
|
|
1306
1578
|
}
|
|
1307
1579
|
return normalizedString(message.messageId);
|
|
@@ -1314,30 +1586,30 @@ function mergeDeltaMessage(existing, incoming) {
|
|
|
1314
1586
|
const data = new Uint8Array(existing.data.length + incoming.data.length);
|
|
1315
1587
|
data.set(existing.data, 0);
|
|
1316
1588
|
data.set(incoming.data, existing.data.length);
|
|
1317
|
-
return
|
|
1589
|
+
return AgentAudioGenerationDelta.fromJson({ ...existing.toJson(), data });
|
|
1318
1590
|
}
|
|
1319
1591
|
if (existing.type === agentAudioTranscriptionDeltaType && existing instanceof AgentAudioTranscriptionDelta && incoming instanceof AgentAudioTranscriptionDelta) {
|
|
1320
|
-
return
|
|
1592
|
+
return AgentAudioTranscriptionDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
|
|
1321
1593
|
}
|
|
1322
1594
|
if (existing.type === agentFileContentDeltaType && incoming instanceof AgentFileContentDelta) {
|
|
1323
1595
|
return incoming;
|
|
1324
1596
|
}
|
|
1325
1597
|
if (existing.type === agentReasoningContentDeltaType && existing instanceof AgentReasoningContentDelta && incoming instanceof AgentReasoningContentDelta) {
|
|
1326
|
-
return
|
|
1598
|
+
return AgentReasoningContentDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
|
|
1327
1599
|
}
|
|
1328
1600
|
if (existing.type === agentTextContentDeltaType && existing instanceof AgentTextContentDelta && incoming instanceof AgentTextContentDelta) {
|
|
1329
|
-
return
|
|
1601
|
+
return AgentTextContentDelta.fromJson({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
|
|
1330
1602
|
}
|
|
1331
1603
|
if (existing.type === agentToolCallArgumentsDeltaType && existing instanceof AgentToolCallArgumentsDelta && incoming instanceof AgentToolCallArgumentsDelta) {
|
|
1332
|
-
return
|
|
1604
|
+
return AgentToolCallArgumentsDelta.fromJson({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
|
|
1333
1605
|
}
|
|
1334
1606
|
if (existing.type === agentToolCallLogDeltaType && existing instanceof AgentToolCallLogDelta && incoming instanceof AgentToolCallLogDelta) {
|
|
1335
|
-
return
|
|
1607
|
+
return AgentToolCallLogDelta.fromJson({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
|
|
1336
1608
|
}
|
|
1337
1609
|
return undefined;
|
|
1338
1610
|
}
|
|
1339
1611
|
function normalizedString(value) {
|
|
1340
|
-
const normalized = value
|
|
1612
|
+
const normalized = typeof value === "string" ? value.trim() : undefined;
|
|
1341
1613
|
return normalized == null || normalized === "" ? undefined : normalized;
|
|
1342
1614
|
}
|
|
1343
1615
|
function dateOrNull(value) {
|