@meshagent/meshagent-agents 0.43.1 → 0.43.3
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 +8 -0
- package/dist/browser/agent-messages.d.ts +1 -0
- package/dist/browser/agent-messages.js +11 -6
- package/dist/browser/chat-client.d.ts +33 -9
- package/dist/browser/chat-client.js +416 -84
- package/dist/esm/agent-messages.d.ts +1 -0
- package/dist/esm/agent-messages.js +11 -6
- package/dist/esm/chat-client.d.ts +33 -9
- package/dist/esm/chat-client.js +418 -86
- package/dist/node/agent-messages.d.ts +1 -0
- package/dist/node/agent-messages.js +11 -6
- package/dist/node/chat-client.d.ts +33 -9
- package/dist/node/chat-client.js +416 -84
- package/package.json +2 -2
package/dist/esm/chat-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Completer,
|
|
1
|
+
import { Completer, } from "@meshagent/meshagent";
|
|
2
2
|
import { v4 as uuidv4 } from "uuid";
|
|
3
|
-
import { AgentMessage as AgentMessageFactory, AgentRealtimeAudioChunk, AgentRealtimeAudioCommit, ChangeModel, CloseThread, DeleteThread, ModelsRequest, OpenThread, RenameThread, StartThread, AgentConnectionStatus, TurnInterrupt, TurnStart, TurnSteer, AgentClientToolCallResponse, agentInputContent, agentRoomMessageType, agentThreadClearedType, agentThreadLoadedType, agentConnectionStatusType, agentThreadStartRejectedType, agentThreadStartedType, agentTurnEndedType, agentTurnStartAcceptedType, agentTurnStartRejectedType, agentTurnStartType, agentTurnStartedType, agentTurnSteerAcceptedType, agentTurnSteerRejectedType, agentTurnSteerType, agentTurnSteeredType, } from "./agent-messages.js";
|
|
3
|
+
import { AgentMessage as AgentMessageFactory, AgentAudioGenerationDelta, AgentAudioTranscriptionDelta, AgentRealtimeAudioChunk, AgentRealtimeAudioCommit, AgentFileContentDelta, AgentModelChanged, AgentReasoningContentDelta, AgentTextContentDelta, AgentThreadStatus, AgentToolCallArgumentsDelta, AgentToolCallLogDelta, ChangeModel, CloseThread, DeleteThread, ModelsResponse, ModelsRequest, OpenThread, RenameThread, StartThread, AgentConnectionStatus, TurnInterrupt, TurnStart, TurnSteer, AgentClientToolCallRequested, AgentClientToolCallResponse, agentInputContent, agentAudioGenerationDeltaType, agentAudioTranscriptionDeltaType, agentFileContentDeltaType, agentImageGenerationCompletedType, agentImageGenerationPartialType, agentModelChangedType, agentModelsResponseType, agentReasoningContentDeltaType, agentRoomMessageType, agentTextContentDeltaType, agentThreadClearedType, agentThreadLoadedType, agentConnectionStatusType, agentThreadStatusType, agentThreadStartType, agentThreadStartRejectedType, agentThreadStartedType, agentToolCallArgumentsDeltaType, agentToolCallLogDeltaType, agentTurnEndedType, agentTurnStartAcceptedType, agentTurnStartRejectedType, agentTurnStartType, agentTurnStartedType, agentTurnSteerAcceptedType, agentTurnSteerRejectedType, agentTurnSteerType, agentTurnSteeredType, } from "./agent-messages.js";
|
|
4
4
|
import { ChangeEmitter } from "./change-emitter.js";
|
|
5
5
|
export class AgentMessageEvent {
|
|
6
6
|
constructor({ message, attachment }) {
|
|
@@ -11,6 +11,30 @@ export class AgentMessageEvent {
|
|
|
11
11
|
return this.message.toJson();
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
function agentRoomMessageFromEvent(event) {
|
|
15
|
+
const candidate = event.message;
|
|
16
|
+
if (candidate == null || typeof candidate !== "object" || Array.isArray(candidate)) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
const roomMessage = candidate;
|
|
20
|
+
if (roomMessage.type !== agentRoomMessageType) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
if (roomMessage.message == null || typeof roomMessage.message !== "object" || Array.isArray(roomMessage.message)) {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
const rawMessage = roomMessage.message;
|
|
27
|
+
const payload = typeof rawMessage.type === "string" ? rawMessage : rawMessage.payload;
|
|
28
|
+
if (payload == null || typeof payload !== "object" || Array.isArray(payload)) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
fromParticipantId: typeof roomMessage.fromParticipantId === "string" ? roomMessage.fromParticipantId : undefined,
|
|
33
|
+
message: payload,
|
|
34
|
+
local: roomMessage.local === true,
|
|
35
|
+
attachment: roomMessage.attachment instanceof Uint8Array ? roomMessage.attachment : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
14
38
|
export class PendingAgentInput {
|
|
15
39
|
constructor({ messageId, messageType, threadPath, payload, createdAt, awaitingAcceptance = false, awaitingApplication = false, awaitingOnline = false, }) {
|
|
16
40
|
this.messageId = messageId;
|
|
@@ -69,28 +93,52 @@ export class ChatThreadStartResult {
|
|
|
69
93
|
}
|
|
70
94
|
class EventQueue {
|
|
71
95
|
constructor() {
|
|
72
|
-
this.
|
|
73
|
-
this.waiters = [];
|
|
96
|
+
this.subscribers = new Set();
|
|
74
97
|
}
|
|
75
98
|
add(item) {
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
for (const subscriber of this.subscribers) {
|
|
100
|
+
if (subscriber.closed) {
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
const waiter = subscriber.waiters.shift();
|
|
104
|
+
if (waiter != null) {
|
|
105
|
+
waiter({ value: item, done: false });
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
subscriber.items.push(item);
|
|
109
|
+
}
|
|
80
110
|
}
|
|
81
|
-
this.items.push(item);
|
|
82
111
|
}
|
|
83
112
|
get stream() {
|
|
84
113
|
return {
|
|
85
|
-
[Symbol.asyncIterator]: () =>
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
114
|
+
[Symbol.asyncIterator]: () => {
|
|
115
|
+
const subscriber = {
|
|
116
|
+
items: [],
|
|
117
|
+
waiters: [],
|
|
118
|
+
closed: false,
|
|
119
|
+
};
|
|
120
|
+
this.subscribers.add(subscriber);
|
|
121
|
+
return {
|
|
122
|
+
next: async () => {
|
|
123
|
+
if (subscriber.closed) {
|
|
124
|
+
return { done: true, value: undefined };
|
|
125
|
+
}
|
|
126
|
+
const item = subscriber.items.shift();
|
|
127
|
+
if (item !== undefined) {
|
|
128
|
+
return { value: item, done: false };
|
|
129
|
+
}
|
|
130
|
+
return new Promise((resolve) => subscriber.waiters.push(resolve));
|
|
131
|
+
},
|
|
132
|
+
return: async () => {
|
|
133
|
+
subscriber.closed = true;
|
|
134
|
+
this.subscribers.delete(subscriber);
|
|
135
|
+
for (const waiter of subscriber.waiters.splice(0)) {
|
|
136
|
+
waiter({ done: true, value: undefined });
|
|
137
|
+
}
|
|
138
|
+
return { done: true, value: undefined };
|
|
139
|
+
},
|
|
140
|
+
};
|
|
141
|
+
},
|
|
94
142
|
};
|
|
95
143
|
}
|
|
96
144
|
}
|
|
@@ -99,6 +147,7 @@ export class BaseChatClient extends ChangeEmitter {
|
|
|
99
147
|
super(...arguments);
|
|
100
148
|
this.sessionsByPath = new Map();
|
|
101
149
|
this.pendingStartRequests = new Map();
|
|
150
|
+
this.pendingSessionEventsByPath = new Map();
|
|
102
151
|
this.eventQueue = new EventQueue();
|
|
103
152
|
}
|
|
104
153
|
get events() {
|
|
@@ -121,7 +170,7 @@ export class BaseChatClient extends ChangeEmitter {
|
|
|
121
170
|
cleanParticipantName(value) {
|
|
122
171
|
return nonEmptyTrimmed(value);
|
|
123
172
|
}
|
|
124
|
-
openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false } = {}) {
|
|
173
|
+
openThread(threadPath, { load = true, sinceTurn, reloadIfOpen = false, drainPendingEvents = true, } = {}) {
|
|
125
174
|
const normalized = threadPath.trim();
|
|
126
175
|
if (normalized === "") {
|
|
127
176
|
throw new TypeError("thread path cannot be empty");
|
|
@@ -129,23 +178,32 @@ export class BaseChatClient extends ChangeEmitter {
|
|
|
129
178
|
const existing = this.sessionsByPath.get(normalized);
|
|
130
179
|
if (existing != null) {
|
|
131
180
|
if (!existing.isOpen || reloadIfOpen) {
|
|
132
|
-
|
|
181
|
+
this.openSession(existing, { load, sinceTurn });
|
|
133
182
|
}
|
|
134
183
|
return existing;
|
|
135
184
|
}
|
|
136
185
|
const created = new ChatThreadSession({ client: this, threadPath: normalized });
|
|
137
186
|
this.sessionsByPath.set(normalized, created);
|
|
138
|
-
|
|
187
|
+
if (drainPendingEvents) {
|
|
188
|
+
this.drainPendingSessionEvents(normalized, created);
|
|
189
|
+
}
|
|
190
|
+
this.openSession(created, { load, sinceTurn });
|
|
139
191
|
this.notifyListeners();
|
|
140
192
|
return created;
|
|
141
193
|
}
|
|
142
|
-
|
|
194
|
+
openSession(session, options) {
|
|
195
|
+
void session.open(options).catch(() => {
|
|
196
|
+
this.notifyListeners();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
async startThread({ messageId, message, attachments, name, backend, provider, model, voice, outputModalities, realtimeProtocol, senderName, clientToolkits, omitContent = false, }) {
|
|
143
200
|
const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
|
|
144
201
|
const resolvedSenderName = this.cleanParticipantName(senderName) ?? this.localParticipantName();
|
|
145
202
|
const payload = new StartThread({
|
|
146
203
|
messageId: resolvedMessageId,
|
|
147
204
|
content: omitContent ? undefined : agentInputContent({ text: message, attachments }),
|
|
148
205
|
name: nonEmptyTrimmed(name),
|
|
206
|
+
backend: nonEmptyTrimmed(backend),
|
|
149
207
|
provider: nonEmptyTrimmed(provider),
|
|
150
208
|
model: nonEmptyTrimmed(model),
|
|
151
209
|
voice: nonEmptyTrimmed(voice),
|
|
@@ -165,10 +223,11 @@ export class BaseChatClient extends ChangeEmitter {
|
|
|
165
223
|
if (threadPath === "") {
|
|
166
224
|
throw new Error("Agent did not return a thread_id for the new thread.");
|
|
167
225
|
}
|
|
168
|
-
const session = this.openThread(threadPath, { load: false });
|
|
226
|
+
const session = this.openThread(threadPath, { load: false, drainPendingEvents: false });
|
|
169
227
|
if (!omitContent) {
|
|
170
228
|
session.addAgentMessage(new AgentMessageEvent({ message: payload }));
|
|
171
229
|
}
|
|
230
|
+
this.drainPendingSessionEvents(threadPath, session);
|
|
172
231
|
return new ChatThreadStartResult({
|
|
173
232
|
session,
|
|
174
233
|
threadPath,
|
|
@@ -181,6 +240,16 @@ export class BaseChatClient extends ChangeEmitter {
|
|
|
181
240
|
this.pendingStartRequests.delete(resolvedMessageId);
|
|
182
241
|
}
|
|
183
242
|
}
|
|
243
|
+
drainPendingSessionEvents(threadPath, session) {
|
|
244
|
+
const pendingEvents = this.pendingSessionEventsByPath.get(threadPath);
|
|
245
|
+
if (pendingEvents == null) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
this.pendingSessionEventsByPath.delete(threadPath);
|
|
249
|
+
for (const event of pendingEvents) {
|
|
250
|
+
session.addAgentMessage(event);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
184
253
|
handleAgentMessage(message, { attachment } = {}) {
|
|
185
254
|
if (message.type === agentConnectionStatusType) {
|
|
186
255
|
this._connectionStatus = message;
|
|
@@ -205,7 +274,16 @@ export class BaseChatClient extends ChangeEmitter {
|
|
|
205
274
|
this.notifyListeners();
|
|
206
275
|
return;
|
|
207
276
|
}
|
|
208
|
-
|
|
277
|
+
const event = new AgentMessageEvent({ message, attachment });
|
|
278
|
+
const session = this.sessionsByPath.get(threadPath);
|
|
279
|
+
if (session != null) {
|
|
280
|
+
session.addAgentMessage(event);
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
const pending = this.pendingSessionEventsByPath.get(threadPath) ?? [];
|
|
284
|
+
pending.push(event);
|
|
285
|
+
this.pendingSessionEventsByPath.set(threadPath, pending);
|
|
286
|
+
}
|
|
209
287
|
this.notifyListeners();
|
|
210
288
|
}
|
|
211
289
|
emitConnectionStatus({ status, message, reason, retryDelay, }) {
|
|
@@ -238,7 +316,6 @@ export class MessagingChatClient extends BaseChatClient {
|
|
|
238
316
|
constructor({ room, agentName }) {
|
|
239
317
|
super();
|
|
240
318
|
this.pendingParticipantWaits = new Map();
|
|
241
|
-
this.roomAbortController = new AbortController();
|
|
242
319
|
this.started = false;
|
|
243
320
|
this.onMessagingChanged = () => {
|
|
244
321
|
const participant = this.agentParticipant();
|
|
@@ -252,10 +329,21 @@ export class MessagingChatClient extends BaseChatClient {
|
|
|
252
329
|
}
|
|
253
330
|
this.notifyListeners();
|
|
254
331
|
};
|
|
332
|
+
this.onMessagingMessage = (event) => {
|
|
333
|
+
this.onMessagingChanged();
|
|
334
|
+
const roomMessage = agentRoomMessageFromEvent(event);
|
|
335
|
+
if (roomMessage == null || roomMessage.local) {
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const participant = this.agentParticipant();
|
|
339
|
+
if (participant == null || roomMessage.fromParticipantId !== participant.id) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
this.handleAgentMessage(AgentMessageFactory.fromJson(roomMessage.message), { attachment: roomMessage.attachment });
|
|
343
|
+
};
|
|
255
344
|
this.room = room;
|
|
256
345
|
this.agentName = agentName;
|
|
257
|
-
this.room.messaging.addListener("message", this.
|
|
258
|
-
this.roomPump = this.consumeRoomEvents();
|
|
346
|
+
this.room.messaging.addListener("message", this.onMessagingMessage);
|
|
259
347
|
}
|
|
260
348
|
localParticipantName() {
|
|
261
349
|
const name = this.room.localParticipant?.getAttribute("name");
|
|
@@ -271,15 +359,13 @@ export class MessagingChatClient extends BaseChatClient {
|
|
|
271
359
|
}
|
|
272
360
|
async stop() {
|
|
273
361
|
this.started = false;
|
|
274
|
-
this.
|
|
275
|
-
this.room.messaging.removeListener("message", this.onMessagingChanged);
|
|
362
|
+
this.room.messaging.removeListener("message", this.onMessagingMessage);
|
|
276
363
|
for (const wait of this.pendingParticipantWaits.values()) {
|
|
277
364
|
if (!wait.completed) {
|
|
278
365
|
wait.completeError(new Error("Chat client stopped while waiting for an agent participant."));
|
|
279
366
|
}
|
|
280
367
|
}
|
|
281
368
|
this.pendingParticipantWaits.clear();
|
|
282
|
-
await this.roomPump?.catch(() => undefined);
|
|
283
369
|
}
|
|
284
370
|
agentParticipant() {
|
|
285
371
|
const normalizedAgentName = this.agentName?.trim();
|
|
@@ -320,37 +406,15 @@ export class MessagingChatClient extends BaseChatClient {
|
|
|
320
406
|
ignoreOffline,
|
|
321
407
|
});
|
|
322
408
|
}
|
|
323
|
-
async consumeRoomEvents() {
|
|
324
|
-
try {
|
|
325
|
-
for await (const event of this.room.listen({ abortSignal: this.roomAbortController.signal })) {
|
|
326
|
-
this.onRoomEvent(event);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
catch {
|
|
330
|
-
if (!this.roomAbortController.signal.aborted) {
|
|
331
|
-
throw new Error("chat client room event listener stopped unexpectedly");
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
onRoomEvent(event) {
|
|
336
|
-
if (!(event instanceof RoomMessageEvent) || event.message.type !== agentRoomMessageType) {
|
|
337
|
-
return;
|
|
338
|
-
}
|
|
339
|
-
const participant = this.agentParticipant();
|
|
340
|
-
if (participant == null || event.message.fromParticipantId !== participant.id) {
|
|
341
|
-
return;
|
|
342
|
-
}
|
|
343
|
-
const message = event.message.message;
|
|
344
|
-
const rawPayload = typeof message.type === "string" ? message : message.payload;
|
|
345
|
-
if (rawPayload != null && typeof rawPayload === "object" && !Array.isArray(rawPayload)) {
|
|
346
|
-
this.handleAgentMessage(AgentMessageFactory.fromJson(rawPayload), { attachment: event.message.attachment });
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
409
|
}
|
|
350
410
|
export class ChatThreadSession extends ChangeEmitter {
|
|
351
411
|
constructor({ client, threadPath }) {
|
|
352
412
|
super();
|
|
353
413
|
this._messages = [];
|
|
414
|
+
this.messageIndexes = new Map();
|
|
415
|
+
this.localAgentMessageIds = new Set();
|
|
416
|
+
this.pendingLocalInputMessageIds = new Set();
|
|
417
|
+
this.mergedDeltaMessageIds = new Set();
|
|
354
418
|
this.pendingInputsById = new Map();
|
|
355
419
|
this.openState = false;
|
|
356
420
|
this.loadStateValue = ChatThreadSessionLoadState.idle();
|
|
@@ -375,6 +439,18 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
375
439
|
get lastCompletedTurnId() {
|
|
376
440
|
return this.lastCompletedTurnIdValue;
|
|
377
441
|
}
|
|
442
|
+
get activeTurnId() {
|
|
443
|
+
return this.activeTurnIdValue;
|
|
444
|
+
}
|
|
445
|
+
get threadStatus() {
|
|
446
|
+
return this.threadStatusValue;
|
|
447
|
+
}
|
|
448
|
+
get currentModel() {
|
|
449
|
+
return this.currentModelValue;
|
|
450
|
+
}
|
|
451
|
+
get modelsResponse() {
|
|
452
|
+
return this.modelsResponseValue;
|
|
453
|
+
}
|
|
378
454
|
async open({ load = true, sinceTurn } = {}) {
|
|
379
455
|
if (this.openState) {
|
|
380
456
|
if (load) {
|
|
@@ -418,10 +494,11 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
418
494
|
requestModels({ ignoreOffline = false } = {}) {
|
|
419
495
|
return this.client.sendAgentMessage(new ModelsRequest({ messageId: uuidv4() }), { ignoreOffline });
|
|
420
496
|
}
|
|
421
|
-
changeModel({ provider, model, voice }) {
|
|
497
|
+
changeModel({ backend, provider, model, voice }) {
|
|
422
498
|
return this.client.sendAgentMessage(new ChangeModel({
|
|
423
499
|
threadId: this.threadPath,
|
|
424
500
|
messageId: uuidv4(),
|
|
501
|
+
backend: nonEmptyTrimmed(backend),
|
|
425
502
|
provider,
|
|
426
503
|
model,
|
|
427
504
|
voice: nonEmptyTrimmed(voice),
|
|
@@ -438,8 +515,16 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
438
515
|
response,
|
|
439
516
|
}));
|
|
440
517
|
}
|
|
441
|
-
async sendText({ messageId, text, attachments, steer = false, turnId, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
|
|
518
|
+
async sendText({ messageId, text, attachments, steer = false, turnId, backend, provider, model, voice, outputModalities, senderName, clientToolkits, }) {
|
|
442
519
|
const resolvedMessageId = messageId == null || messageId.trim() === "" ? uuidv4() : messageId.trim();
|
|
520
|
+
const currentModel = this.currentModelValue;
|
|
521
|
+
const resolvedBackend = nonEmptyTrimmed(backend) ?? currentModel?.backend;
|
|
522
|
+
const resolvedProvider = nonEmptyTrimmed(provider) ?? currentModel?.provider;
|
|
523
|
+
const resolvedModel = nonEmptyTrimmed(model) ?? currentModel?.model;
|
|
524
|
+
const resolvedVoice = nonEmptyTrimmed(voice) ?? currentModel?.voice;
|
|
525
|
+
const resolvedOutputModalities = outputModalities != null && outputModalities.length > 0
|
|
526
|
+
? outputModalities
|
|
527
|
+
: currentModel?.outputModalities;
|
|
443
528
|
const payload = steer
|
|
444
529
|
? new TurnSteer({
|
|
445
530
|
threadId: this.threadPath,
|
|
@@ -453,21 +538,15 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
453
538
|
messageId: resolvedMessageId,
|
|
454
539
|
senderName: nonEmptyTrimmed(senderName),
|
|
455
540
|
content: agentInputContent({ text, attachments }),
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
541
|
+
backend: resolvedBackend,
|
|
542
|
+
provider: resolvedProvider,
|
|
543
|
+
model: resolvedModel,
|
|
544
|
+
voice: resolvedVoice,
|
|
545
|
+
outputModalities: resolvedOutputModalities,
|
|
460
546
|
clientToolkits: clientToolkits != null && clientToolkits.length > 0 ? clientToolkits : undefined,
|
|
461
547
|
});
|
|
462
|
-
this.
|
|
463
|
-
|
|
464
|
-
messageType: payload.type,
|
|
465
|
-
threadPath: this.threadPath,
|
|
466
|
-
payload,
|
|
467
|
-
createdAt: new Date(),
|
|
468
|
-
awaitingAcceptance: true,
|
|
469
|
-
awaitingApplication: true,
|
|
470
|
-
}));
|
|
548
|
+
this.localAgentMessageIds.add(resolvedMessageId);
|
|
549
|
+
this.pendingLocalInputMessageIds.add(resolvedMessageId);
|
|
471
550
|
this.addAgentMessage(new AgentMessageEvent({ message: payload }));
|
|
472
551
|
try {
|
|
473
552
|
await this.client.sendAgentMessage(payload);
|
|
@@ -511,16 +590,86 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
511
590
|
return this.client.sendAgentMessage(new RenameThread({ threadId: threadPath, name }));
|
|
512
591
|
}
|
|
513
592
|
addAgentMessage(event) {
|
|
514
|
-
this._messages.push(event);
|
|
515
593
|
const message = event.message;
|
|
516
594
|
const type = message.type;
|
|
517
595
|
this.lastAgentMessageType = type;
|
|
518
596
|
this.lastAgentMessageAt = new Date();
|
|
597
|
+
this.updateSessionState(message);
|
|
598
|
+
this.updatePendingInputsForMessage(message);
|
|
599
|
+
this.appendRenderableMessage(event);
|
|
600
|
+
this.notifyListeners();
|
|
601
|
+
}
|
|
602
|
+
updateSessionState(message) {
|
|
603
|
+
if (message.type === agentTurnEndedType) {
|
|
604
|
+
const turnId = normalizedString(threadMessageValue(message, "turnId"));
|
|
605
|
+
if (turnId != null) {
|
|
606
|
+
this.lastCompletedTurnIdValue = turnId;
|
|
607
|
+
if (this.activeTurnIdValue === turnId) {
|
|
608
|
+
this.activeTurnIdValue = undefined;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
|
|
612
|
+
if (statusTurnId == null || statusTurnId === turnId) {
|
|
613
|
+
this.threadStatusValue = undefined;
|
|
614
|
+
}
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
if (message.type === agentTurnStartAcceptedType || message.type === agentTurnStartedType) {
|
|
618
|
+
const turnId = normalizedString(threadMessageValue(message, "turnId"));
|
|
619
|
+
if (turnId != null) {
|
|
620
|
+
this.activeTurnIdValue = turnId;
|
|
621
|
+
}
|
|
622
|
+
if (message.type === agentTurnStartedType) {
|
|
623
|
+
const statusTurnId = normalizedString(this.threadStatusValue?.turnId);
|
|
624
|
+
if (statusTurnId == null || statusTurnId === turnId) {
|
|
625
|
+
this.threadStatusValue = undefined;
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
if (message.type === agentThreadStatusType && message instanceof AgentThreadStatus) {
|
|
631
|
+
this.threadStatusValue = message;
|
|
632
|
+
const mode = normalizedString(message.mode);
|
|
633
|
+
const status = normalizedString(message.status);
|
|
634
|
+
const turnId = normalizedString(message.turnId);
|
|
635
|
+
if (status != null && mode === "steerable" && turnId != null) {
|
|
636
|
+
this.activeTurnIdValue = turnId;
|
|
637
|
+
}
|
|
638
|
+
else if (status == null && turnId != null && this.activeTurnIdValue === turnId) {
|
|
639
|
+
this.activeTurnIdValue = undefined;
|
|
640
|
+
}
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
if (message.type === agentModelChangedType && message instanceof AgentModelChanged) {
|
|
644
|
+
this.currentModelValue = message;
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
if (message.type === agentModelsResponseType && message instanceof ModelsResponse) {
|
|
648
|
+
this.modelsResponseValue = message;
|
|
649
|
+
this.applyActiveModel(message);
|
|
650
|
+
return;
|
|
651
|
+
}
|
|
652
|
+
if (message.type === agentThreadLoadedType) {
|
|
653
|
+
this.setLoadState(new ChatThreadSessionLoadState({
|
|
654
|
+
phase: ChatThreadSessionLoadPhase.Loaded,
|
|
655
|
+
startedAt: this.loadStateValue.startedAt,
|
|
656
|
+
completedAt: this.lastAgentMessageAt,
|
|
657
|
+
requestMessageId: this.loadStateValue.requestMessageId,
|
|
658
|
+
sinceTurn: this.loadStateValue.sinceTurn,
|
|
659
|
+
lastMessageType: message.type,
|
|
660
|
+
lastMessageAt: this.lastAgentMessageAt,
|
|
661
|
+
}), { notify: false });
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
updatePendingInputsForMessage(message) {
|
|
665
|
+
const type = message.type;
|
|
519
666
|
const messageId = message.messageId;
|
|
520
667
|
const sourceMessageId = sourceMessageIdFor(message);
|
|
521
|
-
if (type === agentTurnStartType || type === agentTurnSteerType) {
|
|
668
|
+
if (type === agentThreadStartType || type === agentTurnStartType || type === agentTurnSteerType) {
|
|
522
669
|
const normalizedMessageId = messageId.trim();
|
|
523
670
|
if (normalizedMessageId !== "") {
|
|
671
|
+
this.localAgentMessageIds.add(normalizedMessageId);
|
|
672
|
+
this.pendingLocalInputMessageIds.add(normalizedMessageId);
|
|
524
673
|
this.markPending(new PendingAgentInput({
|
|
525
674
|
messageId: normalizedMessageId,
|
|
526
675
|
messageType: type,
|
|
@@ -532,37 +681,152 @@ export class ChatThreadSession extends ChangeEmitter {
|
|
|
532
681
|
}));
|
|
533
682
|
}
|
|
534
683
|
}
|
|
684
|
+
else if (type === agentThreadStartRejectedType) {
|
|
685
|
+
if (sourceMessageId != null) {
|
|
686
|
+
this.pendingInputsById.delete(sourceMessageId);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
535
689
|
else if (type === agentTurnStartAcceptedType || type === agentTurnSteerAcceptedType) {
|
|
536
690
|
this.updatePending(sourceMessageId, { awaitingAcceptance: false, awaitingApplication: true });
|
|
537
691
|
}
|
|
538
692
|
else if (type === agentTurnStartedType || type === agentTurnSteeredType) {
|
|
539
693
|
if (sourceMessageId != null) {
|
|
540
694
|
this.pendingInputsById.delete(sourceMessageId);
|
|
695
|
+
this.pendingLocalInputMessageIds.delete(sourceMessageId);
|
|
541
696
|
}
|
|
542
697
|
}
|
|
543
698
|
else if (type === agentTurnStartRejectedType || type === agentTurnSteerRejectedType) {
|
|
544
699
|
if (sourceMessageId != null) {
|
|
545
700
|
this.pendingInputsById.delete(sourceMessageId);
|
|
701
|
+
this.pendingLocalInputMessageIds.delete(sourceMessageId);
|
|
546
702
|
}
|
|
547
703
|
}
|
|
548
704
|
else if (type === agentTurnEndedType || type === agentThreadClearedType) {
|
|
549
705
|
this.pendingInputsById.clear();
|
|
550
|
-
|
|
551
|
-
|
|
706
|
+
this.pendingLocalInputMessageIds.clear();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
appendRenderableMessage(event) {
|
|
710
|
+
const message = event.message;
|
|
711
|
+
if (message.type === agentTurnEndedType) {
|
|
712
|
+
if (message.error != null) {
|
|
713
|
+
this.appendMessage(event);
|
|
552
714
|
}
|
|
715
|
+
return;
|
|
553
716
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
717
|
+
if (message.type === agentThreadStartType || message.type === agentTurnStartType || message.type === agentTurnSteerType) {
|
|
718
|
+
if (agentInputText(message.content).trim() !== "") {
|
|
719
|
+
this.appendMessage(event);
|
|
720
|
+
}
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
if (message.type === agentTurnStartAcceptedType || message.type === agentTurnSteerAcceptedType) {
|
|
724
|
+
const sourceMessageId = normalizedString(sourceMessageIdFor(message));
|
|
725
|
+
if (sourceMessageId != null && this.localAgentMessageIds.has(sourceMessageId)) {
|
|
726
|
+
this.pendingLocalInputMessageIds.delete(sourceMessageId);
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (agentInputText(message.content).trim() !== "") {
|
|
730
|
+
this.appendMessage(event, { beforePendingLocalInputs: true });
|
|
731
|
+
}
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
if (isMergeableDelta(message)) {
|
|
735
|
+
this.appendOrMergeDelta(event);
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
if (message.type === agentImageGenerationPartialType || message.type === agentImageGenerationCompletedType) {
|
|
739
|
+
const itemId = normalizedString(message.itemId);
|
|
740
|
+
if (itemId != null) {
|
|
741
|
+
this.upsertItemMessage(`image_generation:${itemId}`, event);
|
|
742
|
+
}
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
if (message instanceof AgentClientToolCallRequested) {
|
|
746
|
+
this.appendMessage(event);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
appendMessage(event, { beforePendingLocalInputs = false } = {}) {
|
|
750
|
+
const normalizedMessageId = normalizedString(event.message.messageId);
|
|
751
|
+
if (normalizedMessageId == null || this.messageIndexes.has(normalizedMessageId)) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (beforePendingLocalInputs) {
|
|
755
|
+
const insertIndex = this._messages.findIndex((existing) => this.pendingLocalInputMessageIds.has(existing.message.messageId));
|
|
756
|
+
if (insertIndex >= 0) {
|
|
757
|
+
this._messages.splice(insertIndex, 0, event);
|
|
758
|
+
this.indexMessagesFrom(insertIndex);
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
this.messageIndexes.set(normalizedMessageId, this._messages.length);
|
|
763
|
+
this._messages.push(event);
|
|
764
|
+
}
|
|
765
|
+
upsertItemMessage(key, event) {
|
|
766
|
+
const existingIndex = this.messageIndexes.get(key);
|
|
767
|
+
if (existingIndex == null) {
|
|
768
|
+
this.messageIndexes.set(key, this._messages.length);
|
|
769
|
+
this._messages.push(event);
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
this._messages[existingIndex] = event;
|
|
773
|
+
}
|
|
774
|
+
appendOrMergeDelta(event) {
|
|
775
|
+
const message = event.message;
|
|
776
|
+
const normalizedMessageId = normalizedString(message.messageId);
|
|
777
|
+
if (normalizedMessageId != null) {
|
|
778
|
+
if (this.mergedDeltaMessageIds.has(normalizedMessageId)) {
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
this.mergedDeltaMessageIds.add(normalizedMessageId);
|
|
782
|
+
}
|
|
783
|
+
const itemId = normalizedString(message.itemId);
|
|
784
|
+
if (itemId == null) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const key = `${message.type}:${itemId}`;
|
|
788
|
+
const existingIndex = this.messageIndexes.get(key);
|
|
789
|
+
if (existingIndex == null) {
|
|
790
|
+
this.messageIndexes.set(key, this._messages.length);
|
|
791
|
+
this._messages.push(event);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
const existing = this._messages[existingIndex].message;
|
|
795
|
+
const merged = mergeDeltaMessage(existing, message);
|
|
796
|
+
if (merged != null) {
|
|
797
|
+
this._messages[existingIndex] = new AgentMessageEvent({ message: merged, attachment: event.attachment });
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
indexMessagesFrom(start) {
|
|
801
|
+
for (let index = Math.max(0, start); index < this._messages.length; index += 1) {
|
|
802
|
+
const key = messageIndexKey(this._messages[index].message);
|
|
803
|
+
if (key != null) {
|
|
804
|
+
this.messageIndexes.set(key, index);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
applyActiveModel(response) {
|
|
809
|
+
for (const provider of response.providers) {
|
|
810
|
+
for (const model of provider.models) {
|
|
811
|
+
if (!model.active) {
|
|
812
|
+
continue;
|
|
813
|
+
}
|
|
814
|
+
this.currentModelValue = new AgentModelChanged({
|
|
815
|
+
threadId: this.threadPath,
|
|
816
|
+
sourceMessageId: response.sourceMessageId,
|
|
817
|
+
backend: provider.backend,
|
|
818
|
+
provider: provider.name,
|
|
819
|
+
model: model.name,
|
|
820
|
+
voice: model.defaultOutputVoice,
|
|
821
|
+
inputFormat: model.inputFormat,
|
|
822
|
+
outputFormat: model.outputFormat,
|
|
823
|
+
turnDetection: model.turnDetection,
|
|
824
|
+
realtimeProtocols: model.realtimeProtocols,
|
|
825
|
+
outputModalities: model.modalities.length > 0 ? [model.modalities[0]] : ["text"],
|
|
826
|
+
});
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
564
829
|
}
|
|
565
|
-
this.notifyListeners();
|
|
566
830
|
}
|
|
567
831
|
markClosed({ notify = true } = {}) {
|
|
568
832
|
this.openState = false;
|
|
@@ -626,6 +890,74 @@ function sourceMessageIdFor(message) {
|
|
|
626
890
|
const value = "sourceMessageId" in message ? message.sourceMessageId : undefined;
|
|
627
891
|
return typeof value === "string" ? value : undefined;
|
|
628
892
|
}
|
|
893
|
+
function threadMessageValue(message, key) {
|
|
894
|
+
const value = message[key];
|
|
895
|
+
return typeof value === "string" ? value : undefined;
|
|
896
|
+
}
|
|
897
|
+
function agentInputText(content) {
|
|
898
|
+
if (!Array.isArray(content)) {
|
|
899
|
+
return "";
|
|
900
|
+
}
|
|
901
|
+
return content
|
|
902
|
+
.filter((entry) => (typeof entry === "object" &&
|
|
903
|
+
entry !== null &&
|
|
904
|
+
"type" in entry &&
|
|
905
|
+
entry.type === "text" &&
|
|
906
|
+
"text" in entry &&
|
|
907
|
+
typeof entry.text === "string"))
|
|
908
|
+
.map((entry) => entry.text)
|
|
909
|
+
.join("");
|
|
910
|
+
}
|
|
911
|
+
function isMergeableDelta(message) {
|
|
912
|
+
return message.type === agentAudioGenerationDeltaType ||
|
|
913
|
+
message.type === agentAudioTranscriptionDeltaType ||
|
|
914
|
+
message.type === agentFileContentDeltaType ||
|
|
915
|
+
message.type === agentReasoningContentDeltaType ||
|
|
916
|
+
message.type === agentTextContentDeltaType ||
|
|
917
|
+
message.type === agentToolCallArgumentsDeltaType ||
|
|
918
|
+
message.type === agentToolCallLogDeltaType;
|
|
919
|
+
}
|
|
920
|
+
function messageIndexKey(message) {
|
|
921
|
+
if (isMergeableDelta(message)) {
|
|
922
|
+
const itemId = normalizedString(message.itemId);
|
|
923
|
+
return itemId == null ? undefined : `${message.type}:${itemId}`;
|
|
924
|
+
}
|
|
925
|
+
return normalizedString(message.messageId);
|
|
926
|
+
}
|
|
927
|
+
function mergeDeltaMessage(existing, incoming) {
|
|
928
|
+
if (existing.type !== incoming.type) {
|
|
929
|
+
return undefined;
|
|
930
|
+
}
|
|
931
|
+
if (existing.type === agentAudioGenerationDeltaType && existing instanceof AgentAudioGenerationDelta && incoming instanceof AgentAudioGenerationDelta) {
|
|
932
|
+
const data = new Uint8Array(existing.data.length + incoming.data.length);
|
|
933
|
+
data.set(existing.data, 0);
|
|
934
|
+
data.set(incoming.data, existing.data.length);
|
|
935
|
+
return new AgentAudioGenerationDelta({ ...existing.toJson(), data });
|
|
936
|
+
}
|
|
937
|
+
if (existing.type === agentAudioTranscriptionDeltaType && existing instanceof AgentAudioTranscriptionDelta && incoming instanceof AgentAudioTranscriptionDelta) {
|
|
938
|
+
return new AgentAudioTranscriptionDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
|
|
939
|
+
}
|
|
940
|
+
if (existing.type === agentFileContentDeltaType && incoming instanceof AgentFileContentDelta) {
|
|
941
|
+
return incoming;
|
|
942
|
+
}
|
|
943
|
+
if (existing.type === agentReasoningContentDeltaType && existing instanceof AgentReasoningContentDelta && incoming instanceof AgentReasoningContentDelta) {
|
|
944
|
+
return new AgentReasoningContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
|
|
945
|
+
}
|
|
946
|
+
if (existing.type === agentTextContentDeltaType && existing instanceof AgentTextContentDelta && incoming instanceof AgentTextContentDelta) {
|
|
947
|
+
return new AgentTextContentDelta({ ...existing.toJson(), text: `${existing.text}${incoming.text}` });
|
|
948
|
+
}
|
|
949
|
+
if (existing.type === agentToolCallArgumentsDeltaType && existing instanceof AgentToolCallArgumentsDelta && incoming instanceof AgentToolCallArgumentsDelta) {
|
|
950
|
+
return new AgentToolCallArgumentsDelta({ ...existing.toJson(), delta: `${existing.delta}${incoming.delta}` });
|
|
951
|
+
}
|
|
952
|
+
if (existing.type === agentToolCallLogDeltaType && existing instanceof AgentToolCallLogDelta && incoming instanceof AgentToolCallLogDelta) {
|
|
953
|
+
return new AgentToolCallLogDelta({ ...existing.toJson(), lines: [...existing.lines, ...incoming.lines] });
|
|
954
|
+
}
|
|
955
|
+
return undefined;
|
|
956
|
+
}
|
|
957
|
+
function normalizedString(value) {
|
|
958
|
+
const normalized = value?.trim();
|
|
959
|
+
return normalized == null || normalized === "" ? undefined : normalized;
|
|
960
|
+
}
|
|
629
961
|
function nonEmptyTrimmed(value) {
|
|
630
962
|
const normalized = value?.trim();
|
|
631
963
|
return normalized == null || normalized === "" ? undefined : normalized;
|