@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,4 +1,6 @@
|
|
|
1
|
+
import { isProviderExecutedToolCall } from '../../utilities/provider-executed'
|
|
1
2
|
import { normalizeToolResult } from '../../utilities/tool-result'
|
|
3
|
+
import { tanstackMetadata } from '../../utilities/merge-metadata'
|
|
2
4
|
import type { Message as AGUIMessage } from '@ag-ui/core'
|
|
3
5
|
import type {
|
|
4
6
|
ContentPart,
|
|
@@ -6,6 +8,7 @@ import type {
|
|
|
6
8
|
ModelMessage,
|
|
7
9
|
StructuredOutputPart,
|
|
8
10
|
TextPart,
|
|
11
|
+
ToolCall,
|
|
9
12
|
ToolCallPart,
|
|
10
13
|
UIMessage,
|
|
11
14
|
} from '../../types'
|
|
@@ -35,6 +38,34 @@ export function safeJsonStringify(value: unknown): string {
|
|
|
35
38
|
}
|
|
36
39
|
}
|
|
37
40
|
|
|
41
|
+
function nonEmptyString(value: unknown): string | undefined {
|
|
42
|
+
return typeof value === 'string' && value !== '' ? value : undefined
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function encryptedValueFrom(value: object): string | undefined {
|
|
46
|
+
if ('encryptedValue' in value) {
|
|
47
|
+
const fromSpec = nonEmptyString(value.encryptedValue)
|
|
48
|
+
if (fromSpec !== undefined) return fromSpec
|
|
49
|
+
}
|
|
50
|
+
return nonEmptyString(tanstackMetadata(value)?.signature)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function toolCallFromWire(toolCall: ToolCall, bag: unknown): ToolCall {
|
|
54
|
+
const fromBag =
|
|
55
|
+
bag != null && typeof bag === 'object' && !Array.isArray(bag)
|
|
56
|
+
? bag
|
|
57
|
+
: undefined
|
|
58
|
+
const encrypted = encryptedValueFrom(toolCall)
|
|
59
|
+
if (fromBag === undefined && encrypted === undefined) return toolCall
|
|
60
|
+
return {
|
|
61
|
+
...toolCall,
|
|
62
|
+
metadata: {
|
|
63
|
+
...(fromBag ?? {}),
|
|
64
|
+
...(encrypted !== undefined ? { thoughtSignature: encrypted } : {}),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
38
69
|
function parseToolResultContent(content: string): unknown {
|
|
39
70
|
try {
|
|
40
71
|
return JSON.parse(content)
|
|
@@ -67,8 +98,13 @@ function collapseContentParts(
|
|
|
67
98
|
* Extract text content from ModelMessage content (string, null, or ContentPart array).
|
|
68
99
|
* Used when only the text portion is needed (e.g., tool result content).
|
|
69
100
|
*/
|
|
70
|
-
function getTextContent(
|
|
71
|
-
|
|
101
|
+
function getTextContent(
|
|
102
|
+
content: string | null | undefined | Array<ContentPart>,
|
|
103
|
+
): string {
|
|
104
|
+
// Tool-call-only assistant turns carry no text and reach here as `null` or
|
|
105
|
+
// `undefined`; both must collapse to an empty string rather than crash on
|
|
106
|
+
// `.filter` (issue #532 — the interrupt-boundary MessagesSnapshot).
|
|
107
|
+
if (content === null || content === undefined) return ''
|
|
72
108
|
if (typeof content === 'string') return content
|
|
73
109
|
return content
|
|
74
110
|
.filter((part): part is TextPart => part.type === 'text')
|
|
@@ -97,16 +133,15 @@ export function convertMessagesToModelMessages(
|
|
|
97
133
|
}
|
|
98
134
|
|
|
99
135
|
const modelMessages: Array<ModelMessage> = []
|
|
136
|
+
let pendingThinking: Array<{ content: string; signature?: string }> = []
|
|
100
137
|
for (const msg of messages) {
|
|
101
138
|
if ('parts' in msg) {
|
|
102
|
-
// UIMessage anchor — existing fan-out path
|
|
103
139
|
modelMessages.push(...uiMessageToModelMessages(msg))
|
|
104
140
|
continue
|
|
105
141
|
}
|
|
106
142
|
|
|
107
143
|
const role = (msg as { role: string }).role
|
|
108
144
|
|
|
109
|
-
// AG-UI tool fan-out duplicate — drop if anchor already covers it
|
|
110
145
|
if (
|
|
111
146
|
role === 'tool' &&
|
|
112
147
|
msg.toolCallId &&
|
|
@@ -115,12 +150,22 @@ export function convertMessagesToModelMessages(
|
|
|
115
150
|
continue
|
|
116
151
|
}
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
|
|
153
|
+
if (role === 'reasoning') {
|
|
154
|
+
const content = (msg as { content?: string }).content
|
|
155
|
+
if (content) {
|
|
156
|
+
const signature = encryptedValueFrom(msg)
|
|
157
|
+
pendingThinking.push({
|
|
158
|
+
content,
|
|
159
|
+
...(signature !== undefined ? { signature } : {}),
|
|
160
|
+
})
|
|
161
|
+
}
|
|
162
|
+
continue
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (role === 'activity') {
|
|
120
166
|
continue
|
|
121
167
|
}
|
|
122
168
|
|
|
123
|
-
// AG-UI developer — collapse to system
|
|
124
169
|
if (role === 'developer') {
|
|
125
170
|
modelMessages.push({
|
|
126
171
|
role: 'system' as ModelMessage['role'],
|
|
@@ -129,8 +174,56 @@ export function convertMessagesToModelMessages(
|
|
|
129
174
|
continue
|
|
130
175
|
}
|
|
131
176
|
|
|
132
|
-
|
|
133
|
-
|
|
177
|
+
if (
|
|
178
|
+
role === 'user' &&
|
|
179
|
+
Array.isArray((msg as { content?: unknown }).content)
|
|
180
|
+
) {
|
|
181
|
+
const content = (msg as { content: Array<{ type: string }> }).content
|
|
182
|
+
// TanStack ModelMessage text parts use `{ content }`. AG-UI wire text
|
|
183
|
+
// parts use `{ text }`. Only rewrite the AG-UI shape.
|
|
184
|
+
if (
|
|
185
|
+
!content.some(
|
|
186
|
+
(part) =>
|
|
187
|
+
part.type === 'text' && 'text' in part && !('content' in part),
|
|
188
|
+
)
|
|
189
|
+
) {
|
|
190
|
+
modelMessages.push(msg as ModelMessage)
|
|
191
|
+
continue
|
|
192
|
+
}
|
|
193
|
+
const parts = aguiUserContentToParts(
|
|
194
|
+
content as Extract<AGUIMessage, { role: 'user' }>['content'],
|
|
195
|
+
)
|
|
196
|
+
const contentParts = parts.filter(isContentPart)
|
|
197
|
+
modelMessages.push({
|
|
198
|
+
role: 'user',
|
|
199
|
+
content: collapseContentParts(contentParts),
|
|
200
|
+
...((msg as { id?: string }).id !== undefined && {
|
|
201
|
+
id: (msg as { id: string }).id,
|
|
202
|
+
}),
|
|
203
|
+
})
|
|
204
|
+
continue
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (role === 'assistant') {
|
|
208
|
+
const source = msg as ModelMessage
|
|
209
|
+
const toolCallMetadata = tanstackMetadata(msg)?.toolCallMetadata
|
|
210
|
+
const toolCalls = source.toolCalls?.map((toolCall) =>
|
|
211
|
+
toolCallFromWire(toolCall, toolCallMetadata?.[toolCall.id]),
|
|
212
|
+
)
|
|
213
|
+
modelMessages.push({
|
|
214
|
+
...source,
|
|
215
|
+
...(toolCalls !== undefined ? { toolCalls } : {}),
|
|
216
|
+
...(pendingThinking.length > 0
|
|
217
|
+
? {
|
|
218
|
+
thinking: [...(source.thinking ?? []), ...pendingThinking],
|
|
219
|
+
}
|
|
220
|
+
: {}),
|
|
221
|
+
})
|
|
222
|
+
pendingThinking = []
|
|
223
|
+
continue
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
modelMessages.push(msg as ModelMessage)
|
|
134
227
|
}
|
|
135
228
|
return modelMessages
|
|
136
229
|
}
|
|
@@ -249,14 +342,15 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
249
342
|
const content = collapseContentParts(current.contentParts)
|
|
250
343
|
const hasContent = content !== null
|
|
251
344
|
const hasToolCalls = current.toolCalls.length > 0
|
|
345
|
+
const hasThinking = pendingThinking.length > 0
|
|
252
346
|
|
|
253
|
-
if (hasContent || hasToolCalls) {
|
|
347
|
+
if (hasContent || hasToolCalls || hasThinking) {
|
|
254
348
|
messageList.push({
|
|
255
349
|
id: uiMessage.id,
|
|
256
350
|
role: 'assistant',
|
|
257
351
|
content,
|
|
258
352
|
...(hasToolCalls && { toolCalls: current.toolCalls }),
|
|
259
|
-
...(
|
|
353
|
+
...(hasThinking && { thinking: pendingThinking }),
|
|
260
354
|
...(current.structuredOutput && {
|
|
261
355
|
structuredOutput: current.structuredOutput,
|
|
262
356
|
}),
|
|
@@ -317,6 +411,11 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
317
411
|
|
|
318
412
|
case 'thinking':
|
|
319
413
|
if (part.content) {
|
|
414
|
+
// Provider-executed tools have no tool-result part, so thinking
|
|
415
|
+
// after them has to start the next segment or it replays first.
|
|
416
|
+
if (current.toolCalls.some(isProviderExecutedToolCall)) {
|
|
417
|
+
flushSegment()
|
|
418
|
+
}
|
|
320
419
|
pendingThinking.push({
|
|
321
420
|
content: part.content,
|
|
322
421
|
...(part.signature && { signature: part.signature }),
|
|
@@ -529,58 +628,106 @@ export function aguiSnapshotMessageToUIMessage(
|
|
|
529
628
|
message: AGUIMessage | UIMessage,
|
|
530
629
|
): UIMessage {
|
|
531
630
|
if ('parts' in message) {
|
|
532
|
-
return
|
|
631
|
+
return applySnapshotMetadata(message, {
|
|
632
|
+
...message,
|
|
633
|
+
id: message.id || generateMessageId(),
|
|
634
|
+
})
|
|
533
635
|
}
|
|
534
636
|
|
|
535
637
|
const id = message.id || generateMessageId()
|
|
536
638
|
|
|
537
639
|
switch (message.role) {
|
|
538
640
|
case 'user':
|
|
539
|
-
return {
|
|
641
|
+
return applySnapshotMetadata(message, {
|
|
540
642
|
id,
|
|
541
643
|
role: 'user',
|
|
542
644
|
parts: aguiUserContentToParts(message.content),
|
|
543
|
-
}
|
|
544
|
-
case 'assistant':
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
645
|
+
})
|
|
646
|
+
case 'assistant': {
|
|
647
|
+
const toolCallMetadata = tanstackMetadata(message)?.toolCallMetadata
|
|
648
|
+
const toolCalls = message.toolCalls?.map((toolCall) => {
|
|
649
|
+
const metadata =
|
|
650
|
+
toolCallMetadata != null && typeof toolCallMetadata === 'object'
|
|
651
|
+
? (toolCallMetadata as Record<string, unknown>)[toolCall.id]
|
|
652
|
+
: undefined
|
|
653
|
+
return metadata !== undefined ? { ...toolCall, metadata } : toolCall
|
|
654
|
+
})
|
|
655
|
+
return applySnapshotMetadata(
|
|
656
|
+
message,
|
|
657
|
+
modelMessageToUIMessage(
|
|
658
|
+
{
|
|
659
|
+
role: 'assistant',
|
|
660
|
+
content: message.content ?? null,
|
|
661
|
+
...(toolCalls && { toolCalls }),
|
|
662
|
+
},
|
|
663
|
+
id,
|
|
664
|
+
),
|
|
552
665
|
)
|
|
666
|
+
}
|
|
553
667
|
case 'tool':
|
|
554
|
-
return
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
668
|
+
return applySnapshotMetadata(
|
|
669
|
+
message,
|
|
670
|
+
modelMessageToUIMessage(
|
|
671
|
+
{
|
|
672
|
+
role: 'tool',
|
|
673
|
+
content: message.content,
|
|
674
|
+
toolCallId: message.toolCallId,
|
|
675
|
+
},
|
|
676
|
+
id,
|
|
677
|
+
),
|
|
561
678
|
)
|
|
562
679
|
case 'system':
|
|
563
680
|
case 'developer':
|
|
564
681
|
// `ModelMessage` has no system/developer role; build the part directly.
|
|
565
|
-
return {
|
|
682
|
+
return applySnapshotMetadata(message, {
|
|
566
683
|
id,
|
|
567
684
|
role: 'system',
|
|
568
685
|
parts: message.content
|
|
569
686
|
? [{ type: 'text', content: message.content }]
|
|
570
687
|
: [],
|
|
571
|
-
}
|
|
572
|
-
case 'reasoning':
|
|
573
|
-
|
|
688
|
+
})
|
|
689
|
+
case 'reasoning': {
|
|
690
|
+
const signature = encryptedValueFrom(message)
|
|
691
|
+
return applySnapshotMetadata(message, {
|
|
574
692
|
id,
|
|
575
693
|
role: 'assistant',
|
|
576
694
|
parts: message.content
|
|
577
|
-
? [
|
|
695
|
+
? [
|
|
696
|
+
{
|
|
697
|
+
type: 'thinking' as const,
|
|
698
|
+
content: message.content,
|
|
699
|
+
...(signature !== undefined ? { signature } : {}),
|
|
700
|
+
},
|
|
701
|
+
]
|
|
578
702
|
: [],
|
|
579
|
-
}
|
|
703
|
+
})
|
|
704
|
+
}
|
|
580
705
|
case 'activity':
|
|
581
706
|
default:
|
|
582
707
|
// `activity` (and any future role) has no text/parts equivalent today.
|
|
583
|
-
return
|
|
708
|
+
return applySnapshotMetadata(message, {
|
|
709
|
+
id,
|
|
710
|
+
role: 'assistant',
|
|
711
|
+
parts: [],
|
|
712
|
+
})
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
/** Copy snapshot metadata when it is a record. Rebuild createdAt from tanstack.createdAt. */
|
|
717
|
+
function applySnapshotMetadata(source: object, ui: UIMessage): UIMessage {
|
|
718
|
+
if (!('metadata' in source)) return ui
|
|
719
|
+
const raw = source.metadata
|
|
720
|
+
if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) return ui
|
|
721
|
+
const metadata = raw as NonNullable<UIMessage['metadata']>
|
|
722
|
+
const createdAtRaw = tanstackMetadata(metadata)?.createdAt
|
|
723
|
+
const createdAt =
|
|
724
|
+
typeof createdAtRaw === 'string' ? new Date(createdAtRaw) : undefined
|
|
725
|
+
const createdAtValid =
|
|
726
|
+
createdAt !== undefined && !Number.isNaN(createdAt.getTime())
|
|
727
|
+
return {
|
|
728
|
+
...ui,
|
|
729
|
+
metadata,
|
|
730
|
+
...(createdAtValid ? { createdAt } : {}),
|
|
584
731
|
}
|
|
585
732
|
}
|
|
586
733
|
|