@tanstack/ai 0.47.3 → 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 +115 -25
- 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 +186 -101
- 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 +56 -53
- 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 +11 -4
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/stream-to-response.js +8 -5
- 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/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 +2 -2
- package/src/activities/chat/adapter.ts +4 -4
- package/src/activities/chat/index.ts +398 -400
- package/src/activities/chat/messages.ts +174 -34
- package/src/activities/chat/stream/processor.ts +289 -218
- 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 +94 -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 +11 -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/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,8 +1,10 @@
|
|
|
1
|
+
import { mergeMetadata, tanstackMetadata } from "../../../utilities/merge-metadata.js";
|
|
1
2
|
import { runErrorEventToError } from "../../../utilities/errors.js";
|
|
2
3
|
import { INTERRUPT_BINDING_METADATA_KEY } from "../../../interrupt-resume.js";
|
|
3
4
|
import { normalizeToolResult } from "../../../utilities/tool-result.js";
|
|
4
5
|
import { isProviderExecutedToolCall } from "../../../utilities/provider-executed.js";
|
|
5
6
|
import { aguiSnapshotMessageToUIMessage, generateMessageId, uiMessageToModelMessages } from "../messages.js";
|
|
7
|
+
import { getChunkRunId } from "../../../utilities/chunk-ids.js";
|
|
6
8
|
import { defaultJSONParser } from "./json-parser.js";
|
|
7
9
|
import { appendStructuredOutputDelta, completeStructuredOutputPart, errorStructuredOutputPart, updateTextPart, updateThinkingPart, updateToolCallApproval, updateToolCallApprovalResponse, updateToolCallPart, updateToolCallWithOutput, updateToolResultPart } from "./message-updaters.js";
|
|
8
10
|
import { ImmediateStrategy } from "./strategies.js";
|
|
@@ -90,6 +92,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
90
92
|
*
|
|
91
93
|
* @param content - The message content (string or array of content parts)
|
|
92
94
|
* @param id - Optional custom message ID (generated if not provided)
|
|
95
|
+
* @param metadata - Optional AG-UI metadata bag
|
|
93
96
|
* @returns The created UIMessage
|
|
94
97
|
*
|
|
95
98
|
* @example
|
|
@@ -107,7 +110,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
107
110
|
* processor.addUserMessage('Hello!', 'custom-id-123')
|
|
108
111
|
* ```
|
|
109
112
|
*/
|
|
110
|
-
addUserMessage(content, id) {
|
|
113
|
+
addUserMessage(content, id, metadata) {
|
|
111
114
|
const parts = typeof content === "string" ? [{
|
|
112
115
|
type: "text",
|
|
113
116
|
content
|
|
@@ -118,7 +121,8 @@ var StreamProcessor = class StreamProcessor {
|
|
|
118
121
|
id: id ?? generateMessageId(),
|
|
119
122
|
role: "user",
|
|
120
123
|
parts,
|
|
121
|
-
createdAt: /* @__PURE__ */ new Date()
|
|
124
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
125
|
+
...metadata != null ? { metadata } : {}
|
|
122
126
|
};
|
|
123
127
|
this.messages = [...this.messages, userMessage];
|
|
124
128
|
this.emitMessagesChange();
|
|
@@ -299,6 +303,9 @@ var StreamProcessor = class StreamProcessor {
|
|
|
299
303
|
case "REASONING_MESSAGE_CONTENT":
|
|
300
304
|
this.handleReasoningMessageContentEvent(chunk);
|
|
301
305
|
break;
|
|
306
|
+
case "REASONING_ENCRYPTED_VALUE":
|
|
307
|
+
this.handleReasoningEncryptedValueEvent(chunk);
|
|
308
|
+
break;
|
|
302
309
|
case "TOOL_CALL_RESULT":
|
|
303
310
|
this.handleToolCallResultEvent(chunk);
|
|
304
311
|
break;
|
|
@@ -423,6 +430,33 @@ var StreamProcessor = class StreamProcessor {
|
|
|
423
430
|
};
|
|
424
431
|
}
|
|
425
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
|
+
/**
|
|
426
460
|
* Handle TEXT_MESSAGE_START event
|
|
427
461
|
*/
|
|
428
462
|
handleTextMessageStartEvent(chunk) {
|
|
@@ -450,6 +484,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
450
484
|
this.createMessageState(messageId, uiRole);
|
|
451
485
|
this.activeMessageIds.add(messageId);
|
|
452
486
|
}
|
|
487
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
453
488
|
this.emitMessagesChange();
|
|
454
489
|
return;
|
|
455
490
|
}
|
|
@@ -463,6 +498,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
463
498
|
existingState.lastEmittedText = "";
|
|
464
499
|
existingState.hasToolCallsSinceTextStart = false;
|
|
465
500
|
}
|
|
501
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
466
502
|
return;
|
|
467
503
|
}
|
|
468
504
|
const newMessage = {
|
|
@@ -474,6 +510,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
474
510
|
this.messages = [...this.messages, newMessage];
|
|
475
511
|
this.createMessageState(messageId, uiRole);
|
|
476
512
|
this.activeMessageIds.add(messageId);
|
|
513
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
477
514
|
this.events.onStreamStart?.();
|
|
478
515
|
this.emitMessagesChange();
|
|
479
516
|
}
|
|
@@ -482,6 +519,7 @@ var StreamProcessor = class StreamProcessor {
|
|
|
482
519
|
*/
|
|
483
520
|
handleTextMessageEndEvent(chunk) {
|
|
484
521
|
const { messageId } = chunk;
|
|
522
|
+
this.mergeMessageMetadata(messageId, chunk.metadata);
|
|
485
523
|
const state = this.getMessageState(messageId);
|
|
486
524
|
if (!state) return;
|
|
487
525
|
if (state.isComplete) return;
|
|
@@ -493,8 +531,17 @@ var StreamProcessor = class StreamProcessor {
|
|
|
493
531
|
handleMessagesSnapshotEvent(chunk) {
|
|
494
532
|
this.resetStreamState();
|
|
495
533
|
const prevMessages = this.messages;
|
|
496
|
-
const
|
|
497
|
-
|
|
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
|
+
});
|
|
498
545
|
this.emitMessagesChange();
|
|
499
546
|
}
|
|
500
547
|
/**
|
|
@@ -520,6 +567,42 @@ var StreamProcessor = class StreamProcessor {
|
|
|
520
567
|
* matching `tool-call` (and prefer pre-snapshot complete/output when the
|
|
521
568
|
* snapshot is poorer) so server tools keep the same UI shape as client tools.
|
|
522
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
|
+
}
|
|
523
606
|
reconcileSnapshotToolCalls(snapshot, prevMessages) {
|
|
524
607
|
const prevToolCalls = /* @__PURE__ */ new Map();
|
|
525
608
|
for (const msg of prevMessages) for (const part of msg.parts) if (part.type === "tool-call") prevToolCalls.set(part.id, part);
|
|
@@ -608,14 +691,9 @@ var StreamProcessor = class StreamProcessor {
|
|
|
608
691
|
*/
|
|
609
692
|
handleTextMessageContentEvent(chunk) {
|
|
610
693
|
const { messageId, state } = this.ensureAssistantMessage(chunk.messageId);
|
|
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 —
|
|
729
|
-
*
|
|
730
|
-
* This event has a DUAL ROLE:
|
|
731
|
-
* - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.
|
|
732
|
-
* - With `result`: Signals tool was executed and result is available (from TextEngine).
|
|
733
|
-
* Creates both output on the tool-call part AND a tool-result part.
|
|
800
|
+
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
801
|
+
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
734
802
|
*
|
|
735
|
-
* If `input
|
|
736
|
-
*
|
|
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.
|
|
737
808
|
*
|
|
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);
|
|
@@ -894,12 +954,12 @@ var StreamProcessor = class StreamProcessor {
|
|
|
894
954
|
/**
|
|
895
955
|
* Handle STEP_STARTED event (for thinking/reasoning content).
|
|
896
956
|
*
|
|
897
|
-
* Records the stepId so
|
|
957
|
+
* Records the stepId so later REASONING_MESSAGE_CONTENT deltas accumulate
|
|
898
958
|
* into their own ThinkingPart. Does not create a message — the message
|
|
899
|
-
* is lazily created when the first
|
|
959
|
+
* is lazily created when the first REASONING_MESSAGE_CONTENT arrives.
|
|
900
960
|
*/
|
|
901
961
|
handleStepStartedEvent(chunk) {
|
|
902
|
-
const stepId = chunk.
|
|
962
|
+
const stepId = chunk.stepName || generateMessageId();
|
|
903
963
|
const activeId = this.getActiveAssistantMessageId();
|
|
904
964
|
if (activeId) {
|
|
905
965
|
const state = this.getMessageState(activeId);
|
|
@@ -916,48 +976,25 @@ var StreamProcessor = class StreamProcessor {
|
|
|
916
976
|
this.pendingThinkingStepId = stepId;
|
|
917
977
|
}
|
|
918
978
|
/**
|
|
919
|
-
* Handle STEP_FINISHED event
|
|
920
|
-
*
|
|
921
|
-
* Accumulates delta into the current thinking step's content and updates
|
|
922
|
-
* the corresponding ThinkingPart in the UIMessage.
|
|
979
|
+
* Handle STEP_FINISHED event.
|
|
923
980
|
*
|
|
924
|
-
*
|
|
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.
|
|
925
985
|
*/
|
|
926
986
|
handleStepFinishedEvent(chunk) {
|
|
987
|
+
const extra = chunk;
|
|
988
|
+
const signature = extra.signature;
|
|
989
|
+
if (!signature) return;
|
|
927
990
|
const { messageId, state } = this.ensureAssistantMessage(this.getActiveAssistantMessageId() ?? void 0);
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
state.thinkingStepSignatures.set(stepId, chunk.signature);
|
|
935
|
-
this.messages = updateThinkingPart(this.messages, messageId, stepId, thinking, chunk.signature);
|
|
936
|
-
this.emitMessagesChange();
|
|
937
|
-
}
|
|
938
|
-
}
|
|
939
|
-
return;
|
|
940
|
-
}
|
|
941
|
-
this.consumePendingThinkingStep(state);
|
|
942
|
-
const stepId = state.currentThinkingStepId ?? chunk.stepId ?? generateMessageId();
|
|
943
|
-
if (!state.thinkingSteps.has(stepId)) {
|
|
944
|
-
state.thinkingSteps.set(stepId, "");
|
|
945
|
-
state.thinkingStepOrder.push(stepId);
|
|
946
|
-
state.currentThinkingStepId = stepId;
|
|
947
|
-
}
|
|
948
|
-
const previous = state.thinkingSteps.get(stepId) ?? "";
|
|
949
|
-
let nextThinking = previous;
|
|
950
|
-
if (chunk.delta && chunk.delta !== "") nextThinking = previous + chunk.delta;
|
|
951
|
-
else if (chunk.content && chunk.content !== "") {
|
|
952
|
-
if (chunk.content.startsWith(previous)) nextThinking = chunk.content;
|
|
953
|
-
else if (previous.startsWith(chunk.content)) nextThinking = previous;
|
|
954
|
-
else nextThinking = previous + chunk.content;
|
|
955
|
-
}
|
|
956
|
-
state.thinkingSteps.set(stepId, nextThinking);
|
|
957
|
-
if (chunk.signature) state.thinkingStepSignatures.set(stepId, chunk.signature);
|
|
958
|
-
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);
|
|
959
997
|
this.emitMessagesChange();
|
|
960
|
-
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking);
|
|
961
998
|
}
|
|
962
999
|
/**
|
|
963
1000
|
* Handle REASONING_MESSAGE_CONTENT event (AG-UI reasoning protocol).
|
|
@@ -983,6 +1020,58 @@ var StreamProcessor = class StreamProcessor {
|
|
|
983
1020
|
this.events.onThinkingUpdate?.(messageId, stepId, nextThinking);
|
|
984
1021
|
}
|
|
985
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
|
+
/**
|
|
986
1075
|
* Handle CUSTOM event.
|
|
987
1076
|
*
|
|
988
1077
|
* Handles custom events consumed by the processor:
|
|
@@ -1077,12 +1166,8 @@ var StreamProcessor = class StreamProcessor {
|
|
|
1077
1166
|
/**
|
|
1078
1167
|
* Detect if an incoming content chunk represents a NEW text segment
|
|
1079
1168
|
*/
|
|
1080
|
-
isNewTextSegment(
|
|
1081
|
-
|
|
1082
|
-
if (chunk.content.length < previous.length) return true;
|
|
1083
|
-
if (!chunk.content.startsWith(previous) && !previous.startsWith(chunk.content)) return true;
|
|
1084
|
-
}
|
|
1085
|
-
return false;
|
|
1169
|
+
isNewTextSegment(_chunk, _previous) {
|
|
1170
|
+
return true;
|
|
1086
1171
|
}
|
|
1087
1172
|
/**
|
|
1088
1173
|
* Complete all tool calls across all active messages — safety net for stream termination.
|