@tanstack/ai 0.59.0 → 0.63.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 +1 -0
- package/dist/esm/activities/chat/adapter.d.ts +9 -0
- package/dist/esm/activities/chat/adapter.js +1 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- package/dist/esm/activities/chat/agents/define-agent.d.ts +17 -5
- package/dist/esm/activities/chat/agents/define-agent.js.map +1 -1
- package/dist/esm/activities/chat/agents/spawn.d.ts +2 -0
- package/dist/esm/activities/chat/agents/spawn.js +8 -5
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
- package/dist/esm/activities/chat/index.js +289 -78
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +35 -19
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +1 -0
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
- package/dist/esm/activities/chat/stream/message-updaters.js +11 -3
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +11 -10
- package/dist/esm/activities/chat/stream/processor.js +51 -28
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +16 -2
- package/dist/esm/activities/chat/tools/tool-calls.js +57 -16
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-definition.d.ts +4 -0
- package/dist/esm/activities/chat/tools/tool-definition.js +4 -0
- package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
- package/dist/esm/activities/embed/adapter.d.ts +7 -0
- package/dist/esm/activities/embed/adapter.js +1 -0
- package/dist/esm/activities/embed/adapter.js.map +1 -1
- package/dist/esm/activities/embed/index.js +2 -0
- package/dist/esm/activities/embed/index.js.map +1 -1
- package/dist/esm/activities/evaluate/adapter.d.ts +4 -0
- package/dist/esm/activities/evaluate/adapter.js.map +1 -1
- package/dist/esm/activities/evaluate/index.d.ts +4 -0
- package/dist/esm/activities/evaluate/index.js +3 -1
- package/dist/esm/activities/evaluate/index.js.map +1 -1
- package/dist/esm/activities/files/adapter.d.ts +97 -0
- package/dist/esm/activities/files/adapter.js +45 -0
- package/dist/esm/activities/files/adapter.js.map +1 -0
- package/dist/esm/activities/files/index.d.ts +66 -0
- package/dist/esm/activities/files/index.js +78 -0
- package/dist/esm/activities/files/index.js.map +1 -0
- package/dist/esm/activities/generateImage/adapter.d.ts +8 -0
- package/dist/esm/activities/generateImage/adapter.js +1 -0
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +2 -0
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.d.ts +8 -0
- package/dist/esm/activities/generateVideo/adapter.js +1 -0
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +3 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateWorld/adapter.d.ts +4 -2
- package/dist/esm/activities/generateWorld/adapter.js.map +1 -1
- package/dist/esm/activities/generateWorld/index.d.ts +4 -3
- package/dist/esm/activities/generateWorld/index.js +5 -4
- package/dist/esm/activities/generateWorld/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +6 -3
- package/dist/esm/activities/index.js +13 -11
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js +8 -8
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/client.d.ts +3 -1
- package/dist/esm/client.js +2 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +4 -3
- package/dist/esm/index.js +5 -3
- package/dist/esm/interrupt-resume.js +29 -4
- package/dist/esm/interrupt-resume.js.map +1 -1
- package/dist/esm/middlewares/otel.d.ts +5 -2
- package/dist/esm/middlewares/otel.js +114 -0
- package/dist/esm/middlewares/otel.js.map +1 -1
- package/dist/esm/types.d.ts +114 -14
- package/dist/esm/utilities/ag-ui-wire.js +39 -16
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/content-source.d.ts +60 -0
- package/dist/esm/utilities/content-source.js +85 -0
- package/dist/esm/utilities/content-source.js.map +1 -0
- package/dist/esm/utilities/provider-executed.d.ts +7 -0
- package/dist/esm/utilities/provider-executed.js +10 -1
- package/dist/esm/utilities/provider-executed.js.map +1 -1
- package/dist/esm/utilities/tool-result.d.ts +14 -3
- package/dist/esm/utilities/tool-result.js +26 -3
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +4 -4
- package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
- package/skills/ai-core/chat-experience/SKILL.md +134 -0
- package/skills/ai-core/media-generation/SKILL.md +8 -0
- package/skills/ai-core/tool-calling/SKILL.md +103 -0
- package/src/activities/chat/adapter.ts +10 -0
- package/src/activities/chat/agents/define-agent.ts +20 -3
- package/src/activities/chat/agents/spawn.ts +20 -12
- package/src/activities/chat/index.ts +458 -99
- package/src/activities/chat/messages.ts +52 -6
- package/src/activities/chat/middleware/types.ts +1 -0
- package/src/activities/chat/stream/message-updaters.ts +27 -2
- package/src/activities/chat/stream/processor.ts +81 -49
- package/src/activities/chat/tools/tool-calls.ts +104 -9
- package/src/activities/chat/tools/tool-definition.ts +8 -0
- package/src/activities/embed/adapter.ts +7 -0
- package/src/activities/embed/index.ts +5 -0
- package/src/activities/evaluate/adapter.ts +4 -0
- package/src/activities/evaluate/index.ts +6 -0
- package/src/activities/files/adapter.ts +120 -0
- package/src/activities/files/index.ts +113 -0
- package/src/activities/generateImage/adapter.ts +8 -0
- package/src/activities/generateImage/index.ts +4 -0
- package/src/activities/generateVideo/adapter.ts +8 -0
- package/src/activities/generateVideo/index.ts +7 -0
- package/src/activities/generateWorld/adapter.ts +4 -2
- package/src/activities/generateWorld/index.ts +7 -6
- package/src/activities/index.ts +25 -1
- package/src/activities/summarize/chat-stream-summarize.ts +22 -12
- package/src/client.ts +8 -0
- package/src/index.ts +17 -0
- package/src/interrupt-resume.ts +55 -4
- package/src/middlewares/otel.ts +161 -3
- package/src/types.ts +114 -14
- package/src/utilities/ag-ui-wire.ts +72 -17
- package/src/utilities/content-source.ts +138 -0
- package/src/utilities/provider-executed.ts +13 -0
- package/src/utilities/tool-result.ts +45 -3
|
@@ -1,9 +1,16 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
isAssistantSegmentOf,
|
|
3
|
+
isProviderExecutedToolCall,
|
|
4
|
+
} from '../../utilities/provider-executed'
|
|
2
5
|
import {
|
|
3
6
|
isContentPartArray,
|
|
7
|
+
isToolResultOutcome,
|
|
4
8
|
normalizeToolResult,
|
|
5
9
|
} from '../../utilities/tool-result'
|
|
6
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
tanstackMetadata,
|
|
12
|
+
withTanstackMetadata,
|
|
13
|
+
} from '../../utilities/merge-metadata'
|
|
7
14
|
import {
|
|
8
15
|
splitSubagentWire,
|
|
9
16
|
subagentWireText,
|
|
@@ -26,6 +33,7 @@ import type {
|
|
|
26
33
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
27
34
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
28
35
|
}
|
|
36
|
+
|
|
29
37
|
// ===========================
|
|
30
38
|
// Message Converters
|
|
31
39
|
// ===========================
|
|
@@ -543,6 +551,7 @@ function assistantMetadata(
|
|
|
543
551
|
const tanstack: TanStackMessageMetadata = {}
|
|
544
552
|
if (previous?.model !== undefined) tanstack.model = previous.model
|
|
545
553
|
if (previous?.runId !== undefined) tanstack.runId = previous.runId
|
|
554
|
+
if (previous?.run?.id !== undefined) tanstack.run = { id: previous.run.id }
|
|
546
555
|
if (previous?.signature !== undefined) tanstack.signature = previous.signature
|
|
547
556
|
if (fromParts.length > 0) tanstack.uiResources = fromParts
|
|
548
557
|
const result = { ...current }
|
|
@@ -726,6 +735,11 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
726
735
|
(part.state === 'complete' || part.state === 'error') &&
|
|
727
736
|
!emittedToolResultIds.has(part.toolCallId)
|
|
728
737
|
) {
|
|
738
|
+
const metadata =
|
|
739
|
+
part.outcome === undefined
|
|
740
|
+
? part.metadata
|
|
741
|
+
: withTanstackMetadata(part, { toolResultOutcome: part.outcome })
|
|
742
|
+
.metadata
|
|
729
743
|
messageList.push({
|
|
730
744
|
...(part.id !== undefined && { id: part.id }),
|
|
731
745
|
...optionalCreatedAt(part),
|
|
@@ -733,7 +747,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
733
747
|
content: part.content,
|
|
734
748
|
toolCallId: part.toolCallId,
|
|
735
749
|
...(part.name !== undefined && { name: part.name }),
|
|
736
|
-
...(
|
|
750
|
+
...(metadata !== undefined && { metadata }),
|
|
737
751
|
...(part.error !== undefined && { error: part.error }),
|
|
738
752
|
})
|
|
739
753
|
emittedToolResultIds.add(part.toolCallId)
|
|
@@ -838,6 +852,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
838
852
|
: 'User denied this action',
|
|
839
853
|
}),
|
|
840
854
|
toolCallId: part.id,
|
|
855
|
+
...(approved === false && {
|
|
856
|
+
metadata: { tanstack: { toolResultOutcome: 'denied' } },
|
|
857
|
+
}),
|
|
841
858
|
})
|
|
842
859
|
emittedToolResultIds.add(part.id)
|
|
843
860
|
}
|
|
@@ -905,11 +922,21 @@ export function modelMessageToUIMessage(
|
|
|
905
922
|
}
|
|
906
923
|
parts.push(structuredOutput)
|
|
907
924
|
} else if (modelMessage.role === 'tool' && modelMessage.toolCallId) {
|
|
925
|
+
const rawToolResultOutcome =
|
|
926
|
+
tanstackMetadata(modelMessage)?.toolResultOutcome
|
|
927
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
928
|
+
? rawToolResultOutcome
|
|
929
|
+
: undefined
|
|
930
|
+
const resultState =
|
|
931
|
+
modelMessage.error === undefined && toolResultOutcome === undefined
|
|
932
|
+
? 'complete'
|
|
933
|
+
: 'error'
|
|
908
934
|
parts.push({
|
|
909
935
|
type: 'tool-result',
|
|
910
936
|
toolCallId: modelMessage.toolCallId,
|
|
911
937
|
content: toolResultContent(modelMessage.content),
|
|
912
|
-
state:
|
|
938
|
+
state: resultState,
|
|
939
|
+
...(toolResultOutcome !== undefined && { outcome: toolResultOutcome }),
|
|
913
940
|
...(modelMessage.id !== undefined && { id: modelMessage.id }),
|
|
914
941
|
...(modelMessage.name !== undefined && { name: modelMessage.name }),
|
|
915
942
|
...(modelMessage.metadata !== undefined && {
|
|
@@ -1225,6 +1252,14 @@ export function modelMessagesToUIMessages(
|
|
|
1225
1252
|
currentAssistantMessage.role === 'assistant'
|
|
1226
1253
|
) {
|
|
1227
1254
|
const content = toolResultContent(msg.content)
|
|
1255
|
+
const rawToolResultOutcome = tanstackMetadata(msg)?.toolResultOutcome
|
|
1256
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
1257
|
+
? rawToolResultOutcome
|
|
1258
|
+
: undefined
|
|
1259
|
+
const resultState =
|
|
1260
|
+
msg.error === undefined && toolResultOutcome === undefined
|
|
1261
|
+
? 'complete'
|
|
1262
|
+
: 'error'
|
|
1228
1263
|
const toolCallPart = currentAssistantMessage.parts.find(
|
|
1229
1264
|
(part): part is ToolCallPart =>
|
|
1230
1265
|
part.type === 'tool-call' && part.id === msg.toolCallId,
|
|
@@ -1235,14 +1270,17 @@ export function modelMessagesToUIMessages(
|
|
|
1235
1270
|
typeof content === 'string'
|
|
1236
1271
|
? parseToolResultContent(content)
|
|
1237
1272
|
: content
|
|
1238
|
-
toolCallPart.state =
|
|
1273
|
+
toolCallPart.state = resultState
|
|
1239
1274
|
}
|
|
1240
1275
|
|
|
1241
1276
|
currentAssistantMessage.parts.push({
|
|
1242
1277
|
type: 'tool-result',
|
|
1243
1278
|
toolCallId: msg.toolCallId,
|
|
1244
1279
|
content,
|
|
1245
|
-
state:
|
|
1280
|
+
state: resultState,
|
|
1281
|
+
...(toolResultOutcome !== undefined && {
|
|
1282
|
+
outcome: toolResultOutcome,
|
|
1283
|
+
}),
|
|
1246
1284
|
...(msg.id !== undefined &&
|
|
1247
1285
|
msg.id !== currentAssistantMessage.id && { id: msg.id }),
|
|
1248
1286
|
...(msg.name !== undefined && { name: msg.name }),
|
|
@@ -1259,6 +1297,14 @@ export function modelMessagesToUIMessages(
|
|
|
1259
1297
|
// Regular message. Preserve a persisted stable id so a hydrated message
|
|
1260
1298
|
// keeps the same identity as its live stream (enables in-place resume).
|
|
1261
1299
|
const uiMessage = modelMessageToUIMessage(msg, msg.id)
|
|
1300
|
+
if (
|
|
1301
|
+
msg.role === 'assistant' &&
|
|
1302
|
+
currentAssistantMessage &&
|
|
1303
|
+
isAssistantSegmentOf(msg.id, currentAssistantMessage.id)
|
|
1304
|
+
) {
|
|
1305
|
+
currentAssistantMessage.parts.push(...uiMessage.parts)
|
|
1306
|
+
continue
|
|
1307
|
+
}
|
|
1262
1308
|
uiMessages.push(uiMessage)
|
|
1263
1309
|
|
|
1264
1310
|
// Track assistant messages for potential tool result merging
|
|
@@ -344,6 +344,7 @@ export interface ChatMiddlewareConfig {
|
|
|
344
344
|
export interface ChatResumeToolState {
|
|
345
345
|
approvals?: ReadonlyMap<string, ToolApprovalResolution> | undefined
|
|
346
346
|
clientToolResults?: ReadonlyMap<string, unknown> | undefined
|
|
347
|
+
clientToolErrors?: ReadonlyMap<string, string> | undefined
|
|
347
348
|
genericInterrupts?:
|
|
348
349
|
| ReadonlyMap<string, ChatResumeGenericResolution>
|
|
349
350
|
| undefined
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
StructuredOutputPart,
|
|
12
12
|
ThinkingPart,
|
|
13
13
|
ToolCallPart,
|
|
14
|
+
ToolResultOutcome,
|
|
14
15
|
ToolResultPart,
|
|
15
16
|
UIMessage,
|
|
16
17
|
} from '../../../types'
|
|
@@ -117,6 +118,7 @@ export function updateToolResultPart(
|
|
|
117
118
|
content: string | Array<ContentPart>,
|
|
118
119
|
state: ToolResultState,
|
|
119
120
|
error?: string,
|
|
121
|
+
outcome?: ToolResultOutcome,
|
|
120
122
|
): Array<UIMessage> {
|
|
121
123
|
return messages.map((msg) => {
|
|
122
124
|
if (msg.id !== messageId) {
|
|
@@ -134,6 +136,7 @@ export function updateToolResultPart(
|
|
|
134
136
|
toolCallId,
|
|
135
137
|
content,
|
|
136
138
|
state,
|
|
139
|
+
...(outcome !== undefined && { outcome }),
|
|
137
140
|
...(error && { error }),
|
|
138
141
|
}
|
|
139
142
|
|
|
@@ -455,15 +458,37 @@ export function updateThinkingPart(
|
|
|
455
458
|
}
|
|
456
459
|
|
|
457
460
|
const parts = [...msg.parts]
|
|
458
|
-
|
|
461
|
+
let thinkingPartIndex = parts.findIndex(
|
|
459
462
|
(p) => p.type === 'thinking' && p.stepId === stepId,
|
|
460
463
|
)
|
|
461
464
|
|
|
465
|
+
// A hydrated message carries its thinking without a stepId: the stored form
|
|
466
|
+
// has no field for one, so `modelMessageToUIMessage` cannot put it back.
|
|
467
|
+
// When a run is rejoined mid-stream the replayed reasoning is keyed by
|
|
468
|
+
// stepId, matches nothing, and gets appended -- leaving a second thinking
|
|
469
|
+
// part sitting after the answer text. Adopt the first stepId-less thinking
|
|
470
|
+
// part instead, so the replay lands on the part it belongs to. Live
|
|
471
|
+
// streaming always writes a stepId, so the only parts this can match are
|
|
472
|
+
// hydrated ones.
|
|
473
|
+
let adopted: ThinkingPart | undefined
|
|
474
|
+
if (thinkingPartIndex < 0) {
|
|
475
|
+
thinkingPartIndex = parts.findIndex(
|
|
476
|
+
(p) => p.type === 'thinking' && p.stepId === undefined,
|
|
477
|
+
)
|
|
478
|
+
const candidate = parts[thinkingPartIndex]
|
|
479
|
+
if (candidate?.type === 'thinking') adopted = candidate
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// Keep the signature the hydrated part already had when this update does
|
|
483
|
+
// not carry one; losing it would strip the provider's encrypted reasoning
|
|
484
|
+
// from a message that is about to be sent back.
|
|
485
|
+
const nextSignature = signature ?? adopted?.signature
|
|
486
|
+
|
|
462
487
|
const thinkingPart: ThinkingPart = {
|
|
463
488
|
type: 'thinking',
|
|
464
489
|
content,
|
|
465
490
|
stepId,
|
|
466
|
-
...(
|
|
491
|
+
...(nextSignature && { signature: nextSignature }),
|
|
467
492
|
}
|
|
468
493
|
|
|
469
494
|
if (thinkingPartIndex >= 0) {
|
|
@@ -25,14 +25,22 @@ import {
|
|
|
25
25
|
uiMessageToModelMessages,
|
|
26
26
|
} from '../messages.js'
|
|
27
27
|
import { runErrorEventToError } from '../../../utilities/errors'
|
|
28
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
isAssistantSegmentOf,
|
|
30
|
+
isProviderExecutedToolCall,
|
|
31
|
+
} from '../../../utilities/provider-executed'
|
|
29
32
|
import {
|
|
30
33
|
mergeMetadata,
|
|
31
34
|
tanstackMetadata,
|
|
32
35
|
} from '../../../utilities/merge-metadata'
|
|
33
36
|
import { getChunkRunId } from '../../../utilities/chunk-ids'
|
|
34
37
|
import type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'
|
|
35
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
isContentPartArray,
|
|
40
|
+
isToolResultOutcome,
|
|
41
|
+
normalizeToolResult,
|
|
42
|
+
toolResultErrorText,
|
|
43
|
+
} from '../../../utilities/tool-result'
|
|
36
44
|
import { defaultJSONParser } from './json-parser'
|
|
37
45
|
import {
|
|
38
46
|
appendStructuredOutputDelta,
|
|
@@ -175,6 +183,23 @@ function interruptBatchHasGeneric(interrupts: Array<Interrupt>): boolean {
|
|
|
175
183
|
})
|
|
176
184
|
}
|
|
177
185
|
|
|
186
|
+
/**
|
|
187
|
+
* The canonical arguments string for a `TOOL_CALL_END.input`, or `undefined`
|
|
188
|
+
* when JSON cannot carry it: `JSON.stringify` returns `undefined` (despite
|
|
189
|
+
* its declared type) for a top-level function or symbol and throws on BigInt
|
|
190
|
+
* and circular references. `null` is not tool arguments either, so it also
|
|
191
|
+
* keeps the streamed value instead of writing `arguments = "null"`.
|
|
192
|
+
*/
|
|
193
|
+
function serializeToolInput(input: unknown): string | undefined {
|
|
194
|
+
if (input === undefined || input === null) return undefined
|
|
195
|
+
try {
|
|
196
|
+
const serialized: unknown = JSON.stringify(input)
|
|
197
|
+
return typeof serialized === 'string' ? serialized : undefined
|
|
198
|
+
} catch {
|
|
199
|
+
return undefined
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
178
203
|
/**
|
|
179
204
|
* StreamProcessor - State machine for processing AI response streams
|
|
180
205
|
*
|
|
@@ -1518,20 +1543,30 @@ export class StreamProcessor {
|
|
|
1518
1543
|
pending.push(msg)
|
|
1519
1544
|
continue
|
|
1520
1545
|
}
|
|
1546
|
+
let next = msg
|
|
1521
1547
|
if (
|
|
1522
1548
|
msg.role === 'assistant' &&
|
|
1523
1549
|
pending.length > 0 &&
|
|
1524
1550
|
!isToolResultOnly(msg)
|
|
1525
1551
|
) {
|
|
1526
|
-
|
|
1552
|
+
next = {
|
|
1527
1553
|
...msg,
|
|
1528
1554
|
parts: [...pending.flatMap(thinkingParts), ...msg.parts],
|
|
1529
|
-
}
|
|
1555
|
+
}
|
|
1530
1556
|
pending = []
|
|
1557
|
+
} else {
|
|
1558
|
+
flushPending()
|
|
1559
|
+
}
|
|
1560
|
+
const prev = out.at(-1)
|
|
1561
|
+
if (
|
|
1562
|
+
prev?.role === 'assistant' &&
|
|
1563
|
+
next.role === 'assistant' &&
|
|
1564
|
+
isAssistantSegmentOf(next.id, prev.id)
|
|
1565
|
+
) {
|
|
1566
|
+
out[out.length - 1] = { ...prev, parts: [...prev.parts, ...next.parts] }
|
|
1531
1567
|
continue
|
|
1532
1568
|
}
|
|
1533
|
-
|
|
1534
|
-
out.push(msg)
|
|
1569
|
+
out.push(next)
|
|
1535
1570
|
}
|
|
1536
1571
|
flushPending()
|
|
1537
1572
|
return out
|
|
@@ -1705,9 +1740,7 @@ export class StreamProcessor {
|
|
|
1705
1740
|
}
|
|
1706
1741
|
}
|
|
1707
1742
|
const errorText =
|
|
1708
|
-
result.state === 'error'
|
|
1709
|
-
? this.extractToolResultError(output)
|
|
1710
|
-
: undefined
|
|
1743
|
+
result.state === 'error' ? toolResultErrorText(output) : undefined
|
|
1711
1744
|
next = {
|
|
1712
1745
|
...next,
|
|
1713
1746
|
output: errorText ? { error: errorText } : output,
|
|
@@ -1939,11 +1972,13 @@ export class StreamProcessor {
|
|
|
1939
1972
|
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
1940
1973
|
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
1941
1974
|
*
|
|
1942
|
-
* If TOOL_CALL_END carries parsed `input`,
|
|
1943
|
-
*
|
|
1944
|
-
*
|
|
1945
|
-
* server_tool_use / web_search — issue #839) and
|
|
1946
|
-
*
|
|
1975
|
+
* If TOOL_CALL_END carries parsed `input`, it is the canonical arguments:
|
|
1976
|
+
* write it into the accumulated string and override the rendered part's
|
|
1977
|
+
* `input` with it. That covers adapters that deliver the whole input on END
|
|
1978
|
+
* (e.g. Anthropic server_tool_use / web_search — issue #839) and adapters
|
|
1979
|
+
* that stream the wire arguments and then normalize them (OpenAI strict-mode
|
|
1980
|
+
* null widening, undone by the adapter after #939), so `arguments` and
|
|
1981
|
+
* `input` never disagree on the persisted part.
|
|
1947
1982
|
*
|
|
1948
1983
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
1949
1984
|
*/
|
|
@@ -1966,16 +2001,15 @@ export class StreamProcessor {
|
|
|
1966
2001
|
// Transition the tool call to input-complete (the authoritative completion signal)
|
|
1967
2002
|
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)
|
|
1968
2003
|
if (existingToolCall && existingToolCall.state !== 'input-complete') {
|
|
1969
|
-
//
|
|
1970
|
-
//
|
|
1971
|
-
//
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
}
|
|
2004
|
+
// The parsed input replaces the accumulated arguments string, so
|
|
2005
|
+
// completeToolCall's strict parse surfaces the canonical value on the
|
|
2006
|
+
// ToolCallPart even when the streamed deltas carried a provider-side
|
|
2007
|
+
// reshaping of it. An input JSON cannot carry is not canonical: both
|
|
2008
|
+
// `arguments` and `input` then stay with the streamed value, so the
|
|
2009
|
+
// two never disagree.
|
|
2010
|
+
const serializedInput = serializeToolInput(input)
|
|
2011
|
+
if (serializedInput !== undefined) {
|
|
2012
|
+
existingToolCall.arguments = serializedInput
|
|
1979
2013
|
}
|
|
1980
2014
|
|
|
1981
2015
|
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)
|
|
@@ -1984,7 +2018,7 @@ export class StreamProcessor {
|
|
|
1984
2018
|
// Canonicalize on the parsed input: overrides the accumulated-args parse
|
|
1985
2019
|
// that completeToolCall wrote (adapters may coerce values differently
|
|
1986
2020
|
// between streamed args and the final structured input).
|
|
1987
|
-
if (
|
|
2021
|
+
if (serializedInput !== undefined) {
|
|
1988
2022
|
existingToolCall.parsedArguments = input
|
|
1989
2023
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
1990
2024
|
id: existingToolCall.id,
|
|
@@ -2001,18 +2035,6 @@ export class StreamProcessor {
|
|
|
2001
2035
|
}
|
|
2002
2036
|
}
|
|
2003
2037
|
|
|
2004
|
-
private extractToolResultError(output: unknown): string {
|
|
2005
|
-
if (
|
|
2006
|
-
output &&
|
|
2007
|
-
typeof output === 'object' &&
|
|
2008
|
-
'error' in output &&
|
|
2009
|
-
typeof output.error === 'string'
|
|
2010
|
-
) {
|
|
2011
|
-
return output.error
|
|
2012
|
-
}
|
|
2013
|
-
return typeof output === 'string' ? output : 'Tool execution failed'
|
|
2014
|
-
}
|
|
2015
|
-
|
|
2016
2038
|
/**
|
|
2017
2039
|
* Handle TOOL_CALL_RESULT event (AG-UI spec).
|
|
2018
2040
|
*
|
|
@@ -2040,9 +2062,14 @@ export class StreamProcessor {
|
|
|
2040
2062
|
if (!messageId) return
|
|
2041
2063
|
|
|
2042
2064
|
const extra = chunk as AdapterYieldChunk
|
|
2065
|
+
const rawToolResultOutcome = tanstackMetadata(chunk)?.toolResultOutcome
|
|
2066
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
2067
|
+
? rawToolResultOutcome
|
|
2068
|
+
: undefined
|
|
2043
2069
|
const isOutputError =
|
|
2044
2070
|
extra.state === 'output-error' ||
|
|
2045
|
-
tanstackMetadata(chunk)?.state === 'output-error'
|
|
2071
|
+
tanstackMetadata(chunk)?.state === 'output-error' ||
|
|
2072
|
+
toolResultOutcome !== undefined
|
|
2046
2073
|
|
|
2047
2074
|
// Step 1: Update the tool-call part's output field
|
|
2048
2075
|
let output: unknown
|
|
@@ -2067,9 +2094,14 @@ export class StreamProcessor {
|
|
|
2067
2094
|
this.messages,
|
|
2068
2095
|
messageId,
|
|
2069
2096
|
chunk.toolCallId,
|
|
2070
|
-
|
|
2097
|
+
// The server sends a ContentPart[] result as a JSON string. Keep the
|
|
2098
|
+
// parsed array so uiMessagesToWire can carry it in metadata.
|
|
2099
|
+
isContentPartArray(output)
|
|
2100
|
+
? output
|
|
2101
|
+
: aguiContentToContentParts(chunk.content),
|
|
2071
2102
|
resultState,
|
|
2072
|
-
resultState === 'error' ?
|
|
2103
|
+
resultState === 'error' ? toolResultErrorText(output) : undefined,
|
|
2104
|
+
toolResultOutcome,
|
|
2073
2105
|
)
|
|
2074
2106
|
this.emitMessagesChange()
|
|
2075
2107
|
}
|
|
@@ -2676,11 +2708,11 @@ export class StreamProcessor {
|
|
|
2676
2708
|
toolCall.parsedArguments = undefined
|
|
2677
2709
|
}
|
|
2678
2710
|
|
|
2679
|
-
// Don't downgrade the rendered part of a call that already reached
|
|
2680
|
-
// terminal 'error' state (e.g.
|
|
2711
|
+
// Don't downgrade the rendered part of a call that already reached a
|
|
2712
|
+
// terminal 'error' or 'complete' state (e.g. a tool result arrived
|
|
2681
2713
|
// without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream
|
|
2682
|
-
// safety net must not clobber a
|
|
2683
|
-
if (this.
|
|
2714
|
+
// safety net must not clobber a finished call back to 'input-complete'.
|
|
2715
|
+
if (this.isToolCallPartTerminal(toolCall.id)) {
|
|
2684
2716
|
return
|
|
2685
2717
|
}
|
|
2686
2718
|
|
|
@@ -2726,11 +2758,11 @@ export class StreamProcessor {
|
|
|
2726
2758
|
}
|
|
2727
2759
|
|
|
2728
2760
|
/**
|
|
2729
|
-
* Whether the rendered tool-call part for the given id has reached
|
|
2730
|
-
* terminal 'error' state. Used to prevent the completion
|
|
2731
|
-
* downgrading a
|
|
2761
|
+
* Whether the rendered tool-call part for the given id has reached a
|
|
2762
|
+
* terminal 'error' or 'complete' state. Used to prevent the completion
|
|
2763
|
+
* safety net from downgrading a finished call back to 'input-complete'.
|
|
2732
2764
|
*/
|
|
2733
|
-
private
|
|
2765
|
+
private isToolCallPartTerminal(toolCallId: string): boolean {
|
|
2734
2766
|
// `initialMessages` may be ModelMessage-shaped (no `parts`) — e.g. the
|
|
2735
2767
|
// common pattern of seeding a processor with the same messages passed to
|
|
2736
2768
|
// `chat()`. Guard the access so iterating them never throws.
|
|
@@ -2740,7 +2772,7 @@ export class StreamProcessor {
|
|
|
2740
2772
|
(part) =>
|
|
2741
2773
|
part.type === 'tool-call' &&
|
|
2742
2774
|
part.id === toolCallId &&
|
|
2743
|
-
part.state === 'error',
|
|
2775
|
+
(part.state === 'error' || part.state === 'complete'),
|
|
2744
2776
|
),
|
|
2745
2777
|
)
|
|
2746
2778
|
}
|