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