@tanstack/ai 0.58.0 → 0.61.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/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 +81 -0
- package/dist/esm/activities/chat/agents/define-agent.js +34 -0
- package/dist/esm/activities/chat/agents/define-agent.js.map +1 -0
- package/dist/esm/activities/chat/agents/route.d.ts +53 -0
- package/dist/esm/activities/chat/agents/route.js +59 -0
- package/dist/esm/activities/chat/agents/route.js.map +1 -0
- package/dist/esm/activities/chat/agents/spawn.d.ts +124 -0
- package/dist/esm/activities/chat/agents/spawn.js +490 -0
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -0
- package/dist/esm/activities/chat/agents/turn.d.ts +36 -0
- package/dist/esm/activities/chat/agents/turn.js +78 -0
- package/dist/esm/activities/chat/agents/turn.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +13 -3
- package/dist/esm/activities/chat/index.js +462 -26
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +7 -1
- package/dist/esm/activities/chat/messages.js +99 -19
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/run-store.d.ts +43 -7
- package/dist/esm/activities/chat/middleware/run-store.js +8 -1
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +47 -1
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/message-updaters.js +9 -2
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +46 -1
- package/dist/esm/activities/chat/stream/processor.js +294 -18
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +17 -3
- package/dist/esm/activities/chat/tools/tool-calls.js +56 -5
- package/dist/esm/activities/chat/tools/tool-calls.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/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/generateAudio/index.js +1 -1
- 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 +3 -1
- package/dist/esm/activities/generateImage/index.js.map +1 -1
- package/dist/esm/activities/generateLiveVideo/index.js +1 -1
- package/dist/esm/activities/generateSpeech/index.js +1 -1
- package/dist/esm/activities/generateTranscription/index.js +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/generateVoice/index.js +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 +6 -5
- package/dist/esm/activities/generateWorld/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +9 -3
- package/dist/esm/activities/index.js +17 -13
- 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/activities/summarize/index.js +1 -1
- package/dist/esm/client.d.ts +7 -36
- package/dist/esm/client.js +5 -37
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.js +9 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/types.d.ts +179 -98
- package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
- package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
- package/dist/esm/utilities/ag-ui-usage.js +66 -3
- package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
- package/dist/esm/utilities/ag-ui-wire.js +90 -13
- 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/normalize-stream-chunk.js +7 -2
- package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
- 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/spec-event-keys.js +13 -8
- package/dist/esm/utilities/spec-event-keys.js.map +1 -1
- package/dist/esm/utilities/subagent-wire.d.ts +36 -0
- package/dist/esm/utilities/subagent-wire.js +131 -0
- package/dist/esm/utilities/subagent-wire.js.map +1 -0
- package/dist/esm/utilities/tool-result.d.ts +12 -2
- package/dist/esm/utilities/tool-result.js +23 -3
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +14 -0
- package/skills/ai-core/media-generation/SKILL.md +10 -2
- package/skills/ai-core/middleware/SKILL.md +7 -4
- package/src/activities/chat/adapter.ts +10 -0
- package/src/activities/chat/agents/define-agent.ts +121 -0
- package/src/activities/chat/agents/route.ts +115 -0
- package/src/activities/chat/agents/spawn.ts +806 -0
- package/src/activities/chat/agents/turn.ts +151 -0
- package/src/activities/chat/index.ts +734 -30
- package/src/activities/chat/messages.ts +137 -16
- package/src/activities/chat/middleware/run-store.ts +56 -7
- package/src/activities/chat/middleware/types.ts +47 -0
- package/src/activities/chat/stream/message-updaters.ts +24 -2
- package/src/activities/chat/stream/processor.ts +452 -30
- package/src/activities/chat/tools/tool-calls.ts +83 -11
- package/src/activities/embed/adapter.ts +7 -0
- package/src/activities/embed/index.ts +5 -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 +40 -1
- package/src/activities/summarize/chat-stream-summarize.ts +22 -12
- package/src/client.ts +29 -35
- package/src/index.ts +39 -0
- package/src/middlewares/content-guard.ts +7 -5
- package/src/types.ts +226 -103
- package/src/utilities/adapter-yield-chunk.ts +10 -2
- package/src/utilities/ag-ui-usage.test.ts +38 -0
- package/src/utilities/ag-ui-usage.ts +98 -11
- package/src/utilities/ag-ui-wire.ts +134 -16
- package/src/utilities/content-source.ts +138 -0
- package/src/utilities/normalize-stream-chunk.ts +10 -2
- package/src/utilities/provider-executed.ts +13 -0
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
- package/src/utilities/tool-result.ts +38 -2
|
@@ -18,20 +18,27 @@
|
|
|
18
18
|
* adapter contract, single-shot flows, and expected UIMessage output.
|
|
19
19
|
*/
|
|
20
20
|
import {
|
|
21
|
+
aguiContentToContentParts,
|
|
21
22
|
aguiSnapshotMessageToUIMessage,
|
|
22
23
|
coerceCreatedAt,
|
|
23
24
|
generateMessageId,
|
|
24
25
|
uiMessageToModelMessages,
|
|
25
26
|
} from '../messages.js'
|
|
26
27
|
import { runErrorEventToError } from '../../../utilities/errors'
|
|
27
|
-
import {
|
|
28
|
+
import {
|
|
29
|
+
isAssistantSegmentOf,
|
|
30
|
+
isProviderExecutedToolCall,
|
|
31
|
+
} from '../../../utilities/provider-executed'
|
|
28
32
|
import {
|
|
29
33
|
mergeMetadata,
|
|
30
34
|
tanstackMetadata,
|
|
31
35
|
} from '../../../utilities/merge-metadata'
|
|
32
36
|
import { getChunkRunId } from '../../../utilities/chunk-ids'
|
|
33
37
|
import type { AdapterYieldChunk } from '../../../utilities/adapter-yield-chunk'
|
|
34
|
-
import {
|
|
38
|
+
import {
|
|
39
|
+
normalizeToolResult,
|
|
40
|
+
toolResultErrorText,
|
|
41
|
+
} from '../../../utilities/tool-result'
|
|
35
42
|
import { defaultJSONParser } from './json-parser'
|
|
36
43
|
import {
|
|
37
44
|
appendStructuredOutputDelta,
|
|
@@ -47,6 +54,10 @@ import {
|
|
|
47
54
|
} from './message-updaters'
|
|
48
55
|
import { ImmediateStrategy } from './strategies'
|
|
49
56
|
import { INTERRUPT_BINDING_METADATA_KEY } from '../../../interrupt-resume'
|
|
57
|
+
import { EventType } from '../../../types'
|
|
58
|
+
import { splitSubagentWire } from '../../../utilities/subagent-wire'
|
|
59
|
+
import type { SubagentWireGroup } from '../../../utilities/subagent-wire'
|
|
60
|
+
import type { Message as AGUIMessage } from '@ag-ui/core'
|
|
50
61
|
import type {
|
|
51
62
|
ChunkRecording,
|
|
52
63
|
ChunkStrategy,
|
|
@@ -67,6 +78,7 @@ import type {
|
|
|
67
78
|
ToolCall,
|
|
68
79
|
ToolCallPart,
|
|
69
80
|
ToolResultPart,
|
|
81
|
+
SubagentPart,
|
|
70
82
|
UIMessage,
|
|
71
83
|
UIResourceEvent,
|
|
72
84
|
UIResourcePart,
|
|
@@ -144,6 +156,11 @@ export interface StreamProcessorOptions {
|
|
|
144
156
|
recording?: boolean
|
|
145
157
|
/** Initial messages to populate the processor */
|
|
146
158
|
initialMessages?: Array<UIMessage>
|
|
159
|
+
/**
|
|
160
|
+
* Set on the processor of a subagent card. Chunks tagged with this id are
|
|
161
|
+
* the card's own; chunks for an id no card holds are dropped.
|
|
162
|
+
*/
|
|
163
|
+
subagentRunId?: string
|
|
147
164
|
}
|
|
148
165
|
|
|
149
166
|
const STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12
|
|
@@ -207,6 +224,10 @@ export class StreamProcessor {
|
|
|
207
224
|
|
|
208
225
|
// Run tracking (for concurrent run safety)
|
|
209
226
|
private readonly activeRuns = new Set<string>()
|
|
227
|
+
// Direct children by subagentRunId. See childProcessor().
|
|
228
|
+
private readonly childProcessors = new Map<string, StreamProcessor>()
|
|
229
|
+
// Child tool calls whose later events may omit subagentRunId.
|
|
230
|
+
private readonly childToolCalls = new Map<string, string>()
|
|
210
231
|
|
|
211
232
|
// Shared stream state
|
|
212
233
|
private finishReason: string | null = null
|
|
@@ -218,7 +239,10 @@ export class StreamProcessor {
|
|
|
218
239
|
private recording: ChunkRecording | null = null
|
|
219
240
|
private recordingStartTime = 0
|
|
220
241
|
|
|
242
|
+
private readonly subagentRunId?: string
|
|
243
|
+
|
|
221
244
|
constructor(options: StreamProcessorOptions = {}) {
|
|
245
|
+
this.subagentRunId = options.subagentRunId
|
|
222
246
|
this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy()
|
|
223
247
|
this.events = options.events || {}
|
|
224
248
|
this.jsonParser = options.jsonParser || defaultJSONParser
|
|
@@ -378,6 +402,13 @@ export class StreamProcessor {
|
|
|
378
402
|
)
|
|
379
403
|
|
|
380
404
|
if (!messageWithToolCall) {
|
|
405
|
+
const owner = this.childOwningToolCall((part) => part.id === toolCallId)
|
|
406
|
+
if (owner !== undefined) {
|
|
407
|
+
this.updateChild(owner, (child) =>
|
|
408
|
+
child.addToolResult(toolCallId, output, error),
|
|
409
|
+
)
|
|
410
|
+
return
|
|
411
|
+
}
|
|
381
412
|
console.warn(
|
|
382
413
|
`[StreamProcessor] Could not find message with tool call ${toolCallId}`,
|
|
383
414
|
)
|
|
@@ -414,6 +445,15 @@ export class StreamProcessor {
|
|
|
414
445
|
* Add an approval response (called by client after handling onApprovalRequest)
|
|
415
446
|
*/
|
|
416
447
|
addToolApprovalResponse(approvalId: string, approved: boolean): void {
|
|
448
|
+
const owner = this.childOwningToolCall(
|
|
449
|
+
(part) => part.approval?.id === approvalId,
|
|
450
|
+
)
|
|
451
|
+
if (owner !== undefined) {
|
|
452
|
+
this.updateChild(owner, (child) =>
|
|
453
|
+
child.addToolApprovalResponse(approvalId, approved),
|
|
454
|
+
)
|
|
455
|
+
return
|
|
456
|
+
}
|
|
417
457
|
this.messages = updateToolCallApprovalResponse(
|
|
418
458
|
this.messages,
|
|
419
459
|
approvalId,
|
|
@@ -524,6 +564,8 @@ export class StreamProcessor {
|
|
|
524
564
|
this.structuredMessageIds.clear()
|
|
525
565
|
this.structuredOutputUpdateBatches.clear()
|
|
526
566
|
this.pendingManualMessageId = null
|
|
567
|
+
this.childProcessors.clear()
|
|
568
|
+
this.childToolCalls.clear()
|
|
527
569
|
this.emitMessagesChange()
|
|
528
570
|
}
|
|
529
571
|
|
|
@@ -578,12 +620,24 @@ export class StreamProcessor {
|
|
|
578
620
|
})
|
|
579
621
|
}
|
|
580
622
|
|
|
581
|
-
|
|
582
|
-
|
|
623
|
+
if (this.routeToChild(chunk)) return
|
|
624
|
+
|
|
583
625
|
const c = chunk
|
|
584
626
|
// eslint-disable-next-line @typescript-eslint/switch-exhaustiveness-check -- AG-UI EventType enum members vs string-literal case labels; default branch handles untraced events.
|
|
585
627
|
switch (c.type) {
|
|
586
628
|
// AG-UI Events
|
|
629
|
+
case 'SUBAGENT_STARTED':
|
|
630
|
+
this.handleSubagentStartedEvent(chunk)
|
|
631
|
+
break
|
|
632
|
+
|
|
633
|
+
case 'SUBAGENT_FINISHED':
|
|
634
|
+
this.handleSubagentFinishedEvent(chunk)
|
|
635
|
+
break
|
|
636
|
+
|
|
637
|
+
case 'SUBAGENT_ERROR':
|
|
638
|
+
this.handleSubagentErrorEvent(chunk)
|
|
639
|
+
break
|
|
640
|
+
|
|
587
641
|
case 'TEXT_MESSAGE_START':
|
|
588
642
|
this.handleTextMessageStartEvent(
|
|
589
643
|
chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,
|
|
@@ -907,6 +961,241 @@ export class StreamProcessor {
|
|
|
907
961
|
this.emitMessagesChange()
|
|
908
962
|
}
|
|
909
963
|
|
|
964
|
+
private findSubagentPart(subagentRunId: string) {
|
|
965
|
+
for (const message of this.messages) {
|
|
966
|
+
for (const part of message.parts) {
|
|
967
|
+
if (part.type === 'subagent' && part.subagent.id === subagentRunId) {
|
|
968
|
+
return { message, part }
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
return undefined
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
/**
|
|
976
|
+
* The direct child whose card holds `id`: the child itself, or a nested
|
|
977
|
+
* child inside its messages.
|
|
978
|
+
*/
|
|
979
|
+
private childOwning(id: string): string | undefined {
|
|
980
|
+
const holds = (messages: ReadonlyArray<UIMessage>): boolean =>
|
|
981
|
+
messages.some((message) =>
|
|
982
|
+
message.parts.some(
|
|
983
|
+
(part) =>
|
|
984
|
+
part.type === 'subagent' &&
|
|
985
|
+
(part.subagent.id === id || holds(part.subagent.messages)),
|
|
986
|
+
),
|
|
987
|
+
)
|
|
988
|
+
for (const message of this.messages) {
|
|
989
|
+
for (const part of message.parts) {
|
|
990
|
+
if (part.type !== 'subagent') continue
|
|
991
|
+
if (part.subagent.id === id || holds(part.subagent.messages)) {
|
|
992
|
+
return part.subagent.id
|
|
993
|
+
}
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
return undefined
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
/** The direct child whose messages hold this tool call. */
|
|
1000
|
+
private childOwningToolCall(
|
|
1001
|
+
matches: (part: ToolCallPart) => boolean,
|
|
1002
|
+
): string | undefined {
|
|
1003
|
+
const holds = (messages: ReadonlyArray<UIMessage>): boolean =>
|
|
1004
|
+
messages.some((message) =>
|
|
1005
|
+
message.parts.some(
|
|
1006
|
+
(part) =>
|
|
1007
|
+
(part.type === 'tool-call' && matches(part)) ||
|
|
1008
|
+
(part.type === 'subagent' && holds(part.subagent.messages)),
|
|
1009
|
+
),
|
|
1010
|
+
)
|
|
1011
|
+
for (const message of this.messages) {
|
|
1012
|
+
for (const part of message.parts) {
|
|
1013
|
+
if (part.type === 'subagent' && holds(part.subagent.messages)) {
|
|
1014
|
+
return part.subagent.id
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
return undefined
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* The processor for a direct child. The card's messages are the source of
|
|
1023
|
+
* truth. The processor re-reads them when something else replaced them.
|
|
1024
|
+
*/
|
|
1025
|
+
private childProcessor(subagentRunId: string): StreamProcessor | undefined {
|
|
1026
|
+
const found = this.findSubagentPart(subagentRunId)
|
|
1027
|
+
if (!found) return undefined
|
|
1028
|
+
const messages = found.part.subagent.messages
|
|
1029
|
+
const existing = this.childProcessors.get(subagentRunId)
|
|
1030
|
+
if (existing) {
|
|
1031
|
+
if (existing.getMessages() !== messages) existing.messages = messages
|
|
1032
|
+
return existing
|
|
1033
|
+
}
|
|
1034
|
+
const child = new StreamProcessor({
|
|
1035
|
+
subagentRunId,
|
|
1036
|
+
initialMessages: messages,
|
|
1037
|
+
events: {
|
|
1038
|
+
onToolCall: (args) => this.events.onToolCall?.(args),
|
|
1039
|
+
onApprovalRequest: (args) => this.events.onApprovalRequest?.(args),
|
|
1040
|
+
onCustomEvent: (name, value, context) =>
|
|
1041
|
+
this.events.onCustomEvent?.(name, value, context),
|
|
1042
|
+
},
|
|
1043
|
+
})
|
|
1044
|
+
this.childProcessors.set(subagentRunId, child)
|
|
1045
|
+
return child
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
/** Run `update` on a direct child, then copy its messages onto the card. */
|
|
1049
|
+
private updateChild(
|
|
1050
|
+
subagentRunId: string,
|
|
1051
|
+
update: (child: StreamProcessor) => void,
|
|
1052
|
+
): void {
|
|
1053
|
+
const child = this.childProcessor(subagentRunId)
|
|
1054
|
+
if (!child) return
|
|
1055
|
+
update(child)
|
|
1056
|
+
const messages = child.getMessages()
|
|
1057
|
+
this.patchSubagent(subagentRunId, (subagent) => {
|
|
1058
|
+
subagent.messages = messages
|
|
1059
|
+
})
|
|
1060
|
+
}
|
|
1061
|
+
|
|
1062
|
+
private handleSubagentStartedEvent(chunk: StreamChunk): void {
|
|
1063
|
+
if (chunk.type !== 'SUBAGENT_STARTED') return
|
|
1064
|
+
const existing = this.findSubagentPart(chunk.subagentRunId)
|
|
1065
|
+
if (existing) {
|
|
1066
|
+
// A resume continues the same child. Start a fresh stream on its card.
|
|
1067
|
+
this.childProcessors.delete(chunk.subagentRunId)
|
|
1068
|
+
this.patchSubagent(chunk.subagentRunId, (subagent) => {
|
|
1069
|
+
subagent.status = 'running'
|
|
1070
|
+
delete subagent.interruptIds
|
|
1071
|
+
delete subagent.error
|
|
1072
|
+
if (chunk.metadata !== undefined) subagent.metadata = chunk.metadata
|
|
1073
|
+
})
|
|
1074
|
+
return
|
|
1075
|
+
}
|
|
1076
|
+
const { messageId } = this.ensureAssistantMessage()
|
|
1077
|
+
const part: SubagentPart = {
|
|
1078
|
+
type: 'subagent',
|
|
1079
|
+
subagent: {
|
|
1080
|
+
id: chunk.subagentRunId,
|
|
1081
|
+
name: chunk.name,
|
|
1082
|
+
...(chunk.description !== undefined && {
|
|
1083
|
+
description: chunk.description,
|
|
1084
|
+
}),
|
|
1085
|
+
status: 'running',
|
|
1086
|
+
...(chunk.parentSubagentRunId !== undefined && {
|
|
1087
|
+
parentSubagentRunId: chunk.parentSubagentRunId,
|
|
1088
|
+
}),
|
|
1089
|
+
...(chunk.parentToolCallId !== undefined && {
|
|
1090
|
+
parentToolCallId: chunk.parentToolCallId,
|
|
1091
|
+
}),
|
|
1092
|
+
...(chunk.metadata !== undefined && { metadata: chunk.metadata }),
|
|
1093
|
+
messages: [],
|
|
1094
|
+
},
|
|
1095
|
+
}
|
|
1096
|
+
this.messages = this.messages.map((message) =>
|
|
1097
|
+
message.id === messageId
|
|
1098
|
+
? { ...message, parts: [...message.parts, part] }
|
|
1099
|
+
: message,
|
|
1100
|
+
)
|
|
1101
|
+
this.emitMessagesChange()
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
private handleSubagentFinishedEvent(chunk: StreamChunk): void {
|
|
1105
|
+
if (chunk.type !== 'SUBAGENT_FINISHED') return
|
|
1106
|
+
this.updateChild(chunk.subagentRunId, (child) => child.finalizeStream())
|
|
1107
|
+
this.patchSubagent(chunk.subagentRunId, (subagent) => {
|
|
1108
|
+
if (subagent.status === 'error') return
|
|
1109
|
+
if (chunk.outcome?.type === 'suspended') {
|
|
1110
|
+
subagent.status = 'suspended'
|
|
1111
|
+
subagent.interruptIds = chunk.outcome.interruptIds ?? []
|
|
1112
|
+
return
|
|
1113
|
+
}
|
|
1114
|
+
subagent.status = 'finished'
|
|
1115
|
+
})
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
private handleSubagentErrorEvent(chunk: StreamChunk): void {
|
|
1119
|
+
if (chunk.type !== 'SUBAGENT_ERROR') return
|
|
1120
|
+
this.updateChild(chunk.subagentRunId, (child) => child.finalizeStream())
|
|
1121
|
+
this.patchSubagent(chunk.subagentRunId, (subagent) => {
|
|
1122
|
+
subagent.status = 'error'
|
|
1123
|
+
subagent.error = {
|
|
1124
|
+
message: chunk.message,
|
|
1125
|
+
...(chunk.code !== undefined && { code: chunk.code }),
|
|
1126
|
+
}
|
|
1127
|
+
})
|
|
1128
|
+
}
|
|
1129
|
+
|
|
1130
|
+
private patchSubagent(
|
|
1131
|
+
subagentRunId: string,
|
|
1132
|
+
patch: (subagent: SubagentPart['subagent']) => void,
|
|
1133
|
+
): void {
|
|
1134
|
+
this.messages = this.messages.map((message) => {
|
|
1135
|
+
const index = message.parts.findIndex(
|
|
1136
|
+
(part) =>
|
|
1137
|
+
part.type === 'subagent' && part.subagent.id === subagentRunId,
|
|
1138
|
+
)
|
|
1139
|
+
if (index === -1) return message
|
|
1140
|
+
const part = message.parts[index]
|
|
1141
|
+
if (!part || part.type !== 'subagent') return message
|
|
1142
|
+
const next: SubagentPart = {
|
|
1143
|
+
type: 'subagent',
|
|
1144
|
+
subagent: { ...part.subagent },
|
|
1145
|
+
}
|
|
1146
|
+
patch(next.subagent)
|
|
1147
|
+
const parts = [...message.parts]
|
|
1148
|
+
parts[index] = next
|
|
1149
|
+
return { ...message, parts }
|
|
1150
|
+
})
|
|
1151
|
+
this.emitMessagesChange()
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
/**
|
|
1155
|
+
* Send a child's chunk to that child's processor, so the card keeps text,
|
|
1156
|
+
* reasoning, tool calls, results, and nested children. A tool event without
|
|
1157
|
+
* `subagentRunId` follows its `TOOL_CALL_START`.
|
|
1158
|
+
*/
|
|
1159
|
+
private routeToChild(chunk: StreamChunk): boolean {
|
|
1160
|
+
let id: string | undefined
|
|
1161
|
+
if (chunk.type === 'SUBAGENT_STARTED') {
|
|
1162
|
+
id = chunk.parentSubagentRunId
|
|
1163
|
+
} else if (
|
|
1164
|
+
chunk.type === 'SUBAGENT_FINISHED' ||
|
|
1165
|
+
chunk.type === 'SUBAGENT_ERROR'
|
|
1166
|
+
) {
|
|
1167
|
+
// A direct child's own lifecycle goes to processChunk's switch, not to a
|
|
1168
|
+
// child.
|
|
1169
|
+
if (this.findSubagentPart(chunk.subagentRunId)) return false
|
|
1170
|
+
id = chunk.subagentRunId
|
|
1171
|
+
} else if ('subagentRunId' in chunk && chunk.subagentRunId) {
|
|
1172
|
+
id = chunk.subagentRunId
|
|
1173
|
+
} else if ('toolCallId' in chunk && typeof chunk.toolCallId === 'string') {
|
|
1174
|
+
id = this.childToolCalls.get(chunk.toolCallId)
|
|
1175
|
+
}
|
|
1176
|
+
if (id === undefined) return false
|
|
1177
|
+
const owner = this.childOwning(id)
|
|
1178
|
+
if (owner === undefined) {
|
|
1179
|
+
// A card's own chunks reach its processor tagged with its id.
|
|
1180
|
+
if (id === this.subagentRunId) return false
|
|
1181
|
+
// No card holds this id. Keep the chunk off this message list.
|
|
1182
|
+
console.warn(
|
|
1183
|
+
`[StreamProcessor] Dropped a chunk for unknown subagent ${id}`,
|
|
1184
|
+
)
|
|
1185
|
+
return true
|
|
1186
|
+
}
|
|
1187
|
+
if (this.findSubagentPart(owner)?.part.subagent.status === 'error') {
|
|
1188
|
+
// An errored child, and its nested children, ignore late events.
|
|
1189
|
+
// Finished and suspended children still accept them, for a resume.
|
|
1190
|
+
return true
|
|
1191
|
+
}
|
|
1192
|
+
if (chunk.type === 'TOOL_CALL_START') {
|
|
1193
|
+
this.childToolCalls.set(chunk.toolCallId, owner)
|
|
1194
|
+
}
|
|
1195
|
+
this.updateChild(owner, (child) => child.processChunk(chunk))
|
|
1196
|
+
return true
|
|
1197
|
+
}
|
|
1198
|
+
|
|
910
1199
|
/**
|
|
911
1200
|
* Handle TEXT_MESSAGE_START event
|
|
912
1201
|
*/
|
|
@@ -1061,10 +1350,11 @@ export class StreamProcessor {
|
|
|
1061
1350
|
// the pre-snapshot state; see `reconcileSnapshotToolCalls`.
|
|
1062
1351
|
const prevMessages = this.messages
|
|
1063
1352
|
const prevById = new Map(prevMessages.map((msg) => [msg.id, msg]))
|
|
1353
|
+
const { top, groups } = splitSubagentWire(chunk.messages)
|
|
1064
1354
|
const normalized = this.mergeReasoningFanOut(
|
|
1065
|
-
|
|
1355
|
+
top.map(aguiSnapshotMessageToUIMessage),
|
|
1066
1356
|
)
|
|
1067
|
-
|
|
1357
|
+
const reconciled = this.reconcileSnapshotToolCalls(
|
|
1068
1358
|
normalized,
|
|
1069
1359
|
prevMessages,
|
|
1070
1360
|
).map((msg) => {
|
|
@@ -1073,9 +1363,113 @@ export class StreamProcessor {
|
|
|
1073
1363
|
if (prev?.metadata == null) return msg
|
|
1074
1364
|
return { ...msg, metadata: prev.metadata }
|
|
1075
1365
|
})
|
|
1366
|
+
this.messages = this.attachSnapshotSubagents(
|
|
1367
|
+
reconciled,
|
|
1368
|
+
top,
|
|
1369
|
+
groups,
|
|
1370
|
+
prevMessages,
|
|
1371
|
+
)
|
|
1076
1372
|
this.emitMessagesChange()
|
|
1077
1373
|
}
|
|
1078
1374
|
|
|
1375
|
+
/**
|
|
1376
|
+
* Put subagent cards back on a snapshot. Child wire messages (tagged with
|
|
1377
|
+
* `subagentRunId`) become a card on the nearest assistant message before
|
|
1378
|
+
* them, or a new assistant message when there is none. A card that the
|
|
1379
|
+
* snapshot does not carry stays on its message, because a server snapshot
|
|
1380
|
+
* can hold the parent history only.
|
|
1381
|
+
*/
|
|
1382
|
+
private attachSnapshotSubagents(
|
|
1383
|
+
messages: Array<UIMessage>,
|
|
1384
|
+
top: ReadonlyArray<AGUIMessage>,
|
|
1385
|
+
groups: ReadonlyArray<SubagentWireGroup<AGUIMessage>>,
|
|
1386
|
+
prevMessages: ReadonlyArray<UIMessage>,
|
|
1387
|
+
): Array<UIMessage> {
|
|
1388
|
+
const cards: Array<{
|
|
1389
|
+
hostId?: string
|
|
1390
|
+
toolCallId?: string
|
|
1391
|
+
part: SubagentPart
|
|
1392
|
+
carried: boolean
|
|
1393
|
+
}> = []
|
|
1394
|
+
const children = new Map<string, StreamProcessor>()
|
|
1395
|
+
for (const group of groups) {
|
|
1396
|
+
const child = new StreamProcessor({ subagentRunId: group.id })
|
|
1397
|
+
child.processChunk({
|
|
1398
|
+
type: EventType.MESSAGES_SNAPSHOT,
|
|
1399
|
+
messages: group.messages,
|
|
1400
|
+
timestamp: Date.now(),
|
|
1401
|
+
})
|
|
1402
|
+
children.set(group.id, child)
|
|
1403
|
+
const { placeholder: _placeholder, ...info } = group.info
|
|
1404
|
+
void _placeholder
|
|
1405
|
+
cards.push({
|
|
1406
|
+
hostId: top
|
|
1407
|
+
.slice(0, group.hostIndex + 1)
|
|
1408
|
+
.findLast((message) => message.role === 'assistant')?.id,
|
|
1409
|
+
part: {
|
|
1410
|
+
type: 'subagent',
|
|
1411
|
+
subagent: { ...info, id: group.id, messages: child.getMessages() },
|
|
1412
|
+
},
|
|
1413
|
+
carried: false,
|
|
1414
|
+
})
|
|
1415
|
+
}
|
|
1416
|
+
const inSnapshot = new Set(groups.map((group) => group.id))
|
|
1417
|
+
for (const message of prevMessages) {
|
|
1418
|
+
for (const part of message.parts) {
|
|
1419
|
+
if (part.type !== 'subagent' || inSnapshot.has(part.subagent.id)) {
|
|
1420
|
+
continue
|
|
1421
|
+
}
|
|
1422
|
+
cards.push({
|
|
1423
|
+
hostId: message.id,
|
|
1424
|
+
...(part.subagent.parentToolCallId !== undefined && {
|
|
1425
|
+
toolCallId: part.subagent.parentToolCallId,
|
|
1426
|
+
}),
|
|
1427
|
+
part,
|
|
1428
|
+
carried: true,
|
|
1429
|
+
})
|
|
1430
|
+
const kept = this.childProcessors.get(part.subagent.id)
|
|
1431
|
+
if (kept) children.set(part.subagent.id, kept)
|
|
1432
|
+
}
|
|
1433
|
+
}
|
|
1434
|
+
this.childProcessors.clear()
|
|
1435
|
+
for (const [id, child] of children) this.childProcessors.set(id, child)
|
|
1436
|
+
|
|
1437
|
+
let out = messages
|
|
1438
|
+
for (const card of cards) {
|
|
1439
|
+
let index = out.findIndex((message) => message.id === card.hostId)
|
|
1440
|
+
if (index === -1 && card.toolCallId !== undefined) {
|
|
1441
|
+
index = out.findIndex((message) =>
|
|
1442
|
+
message.parts.some(
|
|
1443
|
+
(part) => part.type === 'tool-call' && part.id === card.toolCallId,
|
|
1444
|
+
),
|
|
1445
|
+
)
|
|
1446
|
+
}
|
|
1447
|
+
if (index === -1 && !card.carried) {
|
|
1448
|
+
index = out.findLastIndex((message) => message.role === 'assistant')
|
|
1449
|
+
}
|
|
1450
|
+
if (index === -1) {
|
|
1451
|
+
if (!card.carried) {
|
|
1452
|
+
out = [
|
|
1453
|
+
...out,
|
|
1454
|
+
{ id: generateMessageId(), role: 'assistant', parts: [card.part] },
|
|
1455
|
+
]
|
|
1456
|
+
}
|
|
1457
|
+
continue
|
|
1458
|
+
}
|
|
1459
|
+
out = out.map((message, position) =>
|
|
1460
|
+
position !== index ||
|
|
1461
|
+
message.parts.some(
|
|
1462
|
+
(part) =>
|
|
1463
|
+
part.type === 'subagent' &&
|
|
1464
|
+
part.subagent.id === card.part.subagent.id,
|
|
1465
|
+
)
|
|
1466
|
+
? message
|
|
1467
|
+
: { ...message, parts: [...message.parts, card.part] },
|
|
1468
|
+
)
|
|
1469
|
+
}
|
|
1470
|
+
return out
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1079
1473
|
/**
|
|
1080
1474
|
* Reconcile a freshly normalized snapshot with the pre-snapshot message
|
|
1081
1475
|
* state so unreconstructable tool-call metadata is preserved.
|
|
@@ -1130,20 +1524,30 @@ export class StreamProcessor {
|
|
|
1130
1524
|
pending.push(msg)
|
|
1131
1525
|
continue
|
|
1132
1526
|
}
|
|
1527
|
+
let next = msg
|
|
1133
1528
|
if (
|
|
1134
1529
|
msg.role === 'assistant' &&
|
|
1135
1530
|
pending.length > 0 &&
|
|
1136
1531
|
!isToolResultOnly(msg)
|
|
1137
1532
|
) {
|
|
1138
|
-
|
|
1533
|
+
next = {
|
|
1139
1534
|
...msg,
|
|
1140
1535
|
parts: [...pending.flatMap(thinkingParts), ...msg.parts],
|
|
1141
|
-
}
|
|
1536
|
+
}
|
|
1142
1537
|
pending = []
|
|
1538
|
+
} else {
|
|
1539
|
+
flushPending()
|
|
1540
|
+
}
|
|
1541
|
+
const prev = out.at(-1)
|
|
1542
|
+
if (
|
|
1543
|
+
prev?.role === 'assistant' &&
|
|
1544
|
+
next.role === 'assistant' &&
|
|
1545
|
+
isAssistantSegmentOf(next.id, prev.id)
|
|
1546
|
+
) {
|
|
1547
|
+
out[out.length - 1] = { ...prev, parts: [...prev.parts, ...next.parts] }
|
|
1143
1548
|
continue
|
|
1144
1549
|
}
|
|
1145
|
-
|
|
1146
|
-
out.push(msg)
|
|
1550
|
+
out.push(next)
|
|
1147
1551
|
}
|
|
1148
1552
|
flushPending()
|
|
1149
1553
|
return out
|
|
@@ -1317,9 +1721,7 @@ export class StreamProcessor {
|
|
|
1317
1721
|
}
|
|
1318
1722
|
}
|
|
1319
1723
|
const errorText =
|
|
1320
|
-
result.state === 'error'
|
|
1321
|
-
? this.extractToolResultError(output)
|
|
1322
|
-
: undefined
|
|
1724
|
+
result.state === 'error' ? toolResultErrorText(output) : undefined
|
|
1323
1725
|
next = {
|
|
1324
1726
|
...next,
|
|
1325
1727
|
output: errorText ? { error: errorText } : output,
|
|
@@ -1613,18 +2015,6 @@ export class StreamProcessor {
|
|
|
1613
2015
|
}
|
|
1614
2016
|
}
|
|
1615
2017
|
|
|
1616
|
-
private extractToolResultError(output: unknown): string {
|
|
1617
|
-
if (
|
|
1618
|
-
output &&
|
|
1619
|
-
typeof output === 'object' &&
|
|
1620
|
-
'error' in output &&
|
|
1621
|
-
typeof output.error === 'string'
|
|
1622
|
-
) {
|
|
1623
|
-
return output.error
|
|
1624
|
-
}
|
|
1625
|
-
return typeof output === 'string' ? output : 'Tool execution failed'
|
|
1626
|
-
}
|
|
1627
|
-
|
|
1628
2018
|
/**
|
|
1629
2019
|
* Handle TOOL_CALL_RESULT event (AG-UI spec).
|
|
1630
2020
|
*
|
|
@@ -1659,7 +2049,10 @@ export class StreamProcessor {
|
|
|
1659
2049
|
// Step 1: Update the tool-call part's output field
|
|
1660
2050
|
let output: unknown
|
|
1661
2051
|
try {
|
|
1662
|
-
output =
|
|
2052
|
+
output =
|
|
2053
|
+
typeof chunk.content === 'string'
|
|
2054
|
+
? JSON.parse(chunk.content)
|
|
2055
|
+
: chunk.content
|
|
1663
2056
|
} catch {
|
|
1664
2057
|
output = chunk.content
|
|
1665
2058
|
}
|
|
@@ -1676,9 +2069,9 @@ export class StreamProcessor {
|
|
|
1676
2069
|
this.messages,
|
|
1677
2070
|
messageId,
|
|
1678
2071
|
chunk.toolCallId,
|
|
1679
|
-
chunk.content,
|
|
2072
|
+
aguiContentToContentParts(chunk.content),
|
|
1680
2073
|
resultState,
|
|
1681
|
-
resultState === 'error' ?
|
|
2074
|
+
resultState === 'error' ? toolResultErrorText(output) : undefined,
|
|
1682
2075
|
)
|
|
1683
2076
|
this.emitMessagesChange()
|
|
1684
2077
|
}
|
|
@@ -1732,7 +2125,12 @@ export class StreamProcessor {
|
|
|
1732
2125
|
}
|
|
1733
2126
|
}
|
|
1734
2127
|
|
|
1735
|
-
|
|
2128
|
+
/**
|
|
2129
|
+
* Apply interrupt state to tool-call parts, then fire the client events.
|
|
2130
|
+
* A child's interrupt updates the child's card. `emit` is false there, so
|
|
2131
|
+
* the events fire once, from the top processor.
|
|
2132
|
+
*/
|
|
2133
|
+
private handleInterrupts(interrupts: Array<Interrupt>, emit = true): void {
|
|
1736
2134
|
const hasGeneric = interruptBatchHasGeneric(interrupts)
|
|
1737
2135
|
for (const interrupt of interrupts) {
|
|
1738
2136
|
const metadata =
|
|
@@ -1743,6 +2141,16 @@ export class StreamProcessor {
|
|
|
1743
2141
|
const toolCallId = interrupt.toolCallId
|
|
1744
2142
|
if (!toolCallId) continue
|
|
1745
2143
|
|
|
2144
|
+
const owner =
|
|
2145
|
+
interrupt.subagentRunId !== undefined
|
|
2146
|
+
? this.childOwning(interrupt.subagentRunId)
|
|
2147
|
+
: this.childOwningToolCall((part) => part.id === toolCallId)
|
|
2148
|
+
if (owner !== undefined) {
|
|
2149
|
+
this.updateChild(owner, (child) =>
|
|
2150
|
+
child.handleInterrupts([interrupt], false),
|
|
2151
|
+
)
|
|
2152
|
+
}
|
|
2153
|
+
|
|
1746
2154
|
const toolName =
|
|
1747
2155
|
typeof metadata.toolName === 'string'
|
|
1748
2156
|
? metadata.toolName
|
|
@@ -1750,6 +2158,17 @@ export class StreamProcessor {
|
|
|
1750
2158
|
const input = Object.hasOwn(metadata, 'input') ? metadata.input : {}
|
|
1751
2159
|
|
|
1752
2160
|
if (kind === 'approval' || interrupt.reason === 'approval_required') {
|
|
2161
|
+
if (owner !== undefined) {
|
|
2162
|
+
if (emit) {
|
|
2163
|
+
this.events.onApprovalRequest?.({
|
|
2164
|
+
toolCallId,
|
|
2165
|
+
toolName,
|
|
2166
|
+
input,
|
|
2167
|
+
approvalId: interrupt.id,
|
|
2168
|
+
})
|
|
2169
|
+
}
|
|
2170
|
+
continue
|
|
2171
|
+
}
|
|
1753
2172
|
// An interrupt terminal arrives after MESSAGES_SNAPSHOT, which may have
|
|
1754
2173
|
// rebuilt the message list without the live active-message/tool-call
|
|
1755
2174
|
// maps. Fall back to locating the assistant message that actually owns
|
|
@@ -1774,6 +2193,7 @@ export class StreamProcessor {
|
|
|
1774
2193
|
this.emitMessagesChange()
|
|
1775
2194
|
}
|
|
1776
2195
|
|
|
2196
|
+
if (!emit) continue
|
|
1777
2197
|
this.events.onApprovalRequest?.({
|
|
1778
2198
|
toolCallId,
|
|
1779
2199
|
toolName,
|
|
@@ -1786,7 +2206,7 @@ export class StreamProcessor {
|
|
|
1786
2206
|
if (kind === 'client_tool' || interrupt.reason === 'client_tool_input') {
|
|
1787
2207
|
// Generic interrupts in the same batch decide `toolResume`. Do not
|
|
1788
2208
|
// run client tools until that policy is `continue`.
|
|
1789
|
-
if (hasGeneric) continue
|
|
2209
|
+
if (hasGeneric || !emit) continue
|
|
1790
2210
|
this.events.onToolCall?.({
|
|
1791
2211
|
toolCallId,
|
|
1792
2212
|
toolName,
|
|
@@ -2617,6 +3037,8 @@ export class StreamProcessor {
|
|
|
2617
3037
|
reset(): void {
|
|
2618
3038
|
this.resetStreamState()
|
|
2619
3039
|
this.messages = []
|
|
3040
|
+
this.childProcessors.clear()
|
|
3041
|
+
this.childToolCalls.clear()
|
|
2620
3042
|
}
|
|
2621
3043
|
|
|
2622
3044
|
/**
|