@tanstack/ai 0.6.3 → 0.8.1
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/README.md +6 -6
- package/dist/esm/activities/chat/index.d.ts +20 -0
- package/dist/esm/activities/chat/index.js +248 -213
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/middleware/compose.d.ts +66 -0
- package/dist/esm/activities/chat/middleware/compose.js +327 -0
- package/dist/esm/activities/chat/middleware/compose.js.map +1 -0
- package/dist/esm/activities/chat/middleware/index.d.ts +2 -0
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.d.ts +89 -0
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +76 -0
- package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -0
- package/dist/esm/activities/chat/middleware/types.d.ts +307 -0
- package/dist/esm/activities/chat/stream/processor.d.ts +64 -40
- package/dist/esm/activities/chat/stream/processor.js +466 -218
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/stream/types.d.ts +17 -0
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -1
- package/dist/esm/activities/chat/tools/tool-calls.js +148 -64
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +1 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateSpeech/index.js +1 -1
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateTranscription/index.js +1 -1
- package/dist/esm/activities/generateTranscription/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +1 -1
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/summarize/index.js +1 -1
- package/dist/esm/activities/summarize/index.js.map +1 -1
- package/dist/esm/index.d.ts +3 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/middlewares/content-guard.d.ts +77 -0
- package/dist/esm/middlewares/content-guard.js +155 -0
- package/dist/esm/middlewares/content-guard.js.map +1 -0
- package/dist/esm/middlewares/index.d.ts +2 -0
- package/dist/esm/middlewares/index.js +7 -0
- package/dist/esm/middlewares/index.js.map +1 -0
- package/dist/esm/middlewares/tool-cache.d.ts +1 -0
- package/dist/esm/realtime/index.d.ts +30 -0
- package/dist/esm/realtime/index.js +8 -0
- package/dist/esm/realtime/index.js.map +1 -0
- package/dist/esm/realtime/types.d.ts +234 -0
- package/dist/esm/types.d.ts +18 -4
- package/package.json +6 -6
- package/src/activities/chat/index.ts +322 -256
- package/src/activities/chat/middleware/compose.ts +392 -0
- package/src/activities/chat/middleware/index.ts +17 -0
- package/src/activities/chat/middleware/tool-cache-middleware.ts +189 -0
- package/src/activities/chat/middleware/types.ts +419 -0
- package/src/activities/chat/stream/processor.ts +630 -259
- package/src/activities/chat/stream/types.ts +18 -0
- package/src/activities/chat/tools/tool-calls.ts +225 -87
- package/src/activities/generateImage/index.ts +1 -1
- package/src/activities/generateSpeech/index.ts +1 -1
- package/src/activities/generateTranscription/index.ts +1 -1
- package/src/activities/generateVideo/index.ts +1 -1
- package/src/activities/summarize/index.ts +1 -1
- package/src/index.ts +41 -2
- package/src/middlewares/content-guard.ts +285 -0
- package/src/middlewares/index.ts +13 -0
- package/src/middlewares/tool-cache.ts +6 -0
- package/src/realtime/index.ts +38 -0
- package/src/realtime/types.ts +294 -0
- package/src/types.ts +19 -2
- package/dist/esm/event-client.d.ts +0 -394
- package/dist/esm/event-client.js +0 -13
- package/dist/esm/event-client.js.map +0 -1
- package/src/event-client.ts +0 -497
|
@@ -5,13 +5,11 @@ import { ImmediateStrategy } from "./strategies.js";
|
|
|
5
5
|
class StreamProcessor {
|
|
6
6
|
constructor(options = {}) {
|
|
7
7
|
this.messages = [];
|
|
8
|
-
this.
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
11
|
-
this.
|
|
12
|
-
this.
|
|
13
|
-
this.toolCalls = /* @__PURE__ */ new Map();
|
|
14
|
-
this.toolCallOrder = [];
|
|
8
|
+
this.messageStates = /* @__PURE__ */ new Map();
|
|
9
|
+
this.activeMessageIds = /* @__PURE__ */ new Set();
|
|
10
|
+
this.toolCallToMessage = /* @__PURE__ */ new Map();
|
|
11
|
+
this.pendingManualMessageId = null;
|
|
12
|
+
this.activeRuns = /* @__PURE__ */ new Set();
|
|
15
13
|
this.finishReason = null;
|
|
16
14
|
this.hasError = false;
|
|
17
15
|
this.isDone = false;
|
|
@@ -81,15 +79,16 @@ class StreamProcessor {
|
|
|
81
79
|
*/
|
|
82
80
|
prepareAssistantMessage() {
|
|
83
81
|
this.resetStreamState();
|
|
84
|
-
this.currentAssistantMessageId = null;
|
|
85
82
|
}
|
|
86
83
|
/**
|
|
87
84
|
* @deprecated Use prepareAssistantMessage() instead. This eagerly creates
|
|
88
85
|
* an assistant message which can cause empty message flicker.
|
|
89
86
|
*/
|
|
90
|
-
startAssistantMessage() {
|
|
87
|
+
startAssistantMessage(messageId) {
|
|
91
88
|
this.prepareAssistantMessage();
|
|
92
|
-
|
|
89
|
+
const { messageId: id } = this.ensureAssistantMessage(messageId);
|
|
90
|
+
this.pendingManualMessageId = id;
|
|
91
|
+
return id;
|
|
93
92
|
}
|
|
94
93
|
/**
|
|
95
94
|
* Get the current assistant message ID (if one has been created).
|
|
@@ -97,33 +96,13 @@ class StreamProcessor {
|
|
|
97
96
|
* has arrived yet.
|
|
98
97
|
*/
|
|
99
98
|
getCurrentAssistantMessageId() {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
* Returns the message ID.
|
|
106
|
-
*
|
|
107
|
-
* Content-bearing chunks that trigger this:
|
|
108
|
-
* TEXT_MESSAGE_CONTENT, TOOL_CALL_START, STEP_FINISHED, RUN_ERROR.
|
|
109
|
-
*
|
|
110
|
-
* @see docs/chat-architecture.md#streamprocessor-internal-state — Lazy creation pattern
|
|
111
|
-
*/
|
|
112
|
-
ensureAssistantMessage() {
|
|
113
|
-
if (this.currentAssistantMessageId) {
|
|
114
|
-
return this.currentAssistantMessageId;
|
|
99
|
+
let lastId = null;
|
|
100
|
+
for (const [id, state] of this.messageStates) {
|
|
101
|
+
if (state.role === "assistant") {
|
|
102
|
+
lastId = id;
|
|
103
|
+
}
|
|
115
104
|
}
|
|
116
|
-
|
|
117
|
-
id: generateMessageId(),
|
|
118
|
-
role: "assistant",
|
|
119
|
-
parts: [],
|
|
120
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
121
|
-
};
|
|
122
|
-
this.currentAssistantMessageId = assistantMessage.id;
|
|
123
|
-
this.messages = [...this.messages, assistantMessage];
|
|
124
|
-
this.events.onStreamStart?.();
|
|
125
|
-
this.emitMessagesChange();
|
|
126
|
-
return assistantMessage.id;
|
|
105
|
+
return lastId;
|
|
127
106
|
}
|
|
128
107
|
/**
|
|
129
108
|
* Add a tool result (called by client after handling onToolCall)
|
|
@@ -219,7 +198,10 @@ class StreamProcessor {
|
|
|
219
198
|
*/
|
|
220
199
|
clearMessages() {
|
|
221
200
|
this.messages = [];
|
|
222
|
-
this.
|
|
201
|
+
this.messageStates.clear();
|
|
202
|
+
this.activeMessageIds.clear();
|
|
203
|
+
this.toolCallToMessage.clear();
|
|
204
|
+
this.pendingManualMessageId = null;
|
|
223
205
|
this.emitMessagesChange();
|
|
224
206
|
}
|
|
225
207
|
// ============================================
|
|
@@ -247,7 +229,7 @@ class StreamProcessor {
|
|
|
247
229
|
*
|
|
248
230
|
* Central dispatch for all AG-UI events. Each event type maps to a specific
|
|
249
231
|
* handler. Events not listed in the switch are intentionally ignored
|
|
250
|
-
* (RUN_STARTED,
|
|
232
|
+
* (RUN_STARTED, STEP_STARTED, STATE_DELTA).
|
|
251
233
|
*
|
|
252
234
|
* @see docs/chat-architecture.md#adapter-contract — Expected event types and ordering
|
|
253
235
|
*/
|
|
@@ -262,11 +244,14 @@ class StreamProcessor {
|
|
|
262
244
|
switch (chunk.type) {
|
|
263
245
|
// AG-UI Events
|
|
264
246
|
case "TEXT_MESSAGE_START":
|
|
265
|
-
this.handleTextMessageStartEvent();
|
|
247
|
+
this.handleTextMessageStartEvent(chunk);
|
|
266
248
|
break;
|
|
267
249
|
case "TEXT_MESSAGE_CONTENT":
|
|
268
250
|
this.handleTextMessageContentEvent(chunk);
|
|
269
251
|
break;
|
|
252
|
+
case "TEXT_MESSAGE_END":
|
|
253
|
+
this.handleTextMessageEndEvent(chunk);
|
|
254
|
+
break;
|
|
270
255
|
case "TOOL_CALL_START":
|
|
271
256
|
this.handleToolCallStartEvent(chunk);
|
|
272
257
|
break;
|
|
@@ -285,28 +270,190 @@ class StreamProcessor {
|
|
|
285
270
|
case "STEP_FINISHED":
|
|
286
271
|
this.handleStepFinishedEvent(chunk);
|
|
287
272
|
break;
|
|
273
|
+
case "MESSAGES_SNAPSHOT":
|
|
274
|
+
this.handleMessagesSnapshotEvent(chunk);
|
|
275
|
+
break;
|
|
288
276
|
case "CUSTOM":
|
|
289
277
|
this.handleCustomEvent(chunk);
|
|
290
278
|
break;
|
|
279
|
+
case "RUN_STARTED":
|
|
280
|
+
this.handleRunStartedEvent(chunk);
|
|
281
|
+
break;
|
|
291
282
|
}
|
|
292
283
|
}
|
|
284
|
+
// ============================================
|
|
285
|
+
// Per-Message State Helpers
|
|
286
|
+
// ============================================
|
|
293
287
|
/**
|
|
294
|
-
*
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
288
|
+
* Create a new MessageStreamState for a message
|
|
289
|
+
*/
|
|
290
|
+
createMessageState(messageId, role) {
|
|
291
|
+
const state = {
|
|
292
|
+
id: messageId,
|
|
293
|
+
role,
|
|
294
|
+
totalTextContent: "",
|
|
295
|
+
currentSegmentText: "",
|
|
296
|
+
lastEmittedText: "",
|
|
297
|
+
thinkingContent: "",
|
|
298
|
+
toolCalls: /* @__PURE__ */ new Map(),
|
|
299
|
+
toolCallOrder: [],
|
|
300
|
+
hasToolCallsSinceTextStart: false,
|
|
301
|
+
isComplete: false
|
|
302
|
+
};
|
|
303
|
+
this.messageStates.set(messageId, state);
|
|
304
|
+
return state;
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Get the MessageStreamState for a message
|
|
308
|
+
*/
|
|
309
|
+
getMessageState(messageId) {
|
|
310
|
+
return this.messageStates.get(messageId);
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Get the most recent active assistant message ID.
|
|
314
|
+
* Used as fallback for events that don't include a messageId.
|
|
315
|
+
*/
|
|
316
|
+
getActiveAssistantMessageId() {
|
|
317
|
+
const ids = Array.from(this.activeMessageIds);
|
|
318
|
+
for (let i = ids.length - 1; i >= 0; i--) {
|
|
319
|
+
const id = ids[i];
|
|
320
|
+
const state = this.messageStates.get(id);
|
|
321
|
+
if (state && state.role === "assistant") {
|
|
322
|
+
return id;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Ensure an active assistant message exists, creating one if needed.
|
|
329
|
+
* Used for backward compat when events arrive without prior TEXT_MESSAGE_START.
|
|
300
330
|
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
331
|
+
* On reconnect/resume, a TEXT_MESSAGE_CONTENT may arrive for a message that
|
|
332
|
+
* already exists in this.messages (e.g. from initialMessages or a prior
|
|
333
|
+
* MESSAGES_SNAPSHOT) but whose transient state was cleared. In that case we
|
|
334
|
+
* hydrate state from the existing message rather than creating a duplicate.
|
|
303
335
|
*/
|
|
304
|
-
|
|
305
|
-
if (
|
|
306
|
-
this.
|
|
336
|
+
ensureAssistantMessage(preferredId) {
|
|
337
|
+
if (preferredId) {
|
|
338
|
+
const state2 = this.getMessageState(preferredId);
|
|
339
|
+
if (state2) return { messageId: preferredId, state: state2 };
|
|
307
340
|
}
|
|
308
|
-
|
|
309
|
-
|
|
341
|
+
const activeId = this.getActiveAssistantMessageId();
|
|
342
|
+
if (activeId) {
|
|
343
|
+
const state2 = this.getMessageState(activeId);
|
|
344
|
+
return { messageId: activeId, state: state2 };
|
|
345
|
+
}
|
|
346
|
+
if (preferredId) {
|
|
347
|
+
const existingMsg = this.messages.find((m) => m.id === preferredId);
|
|
348
|
+
if (existingMsg) {
|
|
349
|
+
const state2 = this.createMessageState(preferredId, existingMsg.role);
|
|
350
|
+
this.activeMessageIds.add(preferredId);
|
|
351
|
+
const lastPart = existingMsg.parts.length > 0 ? existingMsg.parts[existingMsg.parts.length - 1] : null;
|
|
352
|
+
if (lastPart && lastPart.type === "text") {
|
|
353
|
+
state2.currentSegmentText = lastPart.content;
|
|
354
|
+
state2.lastEmittedText = lastPart.content;
|
|
355
|
+
state2.totalTextContent = lastPart.content;
|
|
356
|
+
}
|
|
357
|
+
return { messageId: preferredId, state: state2 };
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
const id = preferredId || generateMessageId();
|
|
361
|
+
const assistantMessage = {
|
|
362
|
+
id,
|
|
363
|
+
role: "assistant",
|
|
364
|
+
parts: [],
|
|
365
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
366
|
+
};
|
|
367
|
+
this.messages = [...this.messages, assistantMessage];
|
|
368
|
+
const state = this.createMessageState(id, "assistant");
|
|
369
|
+
this.activeMessageIds.add(id);
|
|
370
|
+
this.pendingManualMessageId = id;
|
|
371
|
+
this.events.onStreamStart?.();
|
|
372
|
+
this.emitMessagesChange();
|
|
373
|
+
return { messageId: id, state };
|
|
374
|
+
}
|
|
375
|
+
// ============================================
|
|
376
|
+
// Event Handlers
|
|
377
|
+
// ============================================
|
|
378
|
+
/**
|
|
379
|
+
* Handle TEXT_MESSAGE_START event
|
|
380
|
+
*/
|
|
381
|
+
handleTextMessageStartEvent(chunk) {
|
|
382
|
+
const { messageId, role } = chunk;
|
|
383
|
+
const uiRole = role === "tool" ? "assistant" : role;
|
|
384
|
+
if (this.pendingManualMessageId) {
|
|
385
|
+
const pendingId = this.pendingManualMessageId;
|
|
386
|
+
this.pendingManualMessageId = null;
|
|
387
|
+
if (pendingId !== messageId) {
|
|
388
|
+
this.messages = this.messages.map(
|
|
389
|
+
(msg) => msg.id === pendingId ? { ...msg, id: messageId } : msg
|
|
390
|
+
);
|
|
391
|
+
const existingState = this.messageStates.get(pendingId);
|
|
392
|
+
if (existingState) {
|
|
393
|
+
existingState.id = messageId;
|
|
394
|
+
this.messageStates.delete(pendingId);
|
|
395
|
+
this.messageStates.set(messageId, existingState);
|
|
396
|
+
}
|
|
397
|
+
this.activeMessageIds.delete(pendingId);
|
|
398
|
+
this.activeMessageIds.add(messageId);
|
|
399
|
+
}
|
|
400
|
+
if (!this.messageStates.has(messageId)) {
|
|
401
|
+
this.createMessageState(messageId, uiRole);
|
|
402
|
+
this.activeMessageIds.add(messageId);
|
|
403
|
+
}
|
|
404
|
+
this.emitMessagesChange();
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
const existingMsg = this.messages.find((m) => m.id === messageId);
|
|
408
|
+
if (existingMsg) {
|
|
409
|
+
this.activeMessageIds.add(messageId);
|
|
410
|
+
if (!this.messageStates.has(messageId)) {
|
|
411
|
+
this.createMessageState(messageId, uiRole);
|
|
412
|
+
} else {
|
|
413
|
+
const existingState = this.messageStates.get(messageId);
|
|
414
|
+
if (existingState.hasToolCallsSinceTextStart) {
|
|
415
|
+
if (existingState.currentSegmentText !== existingState.lastEmittedText) {
|
|
416
|
+
this.emitTextUpdateForMessage(messageId);
|
|
417
|
+
}
|
|
418
|
+
existingState.currentSegmentText = "";
|
|
419
|
+
existingState.lastEmittedText = "";
|
|
420
|
+
existingState.hasToolCallsSinceTextStart = false;
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
return;
|
|
424
|
+
}
|
|
425
|
+
const newMessage = {
|
|
426
|
+
id: messageId,
|
|
427
|
+
role: uiRole,
|
|
428
|
+
parts: [],
|
|
429
|
+
createdAt: /* @__PURE__ */ new Date()
|
|
430
|
+
};
|
|
431
|
+
this.messages = [...this.messages, newMessage];
|
|
432
|
+
this.createMessageState(messageId, uiRole);
|
|
433
|
+
this.activeMessageIds.add(messageId);
|
|
434
|
+
this.events.onStreamStart?.();
|
|
435
|
+
this.emitMessagesChange();
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Handle TEXT_MESSAGE_END event
|
|
439
|
+
*/
|
|
440
|
+
handleTextMessageEndEvent(chunk) {
|
|
441
|
+
const { messageId } = chunk;
|
|
442
|
+
const state = this.getMessageState(messageId);
|
|
443
|
+
if (!state) return;
|
|
444
|
+
if (state.isComplete) return;
|
|
445
|
+
if (state.currentSegmentText !== state.lastEmittedText) {
|
|
446
|
+
this.emitTextUpdateForMessage(messageId);
|
|
447
|
+
}
|
|
448
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
449
|
+
}
|
|
450
|
+
/**
|
|
451
|
+
* Handle MESSAGES_SNAPSHOT event
|
|
452
|
+
*/
|
|
453
|
+
handleMessagesSnapshotEvent(chunk) {
|
|
454
|
+
this.resetStreamState();
|
|
455
|
+
this.messages = [...chunk.messages];
|
|
456
|
+
this.emitMessagesChange();
|
|
310
457
|
}
|
|
311
458
|
/**
|
|
312
459
|
* Handle TEXT_MESSAGE_CONTENT event.
|
|
@@ -320,15 +467,42 @@ class StreamProcessor {
|
|
|
320
467
|
* @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
|
|
321
468
|
*/
|
|
322
469
|
handleTextMessageContentEvent(chunk) {
|
|
323
|
-
this.ensureAssistantMessage();
|
|
324
|
-
this.
|
|
325
|
-
|
|
470
|
+
const { messageId, state } = this.ensureAssistantMessage(chunk.messageId);
|
|
471
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
472
|
+
const previousSegment = state.currentSegmentText;
|
|
473
|
+
const isNewSegment = state.hasToolCallsSinceTextStart && previousSegment.length > 0 && this.isNewTextSegment(chunk, previousSegment);
|
|
474
|
+
if (isNewSegment) {
|
|
475
|
+
if (previousSegment !== state.lastEmittedText) {
|
|
476
|
+
this.emitTextUpdateForMessage(messageId);
|
|
477
|
+
}
|
|
478
|
+
state.currentSegmentText = "";
|
|
479
|
+
state.lastEmittedText = "";
|
|
480
|
+
state.hasToolCallsSinceTextStart = false;
|
|
481
|
+
}
|
|
482
|
+
const currentText = state.currentSegmentText;
|
|
483
|
+
let nextText = currentText;
|
|
484
|
+
const delta = chunk.delta || "";
|
|
485
|
+
if (delta !== "") {
|
|
486
|
+
nextText = currentText + delta;
|
|
487
|
+
} else if (chunk.content !== void 0 && chunk.content !== "") {
|
|
488
|
+
if (chunk.content.startsWith(currentText)) {
|
|
489
|
+
nextText = chunk.content;
|
|
490
|
+
} else if (currentText.startsWith(chunk.content)) {
|
|
491
|
+
nextText = currentText;
|
|
492
|
+
} else {
|
|
493
|
+
nextText = currentText + chunk.content;
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
const textDelta = nextText.slice(currentText.length);
|
|
497
|
+
state.currentSegmentText = nextText;
|
|
498
|
+
state.totalTextContent += textDelta;
|
|
499
|
+
const chunkPortion = chunk.delta || chunk.content || "";
|
|
326
500
|
const shouldEmit = this.chunkStrategy.shouldEmit(
|
|
327
|
-
|
|
328
|
-
|
|
501
|
+
chunkPortion,
|
|
502
|
+
state.currentSegmentText
|
|
329
503
|
);
|
|
330
|
-
if (shouldEmit &&
|
|
331
|
-
this.
|
|
504
|
+
if (shouldEmit && state.currentSegmentText !== state.lastEmittedText) {
|
|
505
|
+
this.emitTextUpdateForMessage(messageId);
|
|
332
506
|
}
|
|
333
507
|
}
|
|
334
508
|
/**
|
|
@@ -345,9 +519,13 @@ class StreamProcessor {
|
|
|
345
519
|
* @see docs/chat-architecture.md#adapter-contract — Ordering requirements
|
|
346
520
|
*/
|
|
347
521
|
handleToolCallStartEvent(chunk) {
|
|
348
|
-
this.
|
|
522
|
+
const targetMessageId = chunk.parentMessageId ?? this.getActiveAssistantMessageId();
|
|
523
|
+
const { messageId, state } = this.ensureAssistantMessage(
|
|
524
|
+
targetMessageId ?? void 0
|
|
525
|
+
);
|
|
526
|
+
state.hasToolCallsSinceTextStart = true;
|
|
349
527
|
const toolCallId = chunk.toolCallId;
|
|
350
|
-
const existingToolCall =
|
|
528
|
+
const existingToolCall = state.toolCalls.get(toolCallId);
|
|
351
529
|
if (!existingToolCall) {
|
|
352
530
|
const initialState = "awaiting-input";
|
|
353
531
|
const newToolCall = {
|
|
@@ -356,29 +534,24 @@ class StreamProcessor {
|
|
|
356
534
|
arguments: "",
|
|
357
535
|
state: initialState,
|
|
358
536
|
parsedArguments: void 0,
|
|
359
|
-
index: chunk.index ??
|
|
537
|
+
index: chunk.index ?? state.toolCalls.size
|
|
360
538
|
};
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
chunk.toolCallId,
|
|
378
|
-
initialState,
|
|
379
|
-
""
|
|
380
|
-
);
|
|
381
|
-
}
|
|
539
|
+
state.toolCalls.set(toolCallId, newToolCall);
|
|
540
|
+
state.toolCallOrder.push(toolCallId);
|
|
541
|
+
this.toolCallToMessage.set(toolCallId, messageId);
|
|
542
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
543
|
+
id: chunk.toolCallId,
|
|
544
|
+
name: chunk.toolName,
|
|
545
|
+
arguments: "",
|
|
546
|
+
state: initialState
|
|
547
|
+
});
|
|
548
|
+
this.emitMessagesChange();
|
|
549
|
+
this.events.onToolCallStateChange?.(
|
|
550
|
+
messageId,
|
|
551
|
+
chunk.toolCallId,
|
|
552
|
+
initialState,
|
|
553
|
+
""
|
|
554
|
+
);
|
|
382
555
|
}
|
|
383
556
|
}
|
|
384
557
|
/**
|
|
@@ -395,36 +568,33 @@ class StreamProcessor {
|
|
|
395
568
|
*/
|
|
396
569
|
handleToolCallArgsEvent(chunk) {
|
|
397
570
|
const toolCallId = chunk.toolCallId;
|
|
398
|
-
const
|
|
399
|
-
if (
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
if (this.currentAssistantMessageId) {
|
|
409
|
-
this.messages = updateToolCallPart(
|
|
410
|
-
this.messages,
|
|
411
|
-
this.currentAssistantMessageId,
|
|
412
|
-
{
|
|
413
|
-
id: existingToolCall.id,
|
|
414
|
-
name: existingToolCall.name,
|
|
415
|
-
arguments: existingToolCall.arguments,
|
|
416
|
-
state: existingToolCall.state
|
|
417
|
-
}
|
|
418
|
-
);
|
|
419
|
-
this.emitMessagesChange();
|
|
420
|
-
this.events.onToolCallStateChange?.(
|
|
421
|
-
this.currentAssistantMessageId,
|
|
422
|
-
existingToolCall.id,
|
|
423
|
-
existingToolCall.state,
|
|
424
|
-
existingToolCall.arguments
|
|
425
|
-
);
|
|
426
|
-
}
|
|
571
|
+
const messageId = this.toolCallToMessage.get(toolCallId);
|
|
572
|
+
if (!messageId) return;
|
|
573
|
+
const state = this.getMessageState(messageId);
|
|
574
|
+
if (!state) return;
|
|
575
|
+
const existingToolCall = state.toolCalls.get(toolCallId);
|
|
576
|
+
if (!existingToolCall) return;
|
|
577
|
+
const wasAwaitingInput = existingToolCall.state === "awaiting-input";
|
|
578
|
+
existingToolCall.arguments += chunk.delta || "";
|
|
579
|
+
if (wasAwaitingInput && chunk.delta) {
|
|
580
|
+
existingToolCall.state = "input-streaming";
|
|
427
581
|
}
|
|
582
|
+
existingToolCall.parsedArguments = this.jsonParser.parse(
|
|
583
|
+
existingToolCall.arguments
|
|
584
|
+
);
|
|
585
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
586
|
+
id: existingToolCall.id,
|
|
587
|
+
name: existingToolCall.name,
|
|
588
|
+
arguments: existingToolCall.arguments,
|
|
589
|
+
state: existingToolCall.state
|
|
590
|
+
});
|
|
591
|
+
this.emitMessagesChange();
|
|
592
|
+
this.events.onToolCallStateChange?.(
|
|
593
|
+
messageId,
|
|
594
|
+
existingToolCall.id,
|
|
595
|
+
existingToolCall.state,
|
|
596
|
+
existingToolCall.arguments
|
|
597
|
+
);
|
|
428
598
|
}
|
|
429
599
|
/**
|
|
430
600
|
* Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.
|
|
@@ -441,16 +611,19 @@ class StreamProcessor {
|
|
|
441
611
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
442
612
|
*/
|
|
443
613
|
handleToolCallEndEvent(chunk) {
|
|
444
|
-
const
|
|
614
|
+
const messageId = this.toolCallToMessage.get(chunk.toolCallId);
|
|
615
|
+
if (!messageId) return;
|
|
616
|
+
const msgState = this.getMessageState(messageId);
|
|
617
|
+
if (!msgState) return;
|
|
618
|
+
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
|
|
445
619
|
if (existingToolCall && existingToolCall.state !== "input-complete") {
|
|
446
|
-
const index =
|
|
447
|
-
this.completeToolCall(index, existingToolCall);
|
|
620
|
+
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
|
|
621
|
+
this.completeToolCall(messageId, index, existingToolCall);
|
|
448
622
|
if (chunk.input !== void 0) {
|
|
449
623
|
existingToolCall.parsedArguments = chunk.input;
|
|
450
624
|
}
|
|
451
625
|
}
|
|
452
|
-
if (
|
|
453
|
-
const state = "complete";
|
|
626
|
+
if (chunk.result) {
|
|
454
627
|
let output;
|
|
455
628
|
try {
|
|
456
629
|
output = JSON.parse(chunk.result);
|
|
@@ -462,36 +635,55 @@ class StreamProcessor {
|
|
|
462
635
|
chunk.toolCallId,
|
|
463
636
|
output
|
|
464
637
|
);
|
|
638
|
+
const resultState = "complete";
|
|
465
639
|
this.messages = updateToolResultPart(
|
|
466
640
|
this.messages,
|
|
467
|
-
|
|
641
|
+
messageId,
|
|
468
642
|
chunk.toolCallId,
|
|
469
643
|
chunk.result,
|
|
470
|
-
|
|
644
|
+
resultState
|
|
471
645
|
);
|
|
472
646
|
this.emitMessagesChange();
|
|
473
647
|
}
|
|
474
648
|
}
|
|
649
|
+
/**
|
|
650
|
+
* Handle RUN_STARTED event.
|
|
651
|
+
*
|
|
652
|
+
* Registers the run so that RUN_FINISHED can determine whether other
|
|
653
|
+
* runs are still active before finalizing.
|
|
654
|
+
*/
|
|
655
|
+
handleRunStartedEvent(chunk) {
|
|
656
|
+
this.activeRuns.add(chunk.runId);
|
|
657
|
+
}
|
|
475
658
|
/**
|
|
476
659
|
* Handle RUN_FINISHED event.
|
|
477
660
|
*
|
|
478
|
-
* Records the finishReason and
|
|
479
|
-
*
|
|
480
|
-
*
|
|
661
|
+
* Records the finishReason and removes the run from activeRuns.
|
|
662
|
+
* Only finalizes when no more runs are active, so that concurrent
|
|
663
|
+
* runs don't interfere with each other.
|
|
481
664
|
*
|
|
482
665
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — finishReason semantics
|
|
483
666
|
* @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory
|
|
484
667
|
*/
|
|
485
668
|
handleRunFinishedEvent(chunk) {
|
|
486
669
|
this.finishReason = chunk.finishReason;
|
|
487
|
-
this.
|
|
488
|
-
this.
|
|
670
|
+
this.activeRuns.delete(chunk.runId);
|
|
671
|
+
if (this.activeRuns.size === 0) {
|
|
672
|
+
this.isDone = true;
|
|
673
|
+
this.completeAllToolCalls();
|
|
674
|
+
this.finalizeStream();
|
|
675
|
+
}
|
|
489
676
|
}
|
|
490
677
|
/**
|
|
491
678
|
* Handle RUN_ERROR event
|
|
492
679
|
*/
|
|
493
680
|
handleRunErrorEvent(chunk) {
|
|
494
681
|
this.hasError = true;
|
|
682
|
+
if (chunk.runId) {
|
|
683
|
+
this.activeRuns.delete(chunk.runId);
|
|
684
|
+
} else {
|
|
685
|
+
this.activeRuns.clear();
|
|
686
|
+
}
|
|
495
687
|
this.ensureAssistantMessage();
|
|
496
688
|
this.events.onError?.(new Error(chunk.error.message || "An error occurred"));
|
|
497
689
|
}
|
|
@@ -504,20 +696,30 @@ class StreamProcessor {
|
|
|
504
696
|
* @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow
|
|
505
697
|
*/
|
|
506
698
|
handleStepFinishedEvent(chunk) {
|
|
507
|
-
this.ensureAssistantMessage(
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
699
|
+
const { messageId, state } = this.ensureAssistantMessage(
|
|
700
|
+
this.getActiveAssistantMessageId() ?? void 0
|
|
701
|
+
);
|
|
702
|
+
const previous = state.thinkingContent;
|
|
703
|
+
let nextThinking = previous;
|
|
704
|
+
if (chunk.delta && chunk.delta !== "") {
|
|
705
|
+
nextThinking = previous + chunk.delta;
|
|
706
|
+
} else if (chunk.content && chunk.content !== "") {
|
|
707
|
+
if (chunk.content.startsWith(previous)) {
|
|
708
|
+
nextThinking = chunk.content;
|
|
709
|
+
} else if (previous.startsWith(chunk.content)) {
|
|
710
|
+
nextThinking = previous;
|
|
711
|
+
} else {
|
|
712
|
+
nextThinking = previous + chunk.content;
|
|
713
|
+
}
|
|
520
714
|
}
|
|
715
|
+
state.thinkingContent = nextThinking;
|
|
716
|
+
this.messages = updateThinkingPart(
|
|
717
|
+
this.messages,
|
|
718
|
+
messageId,
|
|
719
|
+
state.thinkingContent
|
|
720
|
+
);
|
|
721
|
+
this.emitMessagesChange();
|
|
722
|
+
this.events.onThinkingUpdate?.(messageId, state.thinkingContent);
|
|
521
723
|
}
|
|
522
724
|
/**
|
|
523
725
|
* Handle CUSTOM event.
|
|
@@ -530,6 +732,7 @@ class StreamProcessor {
|
|
|
530
732
|
* @see docs/chat-architecture.md#client-tools-and-approval-flows — Full flow details
|
|
531
733
|
*/
|
|
532
734
|
handleCustomEvent(chunk) {
|
|
735
|
+
const messageId = this.getActiveAssistantMessageId();
|
|
533
736
|
if (chunk.name === "tool-input-available" && chunk.value) {
|
|
534
737
|
const { toolCallId, toolName, input } = chunk.value;
|
|
535
738
|
this.events.onToolCall?.({
|
|
@@ -541,10 +744,11 @@ class StreamProcessor {
|
|
|
541
744
|
}
|
|
542
745
|
if (chunk.name === "approval-requested" && chunk.value) {
|
|
543
746
|
const { toolCallId, toolName, input, approval } = chunk.value;
|
|
544
|
-
|
|
747
|
+
const resolvedMessageId = messageId ?? this.toolCallToMessage.get(toolCallId);
|
|
748
|
+
if (resolvedMessageId) {
|
|
545
749
|
this.messages = updateToolCallApproval(
|
|
546
750
|
this.messages,
|
|
547
|
-
|
|
751
|
+
resolvedMessageId,
|
|
548
752
|
toolCallId,
|
|
549
753
|
approval.id
|
|
550
754
|
);
|
|
@@ -563,8 +767,25 @@ class StreamProcessor {
|
|
|
563
767
|
this.events.onCustomEvent(chunk.name, chunk.value, { toolCallId });
|
|
564
768
|
}
|
|
565
769
|
}
|
|
770
|
+
// ============================================
|
|
771
|
+
// Internal Helpers
|
|
772
|
+
// ============================================
|
|
566
773
|
/**
|
|
567
|
-
*
|
|
774
|
+
* Detect if an incoming content chunk represents a NEW text segment
|
|
775
|
+
*/
|
|
776
|
+
isNewTextSegment(chunk, previous) {
|
|
777
|
+
if (chunk.content !== void 0) {
|
|
778
|
+
if (chunk.content.length < previous.length) {
|
|
779
|
+
return true;
|
|
780
|
+
}
|
|
781
|
+
if (!chunk.content.startsWith(previous) && !previous.startsWith(chunk.content)) {
|
|
782
|
+
return true;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return false;
|
|
786
|
+
}
|
|
787
|
+
/**
|
|
788
|
+
* Complete all tool calls across all active messages — safety net for stream termination.
|
|
568
789
|
*
|
|
569
790
|
* Called by RUN_FINISHED and finalizeStream(). Force-transitions any tool call
|
|
570
791
|
* not yet in input-complete state. Handles cases where TOOL_CALL_END was
|
|
@@ -573,41 +794,45 @@ class StreamProcessor {
|
|
|
573
794
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — Safety net behavior
|
|
574
795
|
*/
|
|
575
796
|
completeAllToolCalls() {
|
|
576
|
-
this.
|
|
797
|
+
for (const messageId of this.activeMessageIds) {
|
|
798
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Complete all tool calls for a specific message
|
|
803
|
+
*/
|
|
804
|
+
completeAllToolCallsForMessage(messageId) {
|
|
805
|
+
const state = this.getMessageState(messageId);
|
|
806
|
+
if (!state) return;
|
|
807
|
+
state.toolCalls.forEach((toolCall, id) => {
|
|
577
808
|
if (toolCall.state !== "input-complete") {
|
|
578
|
-
const index =
|
|
579
|
-
this.completeToolCall(index, toolCall);
|
|
809
|
+
const index = state.toolCallOrder.indexOf(id);
|
|
810
|
+
this.completeToolCall(messageId, index, toolCall);
|
|
580
811
|
}
|
|
581
812
|
});
|
|
582
813
|
}
|
|
583
814
|
/**
|
|
584
815
|
* Mark a tool call as complete and emit event
|
|
585
816
|
*/
|
|
586
|
-
completeToolCall(_index, toolCall) {
|
|
817
|
+
completeToolCall(messageId, _index, toolCall) {
|
|
587
818
|
toolCall.state = "input-complete";
|
|
588
819
|
toolCall.parsedArguments = this.jsonParser.parse(toolCall.arguments);
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
this.currentAssistantMessageId,
|
|
603
|
-
toolCall.id,
|
|
604
|
-
"input-complete",
|
|
605
|
-
toolCall.arguments
|
|
606
|
-
);
|
|
607
|
-
}
|
|
820
|
+
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
821
|
+
id: toolCall.id,
|
|
822
|
+
name: toolCall.name,
|
|
823
|
+
arguments: toolCall.arguments,
|
|
824
|
+
state: "input-complete"
|
|
825
|
+
});
|
|
826
|
+
this.emitMessagesChange();
|
|
827
|
+
this.events.onToolCallStateChange?.(
|
|
828
|
+
messageId,
|
|
829
|
+
toolCall.id,
|
|
830
|
+
"input-complete",
|
|
831
|
+
toolCall.arguments
|
|
832
|
+
);
|
|
608
833
|
}
|
|
609
834
|
/**
|
|
610
|
-
* Emit pending text update.
|
|
835
|
+
* Emit pending text update for a specific message.
|
|
611
836
|
*
|
|
612
837
|
* Calls updateTextPart() which has critical append-vs-replace logic:
|
|
613
838
|
* - If last UIMessage part is TextPart → replaces its content (same segment).
|
|
@@ -615,20 +840,17 @@ class StreamProcessor {
|
|
|
615
840
|
*
|
|
616
841
|
* @see docs/chat-architecture.md#uimessage-part-ordering-invariants — Replace vs. push logic
|
|
617
842
|
*/
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
this.currentSegmentText
|
|
630
|
-
);
|
|
631
|
-
}
|
|
843
|
+
emitTextUpdateForMessage(messageId) {
|
|
844
|
+
const state = this.getMessageState(messageId);
|
|
845
|
+
if (!state) return;
|
|
846
|
+
state.lastEmittedText = state.currentSegmentText;
|
|
847
|
+
this.messages = updateTextPart(
|
|
848
|
+
this.messages,
|
|
849
|
+
messageId,
|
|
850
|
+
state.currentSegmentText
|
|
851
|
+
);
|
|
852
|
+
this.emitMessagesChange();
|
|
853
|
+
this.events.onTextUpdate?.(messageId, state.currentSegmentText);
|
|
632
854
|
}
|
|
633
855
|
/**
|
|
634
856
|
* Emit messages change event
|
|
@@ -646,66 +868,94 @@ class StreamProcessor {
|
|
|
646
868
|
* @see docs/chat-architecture.md#single-shot-text-response — Finalization step
|
|
647
869
|
*/
|
|
648
870
|
finalizeStream() {
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
this.
|
|
871
|
+
let lastAssistantMessage;
|
|
872
|
+
for (const messageId of this.activeMessageIds) {
|
|
873
|
+
const state = this.getMessageState(messageId);
|
|
874
|
+
if (!state) continue;
|
|
875
|
+
this.completeAllToolCallsForMessage(messageId);
|
|
876
|
+
if (state.currentSegmentText !== state.lastEmittedText) {
|
|
877
|
+
this.emitTextUpdateForMessage(messageId);
|
|
878
|
+
}
|
|
879
|
+
state.isComplete = true;
|
|
880
|
+
const msg = this.messages.find((m) => m.id === messageId);
|
|
881
|
+
if (msg && msg.role === "assistant") {
|
|
882
|
+
lastAssistantMessage = msg;
|
|
883
|
+
}
|
|
652
884
|
}
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
);
|
|
657
|
-
if (assistantMessage && this.isWhitespaceOnlyMessage(assistantMessage)) {
|
|
885
|
+
this.activeMessageIds.clear();
|
|
886
|
+
if (lastAssistantMessage && !this.hasError) {
|
|
887
|
+
if (this.isWhitespaceOnlyMessage(lastAssistantMessage)) {
|
|
658
888
|
this.messages = this.messages.filter(
|
|
659
|
-
(m) => m.id !==
|
|
889
|
+
(m) => m.id !== lastAssistantMessage.id
|
|
660
890
|
);
|
|
661
891
|
this.emitMessagesChange();
|
|
662
|
-
this.currentAssistantMessageId = null;
|
|
663
892
|
return;
|
|
664
893
|
}
|
|
665
894
|
}
|
|
666
|
-
if (
|
|
667
|
-
|
|
668
|
-
(m) => m.id === this.currentAssistantMessageId
|
|
669
|
-
);
|
|
670
|
-
if (assistantMessage) {
|
|
671
|
-
this.events.onStreamEnd?.(assistantMessage);
|
|
672
|
-
}
|
|
895
|
+
if (lastAssistantMessage) {
|
|
896
|
+
this.events.onStreamEnd?.(lastAssistantMessage);
|
|
673
897
|
}
|
|
674
898
|
}
|
|
675
899
|
/**
|
|
676
|
-
* Get completed tool calls in API format
|
|
900
|
+
* Get completed tool calls in API format (aggregated across all messages)
|
|
677
901
|
*/
|
|
678
902
|
getCompletedToolCalls() {
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
903
|
+
const result = [];
|
|
904
|
+
for (const state of this.messageStates.values()) {
|
|
905
|
+
for (const tc of state.toolCalls.values()) {
|
|
906
|
+
if (tc.state === "input-complete") {
|
|
907
|
+
result.push({
|
|
908
|
+
id: tc.id,
|
|
909
|
+
type: "function",
|
|
910
|
+
function: {
|
|
911
|
+
name: tc.name,
|
|
912
|
+
arguments: tc.arguments
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
}
|
|
685
916
|
}
|
|
686
|
-
}
|
|
917
|
+
}
|
|
918
|
+
return result;
|
|
687
919
|
}
|
|
688
920
|
/**
|
|
689
|
-
* Get current result
|
|
921
|
+
* Get current result (aggregated across all messages)
|
|
690
922
|
*/
|
|
691
923
|
getResult() {
|
|
692
924
|
const toolCalls = this.getCompletedToolCalls();
|
|
925
|
+
let content = "";
|
|
926
|
+
let thinking = "";
|
|
927
|
+
for (const state of this.messageStates.values()) {
|
|
928
|
+
content += state.totalTextContent;
|
|
929
|
+
thinking += state.thinkingContent;
|
|
930
|
+
}
|
|
693
931
|
return {
|
|
694
|
-
content
|
|
695
|
-
thinking:
|
|
932
|
+
content,
|
|
933
|
+
thinking: thinking || void 0,
|
|
696
934
|
toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
|
|
697
935
|
finishReason: this.finishReason
|
|
698
936
|
};
|
|
699
937
|
}
|
|
700
938
|
/**
|
|
701
|
-
* Get current processor state
|
|
939
|
+
* Get current processor state (aggregated across all messages)
|
|
702
940
|
*/
|
|
703
941
|
getState() {
|
|
942
|
+
let content = "";
|
|
943
|
+
let thinking = "";
|
|
944
|
+
const toolCalls = /* @__PURE__ */ new Map();
|
|
945
|
+
const toolCallOrder = [];
|
|
946
|
+
for (const state of this.messageStates.values()) {
|
|
947
|
+
content += state.totalTextContent;
|
|
948
|
+
thinking += state.thinkingContent;
|
|
949
|
+
for (const [id, tc] of state.toolCalls) {
|
|
950
|
+
toolCalls.set(id, tc);
|
|
951
|
+
}
|
|
952
|
+
toolCallOrder.push(...state.toolCallOrder);
|
|
953
|
+
}
|
|
704
954
|
return {
|
|
705
|
-
content
|
|
706
|
-
thinking
|
|
707
|
-
toolCalls
|
|
708
|
-
toolCallOrder
|
|
955
|
+
content,
|
|
956
|
+
thinking,
|
|
957
|
+
toolCalls,
|
|
958
|
+
toolCallOrder,
|
|
709
959
|
finishReason: this.finishReason,
|
|
710
960
|
done: this.isDone
|
|
711
961
|
};
|
|
@@ -732,12 +982,11 @@ class StreamProcessor {
|
|
|
732
982
|
* Reset stream state (but keep messages)
|
|
733
983
|
*/
|
|
734
984
|
resetStreamState() {
|
|
735
|
-
this.
|
|
736
|
-
this.
|
|
737
|
-
this.
|
|
738
|
-
this.
|
|
739
|
-
this.
|
|
740
|
-
this.toolCallOrder = [];
|
|
985
|
+
this.messageStates.clear();
|
|
986
|
+
this.activeMessageIds.clear();
|
|
987
|
+
this.activeRuns.clear();
|
|
988
|
+
this.toolCallToMessage.clear();
|
|
989
|
+
this.pendingManualMessageId = null;
|
|
741
990
|
this.finishReason = null;
|
|
742
991
|
this.hasError = false;
|
|
743
992
|
this.isDone = false;
|
|
@@ -749,7 +998,6 @@ class StreamProcessor {
|
|
|
749
998
|
reset() {
|
|
750
999
|
this.resetStreamState();
|
|
751
1000
|
this.messages = [];
|
|
752
|
-
this.currentAssistantMessageId = null;
|
|
753
1001
|
}
|
|
754
1002
|
/**
|
|
755
1003
|
* Check if a message contains only whitespace text and no other meaningful parts
|