@tanstack/ai 0.61.0 → 0.64.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/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 +10 -33
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -1
- package/dist/esm/activities/chat/index.d.ts +2 -0
- package/dist/esm/activities/chat/index.js +166 -60
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.js +43 -22
- 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 +6 -2
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +11 -9
- package/dist/esm/activities/chat/stream/processor.js +38 -18
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/schema-converter.d.ts +8 -0
- package/dist/esm/activities/chat/tools/schema-converter.js +6 -5
- package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +20 -3
- package/dist/esm/activities/chat/tools/tool-calls.js +126 -36
- 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/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/generateSpeech/index.d.ts +1 -1
- package/dist/esm/activities/generateSpeech/index.js +1 -1
- package/dist/esm/activities/generateSpeech/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/adapter.d.ts +14 -6
- package/dist/esm/activities/generateVideo/adapter.js +6 -3
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.d.ts +5 -4
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVideo/snap.d.ts +12 -3
- package/dist/esm/activities/generateVideo/snap.js +47 -8
- package/dist/esm/activities/generateVideo/snap.js.map +1 -1
- package/dist/esm/activities/generateVoice/index.d.ts +1 -1
- package/dist/esm/activities/generateVoice/index.js +1 -1
- package/dist/esm/activities/generateVoice/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +2 -2
- package/dist/esm/activities/index.js +2 -2
- package/dist/esm/adapter-internals.d.ts +1 -0
- package/dist/esm/adapter-internals.js +2 -1
- package/dist/esm/client.d.ts +1 -1
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- 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 +70 -4
- package/dist/esm/utilities/ag-ui-wire.js +8 -4
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/merge-streams.d.ts +6 -0
- package/dist/esm/utilities/merge-streams.js +37 -0
- package/dist/esm/utilities/merge-streams.js.map +1 -0
- package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
- package/dist/esm/utilities/reasoning-encrypted-value.js +11 -1
- package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -1
- package/dist/esm/utilities/tool-result.d.ts +2 -1
- package/dist/esm/utilities/tool-result.js +4 -1
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/chat-experience/SKILL.md +120 -0
- package/skills/ai-core/media-generation/SKILL.md +3 -3
- package/skills/ai-core/tool-calling/SKILL.md +103 -0
- package/src/activities/chat/agents/define-agent.ts +20 -3
- package/src/activities/chat/agents/spawn.ts +22 -43
- package/src/activities/chat/index.ts +248 -65
- package/src/activities/chat/messages.ts +54 -7
- package/src/activities/chat/middleware/types.ts +1 -0
- package/src/activities/chat/stream/message-updaters.ts +8 -0
- package/src/activities/chat/stream/processor.ts +57 -27
- package/src/activities/chat/tools/schema-converter.ts +17 -5
- package/src/activities/chat/tools/tool-calls.ts +215 -68
- package/src/activities/chat/tools/tool-definition.ts +8 -0
- package/src/activities/evaluate/adapter.ts +4 -0
- package/src/activities/evaluate/index.ts +6 -0
- package/src/activities/generateSpeech/index.ts +1 -1
- package/src/activities/generateVideo/adapter.ts +21 -7
- package/src/activities/generateVideo/index.ts +5 -4
- package/src/activities/generateVideo/snap.ts +64 -6
- package/src/activities/generateVoice/index.ts +1 -1
- package/src/activities/index.ts +2 -1
- package/src/adapter-internals.ts +1 -0
- package/src/client.ts +1 -0
- package/src/index.ts +1 -0
- package/src/interrupt-resume.ts +55 -4
- package/src/middlewares/otel.ts +161 -3
- package/src/types.ts +66 -5
- package/src/utilities/ag-ui-wire.ts +17 -2
- package/src/utilities/merge-streams.ts +34 -0
- package/src/utilities/reasoning-encrypted-value.ts +12 -0
- package/src/utilities/tool-result.ts +7 -1
|
@@ -4,9 +4,14 @@ import {
|
|
|
4
4
|
} from '../../utilities/provider-executed'
|
|
5
5
|
import {
|
|
6
6
|
isContentPartArray,
|
|
7
|
+
isToolResultOutcome,
|
|
7
8
|
normalizeToolResult,
|
|
8
9
|
} from '../../utilities/tool-result'
|
|
9
|
-
import {
|
|
10
|
+
import {
|
|
11
|
+
tanstackMetadata,
|
|
12
|
+
withTanstackMetadata,
|
|
13
|
+
} from '../../utilities/merge-metadata'
|
|
14
|
+
import { isRedactedThinkingId } from '../../utilities/reasoning-encrypted-value'
|
|
10
15
|
import {
|
|
11
16
|
splitSubagentWire,
|
|
12
17
|
subagentWireText,
|
|
@@ -29,6 +34,7 @@ import type {
|
|
|
29
34
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
30
35
|
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
31
36
|
}
|
|
37
|
+
|
|
32
38
|
// ===========================
|
|
33
39
|
// Message Converters
|
|
34
40
|
// ===========================
|
|
@@ -67,6 +73,13 @@ function encryptedValueFrom(value: object): string | undefined {
|
|
|
67
73
|
return nonEmptyString(tanstackMetadata(value)?.signature)
|
|
68
74
|
}
|
|
69
75
|
|
|
76
|
+
/** `{ redacted: true }` when a reasoning message's id marks a redacted block. */
|
|
77
|
+
function redactedFrom(value: object) {
|
|
78
|
+
return 'id' in value && isRedactedThinkingId(value.id)
|
|
79
|
+
? { redacted: true }
|
|
80
|
+
: {}
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
function toolCallFromWire(toolCall: ToolCall, bag: unknown): ToolCall {
|
|
71
84
|
const fromBag =
|
|
72
85
|
bag != null && typeof bag === 'object' && !Array.isArray(bag)
|
|
@@ -241,7 +254,7 @@ function convertOwnMessages(
|
|
|
241
254
|
}
|
|
242
255
|
|
|
243
256
|
const modelMessages: Array<ModelMessage> = []
|
|
244
|
-
let pendingThinking:
|
|
257
|
+
let pendingThinking: NonNullable<ModelMessage['thinking']> = []
|
|
245
258
|
for (const msg of messages) {
|
|
246
259
|
if ('parts' in msg) {
|
|
247
260
|
modelMessages.push(...uiMessageToModelMessages(msg))
|
|
@@ -268,6 +281,7 @@ function convertOwnMessages(
|
|
|
268
281
|
pendingThinking.push({
|
|
269
282
|
content: typeof content === 'string' ? content : '',
|
|
270
283
|
...(signature !== undefined ? { signature } : {}),
|
|
284
|
+
...redactedFrom(msg),
|
|
271
285
|
})
|
|
272
286
|
}
|
|
273
287
|
continue
|
|
@@ -546,6 +560,7 @@ function assistantMetadata(
|
|
|
546
560
|
const tanstack: TanStackMessageMetadata = {}
|
|
547
561
|
if (previous?.model !== undefined) tanstack.model = previous.model
|
|
548
562
|
if (previous?.runId !== undefined) tanstack.runId = previous.runId
|
|
563
|
+
if (previous?.run?.id !== undefined) tanstack.run = { id: previous.run.id }
|
|
549
564
|
if (previous?.signature !== undefined) tanstack.signature = previous.signature
|
|
550
565
|
if (fromParts.length > 0) tanstack.uiResources = fromParts
|
|
551
566
|
const result = { ...current }
|
|
@@ -657,7 +672,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
657
672
|
// shared UI id on each one so persistence can retain the original identity.
|
|
658
673
|
const messageList: Array<ModelMessage> = []
|
|
659
674
|
let current = createSegment()
|
|
660
|
-
let pendingThinking:
|
|
675
|
+
let pendingThinking: NonNullable<ModelMessage['thinking']> = []
|
|
661
676
|
|
|
662
677
|
// Track emitted tool result IDs to avoid duplicates.
|
|
663
678
|
// A tool call can have BOTH an explicit tool-result part AND an output
|
|
@@ -729,6 +744,11 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
729
744
|
(part.state === 'complete' || part.state === 'error') &&
|
|
730
745
|
!emittedToolResultIds.has(part.toolCallId)
|
|
731
746
|
) {
|
|
747
|
+
const metadata =
|
|
748
|
+
part.outcome === undefined
|
|
749
|
+
? part.metadata
|
|
750
|
+
: withTanstackMetadata(part, { toolResultOutcome: part.outcome })
|
|
751
|
+
.metadata
|
|
732
752
|
messageList.push({
|
|
733
753
|
...(part.id !== undefined && { id: part.id }),
|
|
734
754
|
...optionalCreatedAt(part),
|
|
@@ -736,7 +756,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
736
756
|
content: part.content,
|
|
737
757
|
toolCallId: part.toolCallId,
|
|
738
758
|
...(part.name !== undefined && { name: part.name }),
|
|
739
|
-
...(
|
|
759
|
+
...(metadata !== undefined && { metadata }),
|
|
740
760
|
...(part.error !== undefined && { error: part.error }),
|
|
741
761
|
})
|
|
742
762
|
emittedToolResultIds.add(part.toolCallId)
|
|
@@ -753,6 +773,7 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
753
773
|
pendingThinking.push({
|
|
754
774
|
content: part.content,
|
|
755
775
|
...(part.signature && { signature: part.signature }),
|
|
776
|
+
...(part.redacted && { redacted: true }),
|
|
756
777
|
})
|
|
757
778
|
}
|
|
758
779
|
break
|
|
@@ -841,6 +862,9 @@ function buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {
|
|
|
841
862
|
: 'User denied this action',
|
|
842
863
|
}),
|
|
843
864
|
toolCallId: part.id,
|
|
865
|
+
...(approved === false && {
|
|
866
|
+
metadata: { tanstack: { toolResultOutcome: 'denied' } },
|
|
867
|
+
}),
|
|
844
868
|
})
|
|
845
869
|
emittedToolResultIds.add(part.id)
|
|
846
870
|
}
|
|
@@ -884,6 +908,7 @@ export function modelMessageToUIMessage(
|
|
|
884
908
|
type: 'thinking',
|
|
885
909
|
content: thinking.content,
|
|
886
910
|
...(thinking.signature && { signature: thinking.signature }),
|
|
911
|
+
...(thinking.redacted && { redacted: true }),
|
|
887
912
|
})
|
|
888
913
|
}
|
|
889
914
|
}
|
|
@@ -908,11 +933,21 @@ export function modelMessageToUIMessage(
|
|
|
908
933
|
}
|
|
909
934
|
parts.push(structuredOutput)
|
|
910
935
|
} else if (modelMessage.role === 'tool' && modelMessage.toolCallId) {
|
|
936
|
+
const rawToolResultOutcome =
|
|
937
|
+
tanstackMetadata(modelMessage)?.toolResultOutcome
|
|
938
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
939
|
+
? rawToolResultOutcome
|
|
940
|
+
: undefined
|
|
941
|
+
const resultState =
|
|
942
|
+
modelMessage.error === undefined && toolResultOutcome === undefined
|
|
943
|
+
? 'complete'
|
|
944
|
+
: 'error'
|
|
911
945
|
parts.push({
|
|
912
946
|
type: 'tool-result',
|
|
913
947
|
toolCallId: modelMessage.toolCallId,
|
|
914
948
|
content: toolResultContent(modelMessage.content),
|
|
915
|
-
state:
|
|
949
|
+
state: resultState,
|
|
950
|
+
...(toolResultOutcome !== undefined && { outcome: toolResultOutcome }),
|
|
916
951
|
...(modelMessage.id !== undefined && { id: modelMessage.id }),
|
|
917
952
|
...(modelMessage.name !== undefined && { name: modelMessage.name }),
|
|
918
953
|
...(modelMessage.metadata !== undefined && {
|
|
@@ -1097,6 +1132,7 @@ export function aguiSnapshotMessageToUIMessage(
|
|
|
1097
1132
|
type: 'thinking' as const,
|
|
1098
1133
|
content,
|
|
1099
1134
|
...(signature !== undefined ? { signature } : {}),
|
|
1135
|
+
...redactedFrom(message),
|
|
1100
1136
|
},
|
|
1101
1137
|
]
|
|
1102
1138
|
: [],
|
|
@@ -1228,6 +1264,14 @@ export function modelMessagesToUIMessages(
|
|
|
1228
1264
|
currentAssistantMessage.role === 'assistant'
|
|
1229
1265
|
) {
|
|
1230
1266
|
const content = toolResultContent(msg.content)
|
|
1267
|
+
const rawToolResultOutcome = tanstackMetadata(msg)?.toolResultOutcome
|
|
1268
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
1269
|
+
? rawToolResultOutcome
|
|
1270
|
+
: undefined
|
|
1271
|
+
const resultState =
|
|
1272
|
+
msg.error === undefined && toolResultOutcome === undefined
|
|
1273
|
+
? 'complete'
|
|
1274
|
+
: 'error'
|
|
1231
1275
|
const toolCallPart = currentAssistantMessage.parts.find(
|
|
1232
1276
|
(part): part is ToolCallPart =>
|
|
1233
1277
|
part.type === 'tool-call' && part.id === msg.toolCallId,
|
|
@@ -1238,14 +1282,17 @@ export function modelMessagesToUIMessages(
|
|
|
1238
1282
|
typeof content === 'string'
|
|
1239
1283
|
? parseToolResultContent(content)
|
|
1240
1284
|
: content
|
|
1241
|
-
toolCallPart.state =
|
|
1285
|
+
toolCallPart.state = resultState
|
|
1242
1286
|
}
|
|
1243
1287
|
|
|
1244
1288
|
currentAssistantMessage.parts.push({
|
|
1245
1289
|
type: 'tool-result',
|
|
1246
1290
|
toolCallId: msg.toolCallId,
|
|
1247
1291
|
content,
|
|
1248
|
-
state:
|
|
1292
|
+
state: resultState,
|
|
1293
|
+
...(toolResultOutcome !== undefined && {
|
|
1294
|
+
outcome: toolResultOutcome,
|
|
1295
|
+
}),
|
|
1249
1296
|
...(msg.id !== undefined &&
|
|
1250
1297
|
msg.id !== currentAssistantMessage.id && { id: msg.id }),
|
|
1251
1298
|
...(msg.name !== undefined && { name: msg.name }),
|
|
@@ -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
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* These are used by StreamProcessor to manage the message array.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { isRedactedThinkingId } from '../../../utilities/reasoning-encrypted-value'
|
|
8
9
|
import { parsePartialJSON } from './json-parser'
|
|
9
10
|
import type {
|
|
10
11
|
ContentPart,
|
|
11
12
|
StructuredOutputPart,
|
|
12
13
|
ThinkingPart,
|
|
13
14
|
ToolCallPart,
|
|
15
|
+
ToolResultOutcome,
|
|
14
16
|
ToolResultPart,
|
|
15
17
|
UIMessage,
|
|
16
18
|
} from '../../../types'
|
|
@@ -117,6 +119,7 @@ export function updateToolResultPart(
|
|
|
117
119
|
content: string | Array<ContentPart>,
|
|
118
120
|
state: ToolResultState,
|
|
119
121
|
error?: string,
|
|
122
|
+
outcome?: ToolResultOutcome,
|
|
120
123
|
): Array<UIMessage> {
|
|
121
124
|
return messages.map((msg) => {
|
|
122
125
|
if (msg.id !== messageId) {
|
|
@@ -134,6 +137,7 @@ export function updateToolResultPart(
|
|
|
134
137
|
toolCallId,
|
|
135
138
|
content,
|
|
136
139
|
state,
|
|
140
|
+
...(outcome !== undefined && { outcome }),
|
|
137
141
|
...(error && { error }),
|
|
138
142
|
}
|
|
139
143
|
|
|
@@ -480,12 +484,16 @@ export function updateThinkingPart(
|
|
|
480
484
|
// not carry one; losing it would strip the provider's encrypted reasoning
|
|
481
485
|
// from a message that is about to be sent back.
|
|
482
486
|
const nextSignature = signature ?? adopted?.signature
|
|
487
|
+
// A hydrated part has no stepId to carry the redacted marker, so it keeps
|
|
488
|
+
// its own flag.
|
|
489
|
+
const redacted = isRedactedThinkingId(stepId) || adopted?.redacted === true
|
|
483
490
|
|
|
484
491
|
const thinkingPart: ThinkingPart = {
|
|
485
492
|
type: 'thinking',
|
|
486
493
|
content,
|
|
487
494
|
stepId,
|
|
488
495
|
...(nextSignature && { signature: nextSignature }),
|
|
496
|
+
...(redacted && { redacted: true }),
|
|
489
497
|
}
|
|
490
498
|
|
|
491
499
|
if (thinkingPartIndex >= 0) {
|
|
@@ -36,6 +36,8 @@ import {
|
|
|
36
36
|
import { getChunkRunId } from '../../../utilities/chunk-ids'
|
|
37
37
|
import type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'
|
|
38
38
|
import {
|
|
39
|
+
isContentPartArray,
|
|
40
|
+
isToolResultOutcome,
|
|
39
41
|
normalizeToolResult,
|
|
40
42
|
toolResultErrorText,
|
|
41
43
|
} from '../../../utilities/tool-result'
|
|
@@ -181,6 +183,23 @@ function interruptBatchHasGeneric(interrupts: Array<Interrupt>): boolean {
|
|
|
181
183
|
})
|
|
182
184
|
}
|
|
183
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
|
+
|
|
184
203
|
/**
|
|
185
204
|
* StreamProcessor - State machine for processing AI response streams
|
|
186
205
|
*
|
|
@@ -1953,11 +1972,13 @@ export class StreamProcessor {
|
|
|
1953
1972
|
* Handle TOOL_CALL_END event — arguments are finalized (input-complete).
|
|
1954
1973
|
* Tool output arrives on TOOL_CALL_RESULT, not on this event.
|
|
1955
1974
|
*
|
|
1956
|
-
* If TOOL_CALL_END carries parsed `input`,
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1959
|
-
* server_tool_use / web_search — issue #839) and
|
|
1960
|
-
*
|
|
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.
|
|
1961
1982
|
*
|
|
1962
1983
|
* @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
|
|
1963
1984
|
*/
|
|
@@ -1980,16 +2001,15 @@ export class StreamProcessor {
|
|
|
1980
2001
|
// Transition the tool call to input-complete (the authoritative completion signal)
|
|
1981
2002
|
const existingToolCall = msgState.toolCalls.get(chunk.toolCallId)
|
|
1982
2003
|
if (existingToolCall && existingToolCall.state !== 'input-complete') {
|
|
1983
|
-
//
|
|
1984
|
-
//
|
|
1985
|
-
//
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
}
|
|
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
|
|
1993
2013
|
}
|
|
1994
2014
|
|
|
1995
2015
|
const index = msgState.toolCallOrder.indexOf(chunk.toolCallId)
|
|
@@ -1998,7 +2018,7 @@ export class StreamProcessor {
|
|
|
1998
2018
|
// Canonicalize on the parsed input: overrides the accumulated-args parse
|
|
1999
2019
|
// that completeToolCall wrote (adapters may coerce values differently
|
|
2000
2020
|
// between streamed args and the final structured input).
|
|
2001
|
-
if (
|
|
2021
|
+
if (serializedInput !== undefined) {
|
|
2002
2022
|
existingToolCall.parsedArguments = input
|
|
2003
2023
|
this.messages = updateToolCallPart(this.messages, messageId, {
|
|
2004
2024
|
id: existingToolCall.id,
|
|
@@ -2042,9 +2062,14 @@ export class StreamProcessor {
|
|
|
2042
2062
|
if (!messageId) return
|
|
2043
2063
|
|
|
2044
2064
|
const extra = chunk as AdapterYieldChunk
|
|
2065
|
+
const rawToolResultOutcome = tanstackMetadata(chunk)?.toolResultOutcome
|
|
2066
|
+
const toolResultOutcome = isToolResultOutcome(rawToolResultOutcome)
|
|
2067
|
+
? rawToolResultOutcome
|
|
2068
|
+
: undefined
|
|
2045
2069
|
const isOutputError =
|
|
2046
2070
|
extra.state === 'output-error' ||
|
|
2047
|
-
tanstackMetadata(chunk)?.state === 'output-error'
|
|
2071
|
+
tanstackMetadata(chunk)?.state === 'output-error' ||
|
|
2072
|
+
toolResultOutcome !== undefined
|
|
2048
2073
|
|
|
2049
2074
|
// Step 1: Update the tool-call part's output field
|
|
2050
2075
|
let output: unknown
|
|
@@ -2069,9 +2094,14 @@ export class StreamProcessor {
|
|
|
2069
2094
|
this.messages,
|
|
2070
2095
|
messageId,
|
|
2071
2096
|
chunk.toolCallId,
|
|
2072
|
-
|
|
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),
|
|
2073
2102
|
resultState,
|
|
2074
2103
|
resultState === 'error' ? toolResultErrorText(output) : undefined,
|
|
2104
|
+
toolResultOutcome,
|
|
2075
2105
|
)
|
|
2076
2106
|
this.emitMessagesChange()
|
|
2077
2107
|
}
|
|
@@ -2678,11 +2708,11 @@ export class StreamProcessor {
|
|
|
2678
2708
|
toolCall.parsedArguments = undefined
|
|
2679
2709
|
}
|
|
2680
2710
|
|
|
2681
|
-
// Don't downgrade the rendered part of a call that already reached
|
|
2682
|
-
// 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
|
|
2683
2713
|
// without a preceding TOOL_CALL_END). The RUN_FINISHED / finalizeStream
|
|
2684
|
-
// safety net must not clobber a
|
|
2685
|
-
if (this.
|
|
2714
|
+
// safety net must not clobber a finished call back to 'input-complete'.
|
|
2715
|
+
if (this.isToolCallPartTerminal(toolCall.id)) {
|
|
2686
2716
|
return
|
|
2687
2717
|
}
|
|
2688
2718
|
|
|
@@ -2728,11 +2758,11 @@ export class StreamProcessor {
|
|
|
2728
2758
|
}
|
|
2729
2759
|
|
|
2730
2760
|
/**
|
|
2731
|
-
* Whether the rendered tool-call part for the given id has reached
|
|
2732
|
-
* terminal 'error' state. Used to prevent the completion
|
|
2733
|
-
* 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'.
|
|
2734
2764
|
*/
|
|
2735
|
-
private
|
|
2765
|
+
private isToolCallPartTerminal(toolCallId: string): boolean {
|
|
2736
2766
|
// `initialMessages` may be ModelMessage-shaped (no `parts`) — e.g. the
|
|
2737
2767
|
// common pattern of seeding a processor with the same messages passed to
|
|
2738
2768
|
// `chat()`. Guard the access so iterating them never throws.
|
|
@@ -2742,7 +2772,7 @@ export class StreamProcessor {
|
|
|
2742
2772
|
(part) =>
|
|
2743
2773
|
part.type === 'tool-call' &&
|
|
2744
2774
|
part.id === toolCallId &&
|
|
2745
|
-
part.state === 'error',
|
|
2775
|
+
(part.state === 'error' || part.state === 'complete'),
|
|
2746
2776
|
),
|
|
2747
2777
|
)
|
|
2748
2778
|
}
|
|
@@ -230,6 +230,14 @@ export interface ConvertSchemaOptions {
|
|
|
230
230
|
* @default false
|
|
231
231
|
*/
|
|
232
232
|
forStructuredOutput?: boolean
|
|
233
|
+
/**
|
|
234
|
+
* Which view of a Standard JSON Schema to convert. A schema with a
|
|
235
|
+
* transform or a pipe has a different `output` view. Use `'output'` to
|
|
236
|
+
* describe the value that parsing returns.
|
|
237
|
+
*
|
|
238
|
+
* @default 'input'
|
|
239
|
+
*/
|
|
240
|
+
io?: 'input' | 'output'
|
|
233
241
|
}
|
|
234
242
|
|
|
235
243
|
/**
|
|
@@ -238,16 +246,20 @@ export interface ConvertSchemaOptions {
|
|
|
238
246
|
*
|
|
239
247
|
* - Standard JSON Schemas are rebuilt structurally (dropping `$schema`, which
|
|
240
248
|
* LLM providers ignore) and given the explicit `type`/`properties`/`required`
|
|
241
|
-
* defaults object shapes need downstream.
|
|
249
|
+
* defaults object shapes need downstream. `io` picks the `input` (default)
|
|
250
|
+
* or `output` view.
|
|
242
251
|
* - Plain `JSONSchema` inputs are rebuilt into the typed view; non-object inputs
|
|
243
252
|
* are surfaced untouched (they can't be widened).
|
|
244
253
|
* - Standard Schema validators lacking a `~standard.jsonSchema` converter throw
|
|
245
254
|
* with actionable guidance, rather than shipping `{ '~standard': … }` to the
|
|
246
255
|
* provider and producing an opaque downstream error.
|
|
247
256
|
*/
|
|
248
|
-
function toTypedJsonSchema(
|
|
257
|
+
function toTypedJsonSchema(
|
|
258
|
+
schema: SchemaInput,
|
|
259
|
+
io: 'input' | 'output' = 'input',
|
|
260
|
+
): JSONSchema | undefined {
|
|
249
261
|
if (isStandardJSONSchema(schema)) {
|
|
250
|
-
const jsonSchema = schema['~standard'].jsonSchema
|
|
262
|
+
const jsonSchema = schema['~standard'].jsonSchema[io]({
|
|
251
263
|
target: 'draft-07',
|
|
252
264
|
})
|
|
253
265
|
const result: JSONSchema = toJsonSchema(jsonSchema)
|
|
@@ -340,7 +352,7 @@ export function convertSchemaToJsonSchema(
|
|
|
340
352
|
): JSONSchema | undefined {
|
|
341
353
|
if (!schema) return undefined
|
|
342
354
|
|
|
343
|
-
const { forStructuredOutput = false } = options
|
|
355
|
+
const { forStructuredOutput = false, io } = options
|
|
344
356
|
|
|
345
357
|
// Plain-JSONSchema passthrough: with no widening requested, return the schema
|
|
346
358
|
// by reference so callers comparing via `===` keep identity. Only the widening
|
|
@@ -353,7 +365,7 @@ export function convertSchemaToJsonSchema(
|
|
|
353
365
|
return schema
|
|
354
366
|
}
|
|
355
367
|
|
|
356
|
-
const base = toTypedJsonSchema(schema)
|
|
368
|
+
const base = toTypedJsonSchema(schema, io)
|
|
357
369
|
// Non-object inputs can't be widened; surface them untouched.
|
|
358
370
|
if (!base || typeof base !== 'object') return base
|
|
359
371
|
if (!forStructuredOutput) return base
|