@tanstack/ai 0.47.2 → 0.48.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/esm/activities/chat/adapter.d.ts +5 -4
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +4 -7
- package/dist/esm/activities/chat/index.js +201 -243
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +126 -31
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +38 -17
- package/dist/esm/activities/chat/stream/processor.js +197 -110
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +3 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +15 -10
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +6 -6
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/stream-generation-result.js +7 -8
- package/dist/esm/activities/stream-generation-result.js.map +1 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -1
- package/dist/esm/activities/summarize/chat-stream-summarize.js +59 -54
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/adapter-internals.d.ts +2 -0
- package/dist/esm/adapter-internals.js +3 -1
- package/dist/esm/client.d.ts +8 -1
- package/dist/esm/client.js +7 -2
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +6 -0
- package/dist/esm/index.js +6 -2
- package/dist/esm/middlewares/otel.js +12 -5
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/stream-to-response.js +13 -6
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/stream-to-websocket.js +4 -2
- package/dist/esm/stream-to-websocket.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.d.ts +10 -13
- package/dist/esm/strip-to-spec-middleware.js +24 -22
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/types.d.ts +82 -305
- package/dist/esm/utilities/adapter-yield-chunk.d.ts +31 -0
- package/dist/esm/utilities/ag-ui-usage.d.ts +24 -0
- package/dist/esm/utilities/ag-ui-usage.js +66 -0
- package/dist/esm/utilities/ag-ui-usage.js.map +1 -0
- package/dist/esm/utilities/ag-ui-wire.d.ts +14 -7
- package/dist/esm/utilities/ag-ui-wire.js +71 -30
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/chat-params.d.ts +3 -3
- package/dist/esm/utilities/chat-params.js +10 -30
- package/dist/esm/utilities/chat-params.js.map +1 -1
- package/dist/esm/utilities/chunk-ids.d.ts +5 -0
- package/dist/esm/utilities/chunk-ids.js +25 -0
- package/dist/esm/utilities/chunk-ids.js.map +1 -0
- package/dist/esm/utilities/errors.d.ts +9 -0
- package/dist/esm/utilities/errors.js +13 -1
- package/dist/esm/utilities/errors.js.map +1 -1
- package/dist/esm/utilities/merge-metadata.d.ts +14 -0
- package/dist/esm/utilities/merge-metadata.js +43 -0
- package/dist/esm/utilities/merge-metadata.js.map +1 -0
- package/dist/esm/utilities/normalize-stream-chunk.d.ts +3 -0
- package/dist/esm/utilities/normalize-stream-chunk.js +100 -0
- package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -0
- package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js +16 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -0
- package/dist/esm/utilities/restore-inbound-chunk.d.ts +15 -0
- package/dist/esm/utilities/restore-inbound-chunk.js +43 -0
- package/dist/esm/utilities/restore-inbound-chunk.js.map +1 -0
- package/dist/esm/utilities/spec-event-keys.d.ts +2 -0
- package/dist/esm/utilities/spec-event-keys.js +56 -0
- package/dist/esm/utilities/spec-event-keys.js.map +1 -0
- package/dist/esm/utilities/structured-output-events.d.ts +3 -3
- package/dist/esm/utilities/structured-output-events.js.map +1 -1
- package/package.json +3 -3
- package/src/activities/chat/adapter.ts +4 -4
- package/src/activities/chat/index.ts +398 -400
- package/src/activities/chat/messages.ts +183 -36
- package/src/activities/chat/stream/processor.ts +313 -239
- package/src/activities/chat/tools/tool-calls.ts +23 -22
- package/src/activities/generateVideo/index.ts +7 -6
- package/src/activities/stream-generation-result.ts +8 -12
- package/src/activities/summarize/chat-stream-summarize.ts +98 -70
- package/src/adapter-internals.ts +2 -0
- package/src/client.ts +13 -0
- package/src/index.ts +6 -0
- package/src/middlewares/otel.ts +16 -8
- package/src/stream-to-response.ts +16 -4
- package/src/stream-to-websocket.ts +3 -1
- package/src/strip-to-spec-middleware.ts +48 -24
- package/src/types.ts +109 -393
- package/src/utilities/adapter-yield-chunk.ts +30 -0
- package/src/utilities/ag-ui-usage.test.ts +194 -0
- package/src/utilities/ag-ui-usage.ts +148 -0
- package/src/utilities/ag-ui-wire.ts +149 -31
- package/src/utilities/chat-params.ts +22 -39
- package/src/utilities/chunk-ids.ts +24 -0
- package/src/utilities/errors.ts +23 -0
- package/src/utilities/merge-metadata.test.ts +117 -0
- package/src/utilities/merge-metadata.ts +59 -0
- package/src/utilities/normalize-stream-chunk.test.ts +423 -0
- package/src/utilities/normalize-stream-chunk.ts +186 -0
- package/src/utilities/reasoning-encrypted-value.ts +18 -0
- package/src/utilities/restore-inbound-chunk.test.ts +133 -0
- package/src/utilities/restore-inbound-chunk.ts +72 -0
- package/src/utilities/spec-event-keys.test.ts +34 -0
- package/src/utilities/spec-event-keys.ts +74 -0
- package/src/utilities/structured-output-events.ts +3 -3
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { mergeMetadata, tanstackMetadata } from "../../../utilities/merge-metadata.js";
|
|
2
|
+
import { runErrorEventToError } from "../../../utilities/errors.js";
|
|
1
3
|
import { INTERRUPT_BINDING_METADATA_KEY } from "../../../interrupt-resume.js";
|
|
2
4
|
import { normalizeToolResult } from "../../../utilities/tool-result.js";
|
|
3
5
|
import { isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
|
|
4
6
|
import { aguiSnapshotMessageToUIMessage, generateMessageId, uiMessageToModelMessages } from "../messages.js";
|
|
7
|
+
import { getChunkRunId } from "../../../utilities/chunk-ids.js";
|
|
5
8
|
import { defaultJSONParser } from "./json-parser.js";
|
|
6
9
|
import { appendStructuredOutputDelta, completeStructuredOutputPart, errorStructuredOutputPart, updateTextPart, updateThinkingPart, updateToolCallApproval, updateToolCallApprovalResponse, updateToolCallPart, updateToolCallWithOutput, updateToolResultPart } from "./message-updaters.js";
|
|
7
10
|
import { ImmediateStrategy } from "./strategies.js";
|
|
@@ -89,6 +92,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
89
92
|
*
|
|
90
93
|
* @param content - The message content (string or array of content parts)
|
|
91
94
|
* @param id - Optional custom message ID (generated if not provided)
|
|
95
|
+
* @param metadata - Optional AG-UI metadata bag
|
|
92
96
|
* @returns The created UIMessage
|
|
93
97
|
*
|
|
94
98
|
* @example
|
|
@@ -106,7 +110,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
106
110
|
* processor.addUserMessage('Hello!', 'custom-id-123')
|
|
107
111
|
* ```
|
|
108
112
|
*/
|
|
109
|
-
addUserMessage(content, id) {
|
|
113
|
+
addUserMessage(content, id, metadata) {
|
|
110
114
|
const parts = typeof content === "string" ? [{
|
|
111
115
|
type: "text",
|
|
112
116
|
content
|
|
@@ -117,7 +121,8 @@ var StreamProcessor = class StreamProcessor {
|
|
|
117
121
|
id: id ?? generateMessageId(),
|
|
118
122
|
role: "user",
|
|
119
123
|
parts,
|
|
120
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
124
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
125
|
+
...metadata != null ? { metadata } : {}
|
|
121
126
|
};
|
|
122
127
|
this.messages = [...this.messages, userMessage];
|
|
123
128
|
this.emitMessagesChange();
|
|
@@ -298,6 +303,9 @@ var StreamProcessor = class StreamProcessor {
|
|
|
298
303
|
case "REASONING_MESSAGE_CONTENT":
|
|
299
304
|
this.handleReasoningMessageContentEvent(chunk);
|
|
300
305
|
break;
|
|
306
|
+
case "REASONING_ENCRYPTED_VALUE":
|
|
307
|
+
this.handleReasoningEncryptedValueEvent(chunk);
|
|
308
|
+
break;
|
|
301
309
|
case "TOOL_CALL_RESULT":
|
|
302
310
|
this.handleToolCallResultEvent(chunk);
|
|
303
311
|
break;
|
|
@@ -422,6 +430,33 @@ var StreamProcessor = class StreamProcessor {
|
|
|
422
430
|
};
|
|
423
431
|
}
|
|
424
432
|
/**
|
|
433
|
+
* Merge event metadata onto a UIMessage. `tanstack` is deep-merged so a
|
|
434
|
+
* later delta does not wipe `tanstack.model`. High-frequency leftover
|
|
435
|
+
* keys (`content`, `args`) never stamp onto the message.
|
|
436
|
+
* Rebuilds `createdAt` when `tanstack.createdAt` is an ISO string.
|
|
437
|
+
*/
|
|
438
|
+
mergeMessageMetadata(messageId, incoming) {
|
|
439
|
+
if (incoming == null || typeof incoming !== "object" || Array.isArray(incoming)) return;
|
|
440
|
+
const message = this.messages.find((msg) => msg.id === messageId);
|
|
441
|
+
if (!message) return;
|
|
442
|
+
const incomingRecord = incoming;
|
|
443
|
+
const incomingTanstack = tanstackMetadata(incomingRecord);
|
|
444
|
+
const toMerge = incomingTanstack != null && ("content" in incomingTanstack || "args" in incomingTanstack) ? {
|
|
445
|
+
...incomingRecord,
|
|
446
|
+
tanstack: Object.fromEntries(Object.entries(incomingTanstack).filter(([key]) => key !== "content" && key !== "args"))
|
|
447
|
+
} : incomingRecord;
|
|
448
|
+
const metadata = mergeMetadata(message.metadata, toMerge);
|
|
449
|
+
const createdAtRaw = tanstackMetadata(incomingRecord)?.createdAt;
|
|
450
|
+
const createdAt = typeof createdAtRaw === "string" ? new Date(createdAtRaw) : void 0;
|
|
451
|
+
const createdAtValid = createdAt !== void 0 && !Number.isNaN(createdAt.getTime());
|
|
452
|
+
this.messages = this.messages.map((msg) => msg.id === messageId ? {
|
|
453
|
+
...msg,
|
|
454
|
+
...metadata !== void 0 ? { metadata } : {},
|
|
455
|
+
...createdAtValid ? { createdAt } : {}
|
|
456
|
+
} : msg);
|
|
457
|
+
this.emitMessagesChange();
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
425
460
|
* Handle TEXT_MESSAGE_START event
|
|
426
461
|
*/
|
|
427
462
|
handleTextMessageStartEvent(chunk) {
|
|
@@ -443,11 +478,13 @@ var StreamProcessor = class StreamProcessor {
|
|
|
443
478
|
}
|
|
444
479
|
this.activeMessageIds.delete(pendingId);
|
|
445
480
|
this.activeMessageIds.add(messageId);
|
|
481
|
+
for (const [toolCallId, mappedMessageId] of this.toolCallToMessage) if (mappedMessageId === pendingId) this.toolCallToMessage.set(toolCallId, messageId);
|
|
446
482
|
}
|
|
447
483
|
if (!this.messageStates.has(messageId)) {
|
|
448
484
|
this.createMessageState(messageId, uiRole);
|
|
449
485
|
this.activeMessageIds.add(messageId);
|
|
450
486
|
}
|
|
487
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
451
488
|
this.emitMessagesChange();
|
|
452
489
|
return;
|
|
453
490
|
}
|
|
@@ -461,6 +498,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
461
498
|
existingState.lastEmittedText = "";
|
|
462
499
|
existingState.hasToolCallsSinceTextStart = false;
|
|
463
500
|
}
|
|
501
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
464
502
|
return;
|
|
465
503
|
}
|
|
466
504
|
const newMessage = {
|
|
@@ -472,6 +510,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
472
510
|
this.messages = [...this.messages, newMessage];
|
|
473
511
|
this.createMessageState(messageId, uiRole);
|
|
474
512
|
this.activeMessageIds.add(messageId);
|
|
513
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
475
514
|
this.events.onStreamStart?.();
|
|
476
515
|
this.emitMessagesChange();
|
|
477
516
|
}
|
|
@@ -480,11 +519,11 @@ var StreamProcessor = class StreamProcessor {
|
|
|
480
519
|
*/
|
|
481
520
|
handleTextMessageEndEvent(chunk) {
|
|
482
521
|
const { messageId } = chunk;
|
|
522
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
483
523
|
const state = this.getMessageState(messageId);
|
|
484
524
|
if (!state) return;
|
|
485
525
|
if (state.isComplete) return;
|
|
486
526
|
if (state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
487
|
-
this.completeAllToolCallsForMessage(messageId);
|
|
488
527
|
}
|
|
489
528
|
/**
|
|
490
529
|
* Handle MESSAGES_SNAPSHOT event
|
|
@@ -492,8 +531,17 @@ var StreamProcessor = class StreamProcessor {
|
|
|
492
531
|
handleMessagesSnapshotEvent(chunk) {
|
|
493
532
|
this.resetStreamState();
|
|
494
533
|
const prevMessages = this.messages;
|
|
495
|
-
const
|
|
496
|
-
|
|
534
|
+
const prevById = new Map(prevMessages.map((msg) => [msg.id, msg]));
|
|
535
|
+
const normalized = this.mergeReasoningFanOut(chunk.messages.map(aguiSnapshotMessageToUIMessage));
|
|
536
|
+
this.messages = this.reconcileSnapshotToolCalls(normalized, prevMessages).map((msg) => {
|
|
537
|
+
if (msg.metadata != null) return msg;
|
|
538
|
+
const prev = prevById.get(msg.id);
|
|
539
|
+
if (prev?.metadata == null) return msg;
|
|
540
|
+
return {
|
|
541
|
+
...msg,
|
|
542
|
+
metadata: prev.metadata
|
|
543
|
+
};
|
|
544
|
+
});
|
|
497
545
|
this.emitMessagesChange();
|
|
498
546
|
}
|
|
499
547
|
/**
|
|
@@ -519,6 +567,42 @@ var StreamProcessor = class StreamProcessor {
|
|
|
519
567
|
* matching `tool-call` (and prefer pre-snapshot complete/output when the
|
|
520
568
|
* snapshot is poorer) so server tools keep the same UI shape as client tools.
|
|
521
569
|
*/
|
|
570
|
+
/**
|
|
571
|
+
* Wire order is reasoning fan-outs, then the assistant anchor.
|
|
572
|
+
* Snapshot conversion turns each reasoning row into its own assistant
|
|
573
|
+
* message. Fold leading thinking-only messages into the next real
|
|
574
|
+
* assistant. Do not fold into a tool-result-only message (`role: 'tool'`
|
|
575
|
+
* on the wire). `reconcileSnapshotToolCalls` anchors those results.
|
|
576
|
+
*/
|
|
577
|
+
mergeReasoningFanOut(messages) {
|
|
578
|
+
const out = [];
|
|
579
|
+
let pending = [];
|
|
580
|
+
const thinkingParts = (msg) => msg.parts.filter((part) => part.type === "thinking");
|
|
581
|
+
const isThinkingOnly = (msg) => msg.role === "assistant" && msg.parts.length > 0 && msg.parts.every((part) => part.type === "thinking");
|
|
582
|
+
const isToolResultOnly = (msg) => msg.role === "assistant" && msg.parts.length === 1 && msg.parts[0]?.type === "tool-result";
|
|
583
|
+
const flushPending = () => {
|
|
584
|
+
out.push(...pending);
|
|
585
|
+
pending = [];
|
|
586
|
+
};
|
|
587
|
+
for (const msg of messages) {
|
|
588
|
+
if (isThinkingOnly(msg)) {
|
|
589
|
+
pending.push(msg);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (msg.role === "assistant" && pending.length > 0 && !isToolResultOnly(msg)) {
|
|
593
|
+
out.push({
|
|
594
|
+
...msg,
|
|
595
|
+
parts: [...pending.flatMap(thinkingParts), ...msg.parts]
|
|
596
|
+
});
|
|
597
|
+
pending = [];
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
flushPending();
|
|
601
|
+
out.push(msg);
|
|
602
|
+
}
|
|
603
|
+
flushPending();
|
|
604
|
+
return out;
|
|
605
|
+
}
|
|
522
606
|
reconcileSnapshotToolCalls(snapshot, prevMessages) {
|
|
523
607
|
const prevToolCalls = /* @__PURE__ */ new Map();
|
|
524
608
|
for (const msg of prevMessages) for (const part of msg.parts) if (part.type === "tool-call") prevToolCalls.set(part.id, part);
|
|
@@ -607,15 +691,9 @@ var StreamProcessor = class StreamProcessor {
|
|
|
607
691
|
*/
|
|
608
692
|
handleTextMessageContentEvent(chunk) {
|
|
609
693
|
const { messageId, state } = this.ensureAssistantMessage(chunk.messageId);
|
|
610
|
-
this.
|
|
694
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
611
695
|
if (this.structuredMessageIds.has(messageId)) {
|
|
612
|
-
|
|
613
|
-
if (delta === "" && chunk.content !== void 0 && chunk.content !== "") {
|
|
614
|
-
const existingRaw = (this.messages.find((m) => m.id === messageId)?.parts.find((p) => p.type === "structured-output") ?? { raw: "" }).raw;
|
|
615
|
-
if (chunk.content.startsWith(existingRaw)) delta = chunk.content.slice(existingRaw.length);
|
|
616
|
-
else if (existingRaw.startsWith(chunk.content)) delta = "";
|
|
617
|
-
else delta = chunk.content;
|
|
618
|
-
}
|
|
696
|
+
const delta = chunk.delta || "";
|
|
619
697
|
if (delta !== "") {
|
|
620
698
|
this.messages = appendStructuredOutputDelta(this.messages, messageId, delta);
|
|
621
699
|
state.totalTextContent += delta;
|
|
@@ -632,18 +710,12 @@ var StreamProcessor = class StreamProcessor {
|
|
|
632
710
|
state.hasToolCallsSinceTextStart = false;
|
|
633
711
|
}
|
|
634
712
|
const currentText = state.currentSegmentText;
|
|
635
|
-
let nextText = currentText;
|
|
636
713
|
const delta = chunk.delta || "";
|
|
637
|
-
|
|
638
|
-
else if (chunk.content !== void 0 && chunk.content !== "") {
|
|
639
|
-
if (chunk.content.startsWith(currentText)) nextText = chunk.content;
|
|
640
|
-
else if (currentText.startsWith(chunk.content)) nextText = currentText;
|
|
641
|
-
else nextText = currentText + chunk.content;
|
|
642
|
-
}
|
|
714
|
+
const nextText = delta !== "" ? currentText + delta : currentText;
|
|
643
715
|
const textDelta = nextText.slice(currentText.length);
|
|
644
716
|
state.currentSegmentText = nextText;
|
|
645
717
|
state.totalTextContent += textDelta;
|
|
646
|
-
const chunkPortion = chunk.delta ||
|
|
718
|
+
const chunkPortion = chunk.delta || "";
|
|
647
719
|
if (this.chunkStrategy.shouldEmit(chunkPortion, state.currentSegmentText) && state.currentSegmentText !== state.lastEmittedText) this.emitTextUpdateForMessage(messageId);
|
|
648
720
|
}
|
|
649
721
|
/**
|
|
@@ -666,7 +738,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
666
738
|
const toolCallId = chunk.toolCallId;
|
|
667
739
|
if (!state.toolCalls.get(toolCallId)) {
|
|
668
740
|
const initialState = "awaiting-input";
|
|
669
|
-
const toolName = chunk.toolCallName
|
|
741
|
+
const toolName = chunk.toolCallName;
|
|
670
742
|
const chunkMetadata = chunk.metadata;
|
|
671
743
|
const newToolCall = {
|
|
672
744
|
id: chunk.toolCallId,
|
|
@@ -674,7 +746,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
674
746
|
arguments: "",
|
|
675
747
|
state: initialState,
|
|
676
748
|
parsedArguments: void 0,
|
|
677
|
-
index:
|
|
749
|
+
index: state.toolCalls.size,
|
|
678
750
|
...chunkMetadata !== void 0 && { metadata: chunkMetadata }
|
|
679
751
|
};
|
|
680
752
|
state.toolCalls.set(toolCallId, newToolCall);
|
|
@@ -725,17 +797,15 @@ var StreamProcessor = class StreamProcessor {
|
|
|
725
797
|
this.events.onToolCallStateChange?.(messageId, existingToolCall.id, existingToolCall.state, existingToolCall.arguments);
|
|
726
798
|
}
|
|
727
799
|
/**
|
|
728
|
-
* Handle TOOL_CALL_END event —
|
|
800
|
+
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
801
|
+
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
729
802
|
*
|
|
730
|
-
*
|
|
731
|
-
* -
|
|
732
|
-
*
|
|
733
|
-
*
|
|
803
|
+
* If TOOL_CALL_END carries parsed `input`, use it as the canonical arguments:
|
|
804
|
+
* back-fill the accumulated string when no TOOL_CALL_ARGS deltas were seen
|
|
805
|
+
* (adapters that deliver the whole input on END — e.g. Anthropic
|
|
806
|
+
* server_tool_use / web_search — issue #839) and override the rendered part's
|
|
807
|
+
* `input` with the canonical value.
|
|
734
808
|
*
|
|
735
|
-
* If `input` is provided, it overrides the accumulated string parse as the
|
|
736
|
-
* canonical parsed arguments.
|
|
737
|
-
*
|
|
738
|
-
* @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation
|
|
739
809
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
740
810
|
*/
|
|
741
811
|
handleToolCallEndEvent(chunk) {
|
|
@@ -743,39 +813,27 @@ var StreamProcessor = class StreamProcessor {
|
|
|
743
813
|
if (!messageId) return;
|
|
744
814
|
const msgState = this.getMessageState(messageId);
|
|
745
815
|
if (!msgState) return;
|
|
816
|
+
const input = chunk.input !== void 0 ? chunk.input : tanstackMetadata(chunk)?.input;
|
|
746
817
|
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId);
|
|
747
818
|
if (existingToolCall && existingToolCall.state !== "input-complete") {
|
|
748
|
-
if (
|
|
749
|
-
existingToolCall.arguments = JSON.stringify(
|
|
819
|
+
if (input !== void 0 && !existingToolCall.arguments) try {
|
|
820
|
+
existingToolCall.arguments = JSON.stringify(input);
|
|
750
821
|
} catch {}
|
|
751
822
|
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId);
|
|
752
823
|
this.completeToolCall(messageId, index, existingToolCall);
|
|
753
|
-
if (
|
|
754
|
-
existingToolCall.parsedArguments =
|
|
824
|
+
if (input !== void 0) {
|
|
825
|
+
existingToolCall.parsedArguments = input;
|
|
755
826
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
756
827
|
id: existingToolCall.id,
|
|
757
828
|
name: existingToolCall.name,
|
|
758
829
|
arguments: existingToolCall.arguments,
|
|
759
830
|
state: "input-complete",
|
|
760
|
-
input
|
|
831
|
+
input,
|
|
761
832
|
...existingToolCall.metadata !== void 0 && { metadata: existingToolCall.metadata }
|
|
762
833
|
});
|
|
763
834
|
this.emitMessagesChange();
|
|
764
835
|
}
|
|
765
836
|
}
|
|
766
|
-
if (chunk.result) {
|
|
767
|
-
let output;
|
|
768
|
-
if (Array.isArray(chunk.result)) output = chunk.result;
|
|
769
|
-
else try {
|
|
770
|
-
output = JSON.parse(chunk.result);
|
|
771
|
-
} catch {
|
|
772
|
-
output = chunk.result;
|
|
773
|
-
}
|
|
774
|
-
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, chunk.state === "output-error" ? "error" : void 0);
|
|
775
|
-
const resultState = chunk.state === "output-error" ? "error" : "complete";
|
|
776
|
-
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.result, resultState, resultState === "error" ? this.extractToolResultError(output) : void 0);
|
|
777
|
-
this.emitMessagesChange();
|
|
778
|
-
}
|
|
779
837
|
}
|
|
780
838
|
extractToolResultError(output) {
|
|
781
839
|
if (output && typeof output === "object" && "error" in output && typeof output.error === "string") return output.error;
|
|
@@ -789,16 +847,17 @@ var StreamProcessor = class StreamProcessor {
|
|
|
789
847
|
* This is the spec-compliant path for delivering tool results to the client.
|
|
790
848
|
*/
|
|
791
849
|
handleToolCallResultEvent(chunk) {
|
|
792
|
-
const messageId = this.toolCallToMessage.get(chunk.toolCallId);
|
|
850
|
+
const messageId = this.toolCallToMessage.get(chunk.toolCallId) ?? this.messages.find((m) => m.parts.some((p) => p.type === "tool-call" && p.id === chunk.toolCallId))?.id;
|
|
793
851
|
if (!messageId) return;
|
|
852
|
+
const isOutputError = chunk.state === "output-error" || tanstackMetadata(chunk)?.state === "output-error";
|
|
794
853
|
let output;
|
|
795
854
|
try {
|
|
796
855
|
output = JSON.parse(chunk.content);
|
|
797
856
|
} catch {
|
|
798
857
|
output = chunk.content;
|
|
799
858
|
}
|
|
800
|
-
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output,
|
|
801
|
-
const resultState =
|
|
859
|
+
this.messages = updateToolCallWithOutput(this.messages, chunk.toolCallId, output, isOutputError ? "error" : void 0);
|
|
860
|
+
const resultState = isOutputError ? "error" : "complete";
|
|
802
861
|
this.messages = updateToolResultPart(this.messages, messageId, chunk.toolCallId, chunk.content, resultState, resultState === "error" ? this.extractToolResultError(output) : void 0);
|
|
803
862
|
this.emitMessagesChange();
|
|
804
863
|
}
|
|
@@ -822,7 +881,8 @@ var StreamProcessor = class StreamProcessor {
|
|
|
822
881
|
* @see docs/chat-architecture.md#adapter-contract — Why RUN_FINISHED is mandatory
|
|
823
882
|
*/
|
|
824
883
|
handleRunFinishedEvent(chunk) {
|
|
825
|
-
|
|
884
|
+
const extra = chunk;
|
|
885
|
+
this.finishReason = extra.finishReason !== void 0 ? extra.finishReason : tanstackMetadata(chunk)?.finishReason ?? null;
|
|
826
886
|
this.activeRuns.delete(chunk.runId);
|
|
827
887
|
if (chunk.outcome?.type === "interrupt") this.handleInterrupts(chunk.outcome.interrupts);
|
|
828
888
|
if (this.activeRuns.size === 0) {
|
|
@@ -876,12 +936,12 @@ var StreamProcessor = class StreamProcessor {
|
|
|
876
936
|
*/
|
|
877
937
|
handleRunErrorEvent(chunk) {
|
|
878
938
|
this.hasError = true;
|
|
879
|
-
const runId =
|
|
939
|
+
const runId = getChunkRunId(chunk);
|
|
880
940
|
if (runId) this.activeRuns.delete(runId);
|
|
881
941
|
else this.activeRuns.clear();
|
|
882
942
|
const { messageId } = this.ensureAssistantMessage();
|
|
883
|
-
const errorMessage = chunk.message ||
|
|
884
|
-
if (!chunk.message
|
|
943
|
+
const errorMessage = chunk.message || "An error occurred";
|
|
944
|
+
if (!chunk.message) console.error("[StreamProcessor] RUN_ERROR with no message; original chunk:", chunk);
|
|
885
945
|
if (this.structuredMessageIds.has(messageId)) {
|
|
886
946
|
this.flushStructuredOutputUpdate(messageId);
|
|
887
947
|
this.messages = errorStructuredOutputPart(this.messages, messageId, errorMessage);
|
|
@@ -889,21 +949,17 @@ var StreamProcessor = class StreamProcessor {
|
|
|
889
949
|
this.emitStructuredOutputChange(messageId, "error");
|
|
890
950
|
this.emitMessagesChange();
|
|
891
951
|
}
|
|
892
|
-
|
|
893
|
-
const code = chunk.code ?? chunk.error?.code;
|
|
894
|
-
if (code !== void 0) Object.assign(error, { code });
|
|
895
|
-
if (chunk.rawEvent !== void 0) Object.assign(error, { rawEvent: chunk.rawEvent });
|
|
896
|
-
this.events.onError?.(error);
|
|
952
|
+
this.events.onError?.(runErrorEventToError(chunk));
|
|
897
953
|
}
|
|
898
954
|
/**
|
|
899
955
|
* Handle STEP_STARTED event (for thinking/reasoning content).
|
|
900
956
|
*
|
|
901
|
-
* Records the stepId so
|
|
957
|
+
* Records the stepId so later REASONING_MESSAGE_CONTENT deltas accumulate
|
|
902
958
|
* into their own ThinkingPart. Does not create a message — the message
|
|
903
|
-
* is lazily created when the first
|
|
959
|
+
* is lazily created when the first REASONING_MESSAGE_CONTENT arrives.
|
|
904
960
|
*/
|
|
905
961
|
handleStepStartedEvent(chunk) {
|
|
906
|
-
const stepId = chunk.
|
|
962
|
+
const stepId = chunk.stepName || generateMessageId();
|
|
907
963
|
const activeId = this.getActiveAssistantMessageId();
|
|
908
964
|
if (activeId) {
|
|
909
965
|
const state = this.getMessageState(activeId);
|
|
@@ -920,48 +976,25 @@ var StreamProcessor = class StreamProcessor {
|
|
|
920
976
|
this.pendingThinkingStepId = stepId;
|
|
921
977
|
}
|
|
922
978
|
/**
|
|
923
|
-
* Handle STEP_FINISHED event
|
|
979
|
+
* Handle STEP_FINISHED event.
|
|
924
980
|
*
|
|
925
|
-
*
|
|
926
|
-
*
|
|
927
|
-
*
|
|
928
|
-
*
|
|
981
|
+
* Thinking *content* comes from REASONING_MESSAGE_CONTENT, not STEP_FINISHED.
|
|
982
|
+
* But some adapters (e.g. BytePlus thinking-summary) carry the provider
|
|
983
|
+
* signature blob ONLY on the STEP_FINISHED event, so still extract that here
|
|
984
|
+
* and attach it to the thinking step the reasoning events already built.
|
|
929
985
|
*/
|
|
930
986
|
handleStepFinishedEvent(chunk) {
|
|
987
|
+
const extra = chunk;
|
|
988
|
+
const signature = extra.signature;
|
|
989
|
+
if (!signature) return;
|
|
931
990
|
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
state.thinkingStepSignatures.set(stepId, chunk.signature);
|
|
939
|
-
this.messages = updateThinkingPart(this.messages, messageId, stepId, thinking, chunk.signature);
|
|
940
|
-
this.emitMessagesChange();
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
return;
|
|
944
|
-
}
|
|
945
|
-
this.consumePendingThinkingStep(state);
|
|
946
|
-
const stepId = state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId();
|
|
947
|
-
if (!state.thinkingSteps.has(stepId)) {
|
|
948
|
-
state.thinkingSteps.set(stepId, "");
|
|
949
|
-
state.thinkingStepOrder.push(stepId);
|
|
950
|
-
state.currentThinkingStepId = stepId;
|
|
951
|
-
}
|
|
952
|
-
const previous = state.thinkingSteps.get(stepId) ?? "";
|
|
953
|
-
let nextThinking = previous;
|
|
954
|
-
if (chunk.delta && chunk.delta !== "") nextThinking = previous + chunk.delta;
|
|
955
|
-
else if (chunk.content && chunk.content !== "") {
|
|
956
|
-
if (chunk.content.startsWith(previous)) nextThinking = chunk.content;
|
|
957
|
-
else if (previous.startsWith(chunk.content)) nextThinking = previous;
|
|
958
|
-
else nextThinking = previous + chunk.content;
|
|
959
|
-
}
|
|
960
|
-
state.thinkingSteps.set(stepId, nextThinking);
|
|
961
|
-
if (chunk.signature) state.thinkingStepSignatures.set(stepId, chunk.signature);
|
|
962
|
-
this.messages = updateThinkingPart(this.messages, messageId, stepId, nextThinking, state.thinkingStepSignatures.get(stepId));
|
|
991
|
+
const stepId = state.currentThinkingStepId ?? extra.stepId;
|
|
992
|
+
if (!stepId) return;
|
|
993
|
+
const thinking = state.thinkingSteps.get(stepId);
|
|
994
|
+
if (thinking === void 0) return;
|
|
995
|
+
state.thinkingStepSignatures.set(stepId, signature);
|
|
996
|
+
this.messages = updateThinkingPart(this.messages, messageId, stepId, thinking, signature);
|
|
963
997
|
this.emitMessagesChange();
|
|
964
|
-
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking);
|
|
965
998
|
}
|
|
966
999
|
/**
|
|
967
1000
|
* Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).
|
|
@@ -987,6 +1020,58 @@ var StreamProcessor = class StreamProcessor {
|
|
|
987
1020
|
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking);
|
|
988
1021
|
}
|
|
989
1022
|
/**
|
|
1023
|
+
* Attach a provider signature blob from REASONING_ENCRYPTED_VALUE.
|
|
1024
|
+
* `subtype: 'message'` updates ThinkingPart.signature.
|
|
1025
|
+
* `subtype: 'tool-call'` stores Gemini thoughtSignature on the tool-call part.
|
|
1026
|
+
*/
|
|
1027
|
+
handleReasoningEncryptedValueEvent(chunk) {
|
|
1028
|
+
const encryptedValue = chunk.encryptedValue;
|
|
1029
|
+
if (typeof encryptedValue !== "string" || encryptedValue === "") return;
|
|
1030
|
+
if (chunk.subtype === "tool-call") {
|
|
1031
|
+
this.attachToolCallSignature(chunk.entityId, encryptedValue);
|
|
1032
|
+
return;
|
|
1033
|
+
}
|
|
1034
|
+
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
1035
|
+
const stepId = state.currentThinkingStepId ?? chunk.entityId;
|
|
1036
|
+
state.thinkingStepSignatures.set(stepId, encryptedValue);
|
|
1037
|
+
const content = state.thinkingSteps.get(stepId) ?? "";
|
|
1038
|
+
if (!state.thinkingSteps.has(stepId)) {
|
|
1039
|
+
state.thinkingSteps.set(stepId, content);
|
|
1040
|
+
state.thinkingStepOrder.push(stepId);
|
|
1041
|
+
}
|
|
1042
|
+
this.messages = updateThinkingPart(this.messages, messageId, stepId, content, encryptedValue);
|
|
1043
|
+
this.emitMessagesChange();
|
|
1044
|
+
}
|
|
1045
|
+
attachToolCallSignature(toolCallId, thoughtSignature) {
|
|
1046
|
+
this.messages = this.messages.map((msg) => {
|
|
1047
|
+
let changed = false;
|
|
1048
|
+
const parts = msg.parts.map((part) => {
|
|
1049
|
+
if (part.type !== "tool-call" || part.id !== toolCallId) return part;
|
|
1050
|
+
changed = true;
|
|
1051
|
+
return {
|
|
1052
|
+
...part,
|
|
1053
|
+
metadata: {
|
|
1054
|
+
...part.metadata != null && typeof part.metadata === "object" ? part.metadata : {},
|
|
1055
|
+
thoughtSignature
|
|
1056
|
+
}
|
|
1057
|
+
};
|
|
1058
|
+
});
|
|
1059
|
+
return changed ? {
|
|
1060
|
+
...msg,
|
|
1061
|
+
parts
|
|
1062
|
+
} : msg;
|
|
1063
|
+
});
|
|
1064
|
+
for (const state of this.messageStates.values()) {
|
|
1065
|
+
const call = state.toolCalls.get(toolCallId);
|
|
1066
|
+
if (!call) continue;
|
|
1067
|
+
call.metadata = {
|
|
1068
|
+
...call.metadata ?? {},
|
|
1069
|
+
thoughtSignature
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
this.emitMessagesChange();
|
|
1073
|
+
}
|
|
1074
|
+
/**
|
|
990
1075
|
* Handle CUSTOM event.
|
|
991
1076
|
*
|
|
992
1077
|
* Handles custom events consumed by the processor:
|
|
@@ -1081,12 +1166,8 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1081
1166
|
/**
|
|
1082
1167
|
* Detect if an incoming content chunk represents a NEW text segment
|
|
1083
1168
|
*/
|
|
1084
|
-
isNewTextSegment(
|
|
1085
|
-
|
|
1086
|
-
if (chunk.content.length < previous.length) return true;
|
|
1087
|
-
if (!chunk.content.startsWith(previous) && !previous.startsWith(chunk.content)) return true;
|
|
1088
|
-
}
|
|
1089
|
-
return false;
|
|
1169
|
+
isNewTextSegment(_chunk, _previous) {
|
|
1170
|
+
return true;
|
|
1090
1171
|
}
|
|
1091
1172
|
/**
|
|
1092
1173
|
* Complete all tool calls across all active messages — safety net for stream termination.
|
|
@@ -1118,7 +1199,13 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1118
1199
|
*/
|
|
1119
1200
|
completeToolCall(messageId, _index, toolCall) {
|
|
1120
1201
|
toolCall.state = "input-complete";
|
|
1121
|
-
|
|
1202
|
+
let strictParseSucceeded = false;
|
|
1203
|
+
try {
|
|
1204
|
+
toolCall.parsedArguments = JSON.parse(toolCall.arguments);
|
|
1205
|
+
strictParseSucceeded = true;
|
|
1206
|
+
} catch {
|
|
1207
|
+
toolCall.parsedArguments = void 0;
|
|
1208
|
+
}
|
|
1122
1209
|
if (this.isToolCallPartErrored(toolCall.id)) return;
|
|
1123
1210
|
if (this.isToolCallPartAwaitingUserAction(toolCall.id)) return;
|
|
1124
1211
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
@@ -1126,7 +1213,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1126
1213
|
name: toolCall.name,
|
|
1127
1214
|
arguments: toolCall.arguments,
|
|
1128
1215
|
state: "input-complete",
|
|
1129
|
-
...
|
|
1216
|
+
...strictParseSucceeded && { input: toolCall.parsedArguments },
|
|
1130
1217
|
...toolCall.metadata !== void 0 && { metadata: toolCall.metadata }
|
|
1131
1218
|
});
|
|
1132
1219
|
this.emitMessagesChange();
|