@tanstack/ai 0.57.0 → 0.59.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/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 +345 -19
- 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 +94 -18
- 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 +50 -3
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +46 -0
- package/dist/esm/activities/chat/stream/processor.js +280 -7
- 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 -7
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.js +1 -1
- package/dist/esm/activities/generateImage/index.js +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/generateVoice/index.js +1 -1
- package/dist/esm/activities/generateWorld/index.js +1 -1
- package/dist/esm/activities/index.d.ts +3 -0
- package/dist/esm/activities/index.js +5 -3
- package/dist/esm/activities/summarize/index.js +1 -1
- package/dist/esm/client.d.ts +5 -36
- package/dist/esm/client.js +4 -37
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +7 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/stream-to-response.js +12 -6
- package/dist/esm/stream-to-response.js.map +1 -1
- package/dist/esm/strip-to-spec-middleware.js +2 -1
- package/dist/esm/strip-to-spec-middleware.js.map +1 -1
- package/dist/esm/types.d.ts +128 -89
- 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 +56 -0
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/durability-batch.d.ts +8 -0
- package/dist/esm/utilities/durability-batch.js +45 -0
- package/dist/esm/utilities/durability-batch.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/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/package.json +4 -4
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
- package/skills/ai-core/media-generation/SKILL.md +2 -2
- package/skills/ai-core/middleware/SKILL.md +7 -4
- 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 +540 -16
- package/src/activities/chat/messages.ts +125 -15
- package/src/activities/chat/middleware/run-store.ts +56 -7
- package/src/activities/chat/middleware/types.ts +55 -2
- package/src/activities/chat/stream/processor.ts +428 -8
- package/src/activities/chat/tools/tool-calls.ts +92 -16
- package/src/activities/index.ts +15 -0
- package/src/client.ts +22 -35
- package/src/index.ts +23 -0
- package/src/middlewares/content-guard.ts +7 -5
- package/src/stream-to-response.ts +16 -6
- package/src/strip-to-spec-middleware.ts +2 -1
- package/src/types.ts +177 -95
- 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 +74 -0
- package/src/utilities/durability-batch.ts +48 -0
- package/src/utilities/normalize-stream-chunk.ts +10 -2
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
|
@@ -18,6 +18,7 @@
|
|
|
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,
|
|
@@ -47,6 +48,10 @@ import {
|
|
|
47
48
|
} from './message-updaters'
|
|
48
49
|
import { ImmediateStrategy } from './strategies'
|
|
49
50
|
import { INTERRUPT_BINDING_METADATA_KEY } from '../../../interrupt-resume'
|
|
51
|
+
import { EventType } from '../../../types'
|
|
52
|
+
import { splitSubagentWire } from '../../../utilities/subagent-wire'
|
|
53
|
+
import type { SubagentWireGroup } from '../../../utilities/subagent-wire'
|
|
54
|
+
import type { Message as AGUIMessage } from '@ag-ui/core'
|
|
50
55
|
import type {
|
|
51
56
|
ChunkRecording,
|
|
52
57
|
ChunkStrategy,
|
|
@@ -67,6 +72,7 @@ import type {
|
|
|
67
72
|
ToolCall,
|
|
68
73
|
ToolCallPart,
|
|
69
74
|
ToolResultPart,
|
|
75
|
+
SubagentPart,
|
|
70
76
|
UIMessage,
|
|
71
77
|
UIResourceEvent,
|
|
72
78
|
UIResourcePart,
|
|
@@ -144,6 +150,11 @@ export interface StreamProcessorOptions {
|
|
|
144
150
|
recording?: boolean
|
|
145
151
|
/** Initial messages to populate the processor */
|
|
146
152
|
initialMessages?: Array<UIMessage>
|
|
153
|
+
/**
|
|
154
|
+
* Set on the processor of a subagent card. Chunks tagged with this id are
|
|
155
|
+
* the card's own; chunks for an id no card holds are dropped.
|
|
156
|
+
*/
|
|
157
|
+
subagentRunId?: string
|
|
147
158
|
}
|
|
148
159
|
|
|
149
160
|
const STRUCTURED_OUTPUT_UPDATE_BATCH_SIZE = 12
|
|
@@ -207,6 +218,10 @@ export class StreamProcessor {
|
|
|
207
218
|
|
|
208
219
|
// Run tracking (for concurrent run safety)
|
|
209
220
|
private readonly activeRuns = new Set<string>()
|
|
221
|
+
// Direct children by subagentRunId. See childProcessor().
|
|
222
|
+
private readonly childProcessors = new Map<string, StreamProcessor>()
|
|
223
|
+
// Child tool calls whose later events may omit subagentRunId.
|
|
224
|
+
private readonly childToolCalls = new Map<string, string>()
|
|
210
225
|
|
|
211
226
|
// Shared stream state
|
|
212
227
|
private finishReason: string | null = null
|
|
@@ -218,7 +233,10 @@ export class StreamProcessor {
|
|
|
218
233
|
private recording: ChunkRecording | null = null
|
|
219
234
|
private recordingStartTime = 0
|
|
220
235
|
|
|
236
|
+
private readonly subagentRunId?: string
|
|
237
|
+
|
|
221
238
|
constructor(options: StreamProcessorOptions = {}) {
|
|
239
|
+
this.subagentRunId = options.subagentRunId
|
|
222
240
|
this.chunkStrategy = options.chunkStrategy || new ImmediateStrategy()
|
|
223
241
|
this.events = options.events || {}
|
|
224
242
|
this.jsonParser = options.jsonParser || defaultJSONParser
|
|
@@ -378,6 +396,13 @@ export class StreamProcessor {
|
|
|
378
396
|
)
|
|
379
397
|
|
|
380
398
|
if (!messageWithToolCall) {
|
|
399
|
+
const owner = this.childOwningToolCall((part) => part.id === toolCallId)
|
|
400
|
+
if (owner !== undefined) {
|
|
401
|
+
this.updateChild(owner, (child) =>
|
|
402
|
+
child.addToolResult(toolCallId, output, error),
|
|
403
|
+
)
|
|
404
|
+
return
|
|
405
|
+
}
|
|
381
406
|
console.warn(
|
|
382
407
|
`[StreamProcessor] Could not find message with tool call ${toolCallId}`,
|
|
383
408
|
)
|
|
@@ -414,6 +439,15 @@ export class StreamProcessor {
|
|
|
414
439
|
* Add an approval response (called by client after handling onApprovalRequest)
|
|
415
440
|
*/
|
|
416
441
|
addToolApprovalResponse(approvalId: string, approved: boolean): void {
|
|
442
|
+
const owner = this.childOwningToolCall(
|
|
443
|
+
(part) => part.approval?.id === approvalId,
|
|
444
|
+
)
|
|
445
|
+
if (owner !== undefined) {
|
|
446
|
+
this.updateChild(owner, (child) =>
|
|
447
|
+
child.addToolApprovalResponse(approvalId, approved),
|
|
448
|
+
)
|
|
449
|
+
return
|
|
450
|
+
}
|
|
417
451
|
this.messages = updateToolCallApprovalResponse(
|
|
418
452
|
this.messages,
|
|
419
453
|
approvalId,
|
|
@@ -524,6 +558,8 @@ export class StreamProcessor {
|
|
|
524
558
|
this.structuredMessageIds.clear()
|
|
525
559
|
this.structuredOutputUpdateBatches.clear()
|
|
526
560
|
this.pendingManualMessageId = null
|
|
561
|
+
this.childProcessors.clear()
|
|
562
|
+
this.childToolCalls.clear()
|
|
527
563
|
this.emitMessagesChange()
|
|
528
564
|
}
|
|
529
565
|
|
|
@@ -578,12 +614,24 @@ export class StreamProcessor {
|
|
|
578
614
|
})
|
|
579
615
|
}
|
|
580
616
|
|
|
581
|
-
|
|
582
|
-
|
|
617
|
+
if (this.routeToChild(chunk)) return
|
|
618
|
+
|
|
583
619
|
const c = chunk
|
|
584
620
|
// 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
621
|
switch (c.type) {
|
|
586
622
|
// AG-UI Events
|
|
623
|
+
case 'SUBAGENT_STARTED':
|
|
624
|
+
this.handleSubagentStartedEvent(chunk)
|
|
625
|
+
break
|
|
626
|
+
|
|
627
|
+
case 'SUBAGENT_FINISHED':
|
|
628
|
+
this.handleSubagentFinishedEvent(chunk)
|
|
629
|
+
break
|
|
630
|
+
|
|
631
|
+
case 'SUBAGENT_ERROR':
|
|
632
|
+
this.handleSubagentErrorEvent(chunk)
|
|
633
|
+
break
|
|
634
|
+
|
|
587
635
|
case 'TEXT_MESSAGE_START':
|
|
588
636
|
this.handleTextMessageStartEvent(
|
|
589
637
|
chunk as Extract<StreamChunk, { type: 'TEXT_MESSAGE_START' }>,
|
|
@@ -907,6 +955,241 @@ export class StreamProcessor {
|
|
|
907
955
|
this.emitMessagesChange()
|
|
908
956
|
}
|
|
909
957
|
|
|
958
|
+
private findSubagentPart(subagentRunId: string) {
|
|
959
|
+
for (const message of this.messages) {
|
|
960
|
+
for (const part of message.parts) {
|
|
961
|
+
if (part.type === 'subagent' && part.subagent.id === subagentRunId) {
|
|
962
|
+
return { message, part }
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
}
|
|
966
|
+
return undefined
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* The direct child whose card holds `id`: the child itself, or a nested
|
|
971
|
+
* child inside its messages.
|
|
972
|
+
*/
|
|
973
|
+
private childOwning(id: string): string | undefined {
|
|
974
|
+
const holds = (messages: ReadonlyArray<UIMessage>): boolean =>
|
|
975
|
+
messages.some((message) =>
|
|
976
|
+
message.parts.some(
|
|
977
|
+
(part) =>
|
|
978
|
+
part.type === 'subagent' &&
|
|
979
|
+
(part.subagent.id === id || holds(part.subagent.messages)),
|
|
980
|
+
),
|
|
981
|
+
)
|
|
982
|
+
for (const message of this.messages) {
|
|
983
|
+
for (const part of message.parts) {
|
|
984
|
+
if (part.type !== 'subagent') continue
|
|
985
|
+
if (part.subagent.id === id || holds(part.subagent.messages)) {
|
|
986
|
+
return part.subagent.id
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return undefined
|
|
991
|
+
}
|
|
992
|
+
|
|
993
|
+
/** The direct child whose messages hold this tool call. */
|
|
994
|
+
private childOwningToolCall(
|
|
995
|
+
matches: (part: ToolCallPart) => boolean,
|
|
996
|
+
): string | undefined {
|
|
997
|
+
const holds = (messages: ReadonlyArray<UIMessage>): boolean =>
|
|
998
|
+
messages.some((message) =>
|
|
999
|
+
message.parts.some(
|
|
1000
|
+
(part) =>
|
|
1001
|
+
(part.type === 'tool-call' && matches(part)) ||
|
|
1002
|
+
(part.type === 'subagent' && holds(part.subagent.messages)),
|
|
1003
|
+
),
|
|
1004
|
+
)
|
|
1005
|
+
for (const message of this.messages) {
|
|
1006
|
+
for (const part of message.parts) {
|
|
1007
|
+
if (part.type === 'subagent' && holds(part.subagent.messages)) {
|
|
1008
|
+
return part.subagent.id
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}
|
|
1012
|
+
return undefined
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
/**
|
|
1016
|
+
* The processor for a direct child. The card's messages are the source of
|
|
1017
|
+
* truth. The processor re-reads them when something else replaced them.
|
|
1018
|
+
*/
|
|
1019
|
+
private childProcessor(subagentRunId: string): StreamProcessor | undefined {
|
|
1020
|
+
const found = this.findSubagentPart(subagentRunId)
|
|
1021
|
+
if (!found) return undefined
|
|
1022
|
+
const messages = found.part.subagent.messages
|
|
1023
|
+
const existing = this.childProcessors.get(subagentRunId)
|
|
1024
|
+
if (existing) {
|
|
1025
|
+
if (existing.getMessages() !== messages) existing.messages = messages
|
|
1026
|
+
return existing
|
|
1027
|
+
}
|
|
1028
|
+
const child = new StreamProcessor({
|
|
1029
|
+
subagentRunId,
|
|
1030
|
+
initialMessages: messages,
|
|
1031
|
+
events: {
|
|
1032
|
+
onToolCall: (args) => this.events.onToolCall?.(args),
|
|
1033
|
+
onApprovalRequest: (args) => this.events.onApprovalRequest?.(args),
|
|
1034
|
+
onCustomEvent: (name, value, context) =>
|
|
1035
|
+
this.events.onCustomEvent?.(name, value, context),
|
|
1036
|
+
},
|
|
1037
|
+
})
|
|
1038
|
+
this.childProcessors.set(subagentRunId, child)
|
|
1039
|
+
return child
|
|
1040
|
+
}
|
|
1041
|
+
|
|
1042
|
+
/** Run `update` on a direct child, then copy its messages onto the card. */
|
|
1043
|
+
private updateChild(
|
|
1044
|
+
subagentRunId: string,
|
|
1045
|
+
update: (child: StreamProcessor) => void,
|
|
1046
|
+
): void {
|
|
1047
|
+
const child = this.childProcessor(subagentRunId)
|
|
1048
|
+
if (!child) return
|
|
1049
|
+
update(child)
|
|
1050
|
+
const messages = child.getMessages()
|
|
1051
|
+
this.patchSubagent(subagentRunId, (subagent) => {
|
|
1052
|
+
subagent.messages = messages
|
|
1053
|
+
})
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
private handleSubagentStartedEvent(chunk: StreamChunk): void {
|
|
1057
|
+
if (chunk.type !== 'SUBAGENT_STARTED') return
|
|
1058
|
+
const existing = this.findSubagentPart(chunk.subagentRunId)
|
|
1059
|
+
if (existing) {
|
|
1060
|
+
// A resume continues the same child. Start a fresh stream on its card.
|
|
1061
|
+
this.childProcessors.delete(chunk.subagentRunId)
|
|
1062
|
+
this.patchSubagent(chunk.subagentRunId, (subagent) => {
|
|
1063
|
+
subagent.status = 'running'
|
|
1064
|
+
delete subagent.interruptIds
|
|
1065
|
+
delete subagent.error
|
|
1066
|
+
if (chunk.metadata !== undefined) subagent.metadata = chunk.metadata
|
|
1067
|
+
})
|
|
1068
|
+
return
|
|
1069
|
+
}
|
|
1070
|
+
const { messageId } = this.ensureAssistantMessage()
|
|
1071
|
+
const part: SubagentPart = {
|
|
1072
|
+
type: 'subagent',
|
|
1073
|
+
subagent: {
|
|
1074
|
+
id: chunk.subagentRunId,
|
|
1075
|
+
name: chunk.name,
|
|
1076
|
+
...(chunk.description !== undefined && {
|
|
1077
|
+
description: chunk.description,
|
|
1078
|
+
}),
|
|
1079
|
+
status: 'running',
|
|
1080
|
+
...(chunk.parentSubagentRunId !== undefined && {
|
|
1081
|
+
parentSubagentRunId: chunk.parentSubagentRunId,
|
|
1082
|
+
}),
|
|
1083
|
+
...(chunk.parentToolCallId !== undefined && {
|
|
1084
|
+
parentToolCallId: chunk.parentToolCallId,
|
|
1085
|
+
}),
|
|
1086
|
+
...(chunk.metadata !== undefined && { metadata: chunk.metadata }),
|
|
1087
|
+
messages: [],
|
|
1088
|
+
},
|
|
1089
|
+
}
|
|
1090
|
+
this.messages = this.messages.map((message) =>
|
|
1091
|
+
message.id === messageId
|
|
1092
|
+
? { ...message, parts: [...message.parts, part] }
|
|
1093
|
+
: message,
|
|
1094
|
+
)
|
|
1095
|
+
this.emitMessagesChange()
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
private handleSubagentFinishedEvent(chunk: StreamChunk): void {
|
|
1099
|
+
if (chunk.type !== 'SUBAGENT_FINISHED') return
|
|
1100
|
+
this.updateChild(chunk.subagentRunId, (child) => child.finalizeStream())
|
|
1101
|
+
this.patchSubagent(chunk.subagentRunId, (subagent) => {
|
|
1102
|
+
if (subagent.status === 'error') return
|
|
1103
|
+
if (chunk.outcome?.type === 'suspended') {
|
|
1104
|
+
subagent.status = 'suspended'
|
|
1105
|
+
subagent.interruptIds = chunk.outcome.interruptIds ?? []
|
|
1106
|
+
return
|
|
1107
|
+
}
|
|
1108
|
+
subagent.status = 'finished'
|
|
1109
|
+
})
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
private handleSubagentErrorEvent(chunk: StreamChunk): void {
|
|
1113
|
+
if (chunk.type !== 'SUBAGENT_ERROR') return
|
|
1114
|
+
this.updateChild(chunk.subagentRunId, (child) => child.finalizeStream())
|
|
1115
|
+
this.patchSubagent(chunk.subagentRunId, (subagent) => {
|
|
1116
|
+
subagent.status = 'error'
|
|
1117
|
+
subagent.error = {
|
|
1118
|
+
message: chunk.message,
|
|
1119
|
+
...(chunk.code !== undefined && { code: chunk.code }),
|
|
1120
|
+
}
|
|
1121
|
+
})
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
private patchSubagent(
|
|
1125
|
+
subagentRunId: string,
|
|
1126
|
+
patch: (subagent: SubagentPart['subagent']) => void,
|
|
1127
|
+
): void {
|
|
1128
|
+
this.messages = this.messages.map((message) => {
|
|
1129
|
+
const index = message.parts.findIndex(
|
|
1130
|
+
(part) =>
|
|
1131
|
+
part.type === 'subagent' && part.subagent.id === subagentRunId,
|
|
1132
|
+
)
|
|
1133
|
+
if (index === -1) return message
|
|
1134
|
+
const part = message.parts[index]
|
|
1135
|
+
if (!part || part.type !== 'subagent') return message
|
|
1136
|
+
const next: SubagentPart = {
|
|
1137
|
+
type: 'subagent',
|
|
1138
|
+
subagent: { ...part.subagent },
|
|
1139
|
+
}
|
|
1140
|
+
patch(next.subagent)
|
|
1141
|
+
const parts = [...message.parts]
|
|
1142
|
+
parts[index] = next
|
|
1143
|
+
return { ...message, parts }
|
|
1144
|
+
})
|
|
1145
|
+
this.emitMessagesChange()
|
|
1146
|
+
}
|
|
1147
|
+
|
|
1148
|
+
/**
|
|
1149
|
+
* Send a child's chunk to that child's processor, so the card keeps text,
|
|
1150
|
+
* reasoning, tool calls, results, and nested children. A tool event without
|
|
1151
|
+
* `subagentRunId` follows its `TOOL_CALL_START`.
|
|
1152
|
+
*/
|
|
1153
|
+
private routeToChild(chunk: StreamChunk): boolean {
|
|
1154
|
+
let id: string | undefined
|
|
1155
|
+
if (chunk.type === 'SUBAGENT_STARTED') {
|
|
1156
|
+
id = chunk.parentSubagentRunId
|
|
1157
|
+
} else if (
|
|
1158
|
+
chunk.type === 'SUBAGENT_FINISHED' ||
|
|
1159
|
+
chunk.type === 'SUBAGENT_ERROR'
|
|
1160
|
+
) {
|
|
1161
|
+
// A direct child's own lifecycle goes to processChunk's switch, not to a
|
|
1162
|
+
// child.
|
|
1163
|
+
if (this.findSubagentPart(chunk.subagentRunId)) return false
|
|
1164
|
+
id = chunk.subagentRunId
|
|
1165
|
+
} else if ('subagentRunId' in chunk && chunk.subagentRunId) {
|
|
1166
|
+
id = chunk.subagentRunId
|
|
1167
|
+
} else if ('toolCallId' in chunk && typeof chunk.toolCallId === 'string') {
|
|
1168
|
+
id = this.childToolCalls.get(chunk.toolCallId)
|
|
1169
|
+
}
|
|
1170
|
+
if (id === undefined) return false
|
|
1171
|
+
const owner = this.childOwning(id)
|
|
1172
|
+
if (owner === undefined) {
|
|
1173
|
+
// A card's own chunks reach its processor tagged with its id.
|
|
1174
|
+
if (id === this.subagentRunId) return false
|
|
1175
|
+
// No card holds this id. Keep the chunk off this message list.
|
|
1176
|
+
console.warn(
|
|
1177
|
+
`[StreamProcessor] Dropped a chunk for unknown subagent ${id}`,
|
|
1178
|
+
)
|
|
1179
|
+
return true
|
|
1180
|
+
}
|
|
1181
|
+
if (this.findSubagentPart(owner)?.part.subagent.status === 'error') {
|
|
1182
|
+
// An errored child, and its nested children, ignore late events.
|
|
1183
|
+
// Finished and suspended children still accept them, for a resume.
|
|
1184
|
+
return true
|
|
1185
|
+
}
|
|
1186
|
+
if (chunk.type === 'TOOL_CALL_START') {
|
|
1187
|
+
this.childToolCalls.set(chunk.toolCallId, owner)
|
|
1188
|
+
}
|
|
1189
|
+
this.updateChild(owner, (child) => child.processChunk(chunk))
|
|
1190
|
+
return true
|
|
1191
|
+
}
|
|
1192
|
+
|
|
910
1193
|
/**
|
|
911
1194
|
* Handle TEXT_MESSAGE_START event
|
|
912
1195
|
*/
|
|
@@ -1061,10 +1344,11 @@ export class StreamProcessor {
|
|
|
1061
1344
|
// the pre-snapshot state; see `reconcileSnapshotToolCalls`.
|
|
1062
1345
|
const prevMessages = this.messages
|
|
1063
1346
|
const prevById = new Map(prevMessages.map((msg) => [msg.id, msg]))
|
|
1347
|
+
const { top, groups } = splitSubagentWire(chunk.messages)
|
|
1064
1348
|
const normalized = this.mergeReasoningFanOut(
|
|
1065
|
-
|
|
1349
|
+
top.map(aguiSnapshotMessageToUIMessage),
|
|
1066
1350
|
)
|
|
1067
|
-
|
|
1351
|
+
const reconciled = this.reconcileSnapshotToolCalls(
|
|
1068
1352
|
normalized,
|
|
1069
1353
|
prevMessages,
|
|
1070
1354
|
).map((msg) => {
|
|
@@ -1073,9 +1357,113 @@ export class StreamProcessor {
|
|
|
1073
1357
|
if (prev?.metadata == null) return msg
|
|
1074
1358
|
return { ...msg, metadata: prev.metadata }
|
|
1075
1359
|
})
|
|
1360
|
+
this.messages = this.attachSnapshotSubagents(
|
|
1361
|
+
reconciled,
|
|
1362
|
+
top,
|
|
1363
|
+
groups,
|
|
1364
|
+
prevMessages,
|
|
1365
|
+
)
|
|
1076
1366
|
this.emitMessagesChange()
|
|
1077
1367
|
}
|
|
1078
1368
|
|
|
1369
|
+
/**
|
|
1370
|
+
* Put subagent cards back on a snapshot. Child wire messages (tagged with
|
|
1371
|
+
* `subagentRunId`) become a card on the nearest assistant message before
|
|
1372
|
+
* them, or a new assistant message when there is none. A card that the
|
|
1373
|
+
* snapshot does not carry stays on its message, because a server snapshot
|
|
1374
|
+
* can hold the parent history only.
|
|
1375
|
+
*/
|
|
1376
|
+
private attachSnapshotSubagents(
|
|
1377
|
+
messages: Array<UIMessage>,
|
|
1378
|
+
top: ReadonlyArray<AGUIMessage>,
|
|
1379
|
+
groups: ReadonlyArray<SubagentWireGroup<AGUIMessage>>,
|
|
1380
|
+
prevMessages: ReadonlyArray<UIMessage>,
|
|
1381
|
+
): Array<UIMessage> {
|
|
1382
|
+
const cards: Array<{
|
|
1383
|
+
hostId?: string
|
|
1384
|
+
toolCallId?: string
|
|
1385
|
+
part: SubagentPart
|
|
1386
|
+
carried: boolean
|
|
1387
|
+
}> = []
|
|
1388
|
+
const children = new Map<string, StreamProcessor>()
|
|
1389
|
+
for (const group of groups) {
|
|
1390
|
+
const child = new StreamProcessor({ subagentRunId: group.id })
|
|
1391
|
+
child.processChunk({
|
|
1392
|
+
type: EventType.MESSAGES_SNAPSHOT,
|
|
1393
|
+
messages: group.messages,
|
|
1394
|
+
timestamp: Date.now(),
|
|
1395
|
+
})
|
|
1396
|
+
children.set(group.id, child)
|
|
1397
|
+
const { placeholder: _placeholder, ...info } = group.info
|
|
1398
|
+
void _placeholder
|
|
1399
|
+
cards.push({
|
|
1400
|
+
hostId: top
|
|
1401
|
+
.slice(0, group.hostIndex + 1)
|
|
1402
|
+
.findLast((message) => message.role === 'assistant')?.id,
|
|
1403
|
+
part: {
|
|
1404
|
+
type: 'subagent',
|
|
1405
|
+
subagent: { ...info, id: group.id, messages: child.getMessages() },
|
|
1406
|
+
},
|
|
1407
|
+
carried: false,
|
|
1408
|
+
})
|
|
1409
|
+
}
|
|
1410
|
+
const inSnapshot = new Set(groups.map((group) => group.id))
|
|
1411
|
+
for (const message of prevMessages) {
|
|
1412
|
+
for (const part of message.parts) {
|
|
1413
|
+
if (part.type !== 'subagent' || inSnapshot.has(part.subagent.id)) {
|
|
1414
|
+
continue
|
|
1415
|
+
}
|
|
1416
|
+
cards.push({
|
|
1417
|
+
hostId: message.id,
|
|
1418
|
+
...(part.subagent.parentToolCallId !== undefined && {
|
|
1419
|
+
toolCallId: part.subagent.parentToolCallId,
|
|
1420
|
+
}),
|
|
1421
|
+
part,
|
|
1422
|
+
carried: true,
|
|
1423
|
+
})
|
|
1424
|
+
const kept = this.childProcessors.get(part.subagent.id)
|
|
1425
|
+
if (kept) children.set(part.subagent.id, kept)
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
this.childProcessors.clear()
|
|
1429
|
+
for (const [id, child] of children) this.childProcessors.set(id, child)
|
|
1430
|
+
|
|
1431
|
+
let out = messages
|
|
1432
|
+
for (const card of cards) {
|
|
1433
|
+
let index = out.findIndex((message) => message.id === card.hostId)
|
|
1434
|
+
if (index === -1 && card.toolCallId !== undefined) {
|
|
1435
|
+
index = out.findIndex((message) =>
|
|
1436
|
+
message.parts.some(
|
|
1437
|
+
(part) => part.type === 'tool-call' && part.id === card.toolCallId,
|
|
1438
|
+
),
|
|
1439
|
+
)
|
|
1440
|
+
}
|
|
1441
|
+
if (index === -1 && !card.carried) {
|
|
1442
|
+
index = out.findLastIndex((message) => message.role === 'assistant')
|
|
1443
|
+
}
|
|
1444
|
+
if (index === -1) {
|
|
1445
|
+
if (!card.carried) {
|
|
1446
|
+
out = [
|
|
1447
|
+
...out,
|
|
1448
|
+
{ id: generateMessageId(), role: 'assistant', parts: [card.part] },
|
|
1449
|
+
]
|
|
1450
|
+
}
|
|
1451
|
+
continue
|
|
1452
|
+
}
|
|
1453
|
+
out = out.map((message, position) =>
|
|
1454
|
+
position !== index ||
|
|
1455
|
+
message.parts.some(
|
|
1456
|
+
(part) =>
|
|
1457
|
+
part.type === 'subagent' &&
|
|
1458
|
+
part.subagent.id === card.part.subagent.id,
|
|
1459
|
+
)
|
|
1460
|
+
? message
|
|
1461
|
+
: { ...message, parts: [...message.parts, card.part] },
|
|
1462
|
+
)
|
|
1463
|
+
}
|
|
1464
|
+
return out
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1079
1467
|
/**
|
|
1080
1468
|
* Reconcile a freshly normalized snapshot with the pre-snapshot message
|
|
1081
1469
|
* state so unreconstructable tool-call metadata is preserved.
|
|
@@ -1659,7 +2047,10 @@ export class StreamProcessor {
|
|
|
1659
2047
|
// Step 1: Update the tool-call part's output field
|
|
1660
2048
|
let output: unknown
|
|
1661
2049
|
try {
|
|
1662
|
-
output =
|
|
2050
|
+
output =
|
|
2051
|
+
typeof chunk.content === 'string'
|
|
2052
|
+
? JSON.parse(chunk.content)
|
|
2053
|
+
: chunk.content
|
|
1663
2054
|
} catch {
|
|
1664
2055
|
output = chunk.content
|
|
1665
2056
|
}
|
|
@@ -1676,7 +2067,7 @@ export class StreamProcessor {
|
|
|
1676
2067
|
this.messages,
|
|
1677
2068
|
messageId,
|
|
1678
2069
|
chunk.toolCallId,
|
|
1679
|
-
chunk.content,
|
|
2070
|
+
aguiContentToContentParts(chunk.content),
|
|
1680
2071
|
resultState,
|
|
1681
2072
|
resultState === 'error' ? this.extractToolResultError(output) : undefined,
|
|
1682
2073
|
)
|
|
@@ -1732,7 +2123,12 @@ export class StreamProcessor {
|
|
|
1732
2123
|
}
|
|
1733
2124
|
}
|
|
1734
2125
|
|
|
1735
|
-
|
|
2126
|
+
/**
|
|
2127
|
+
* Apply interrupt state to tool-call parts, then fire the client events.
|
|
2128
|
+
* A child's interrupt updates the child's card. `emit` is false there, so
|
|
2129
|
+
* the events fire once, from the top processor.
|
|
2130
|
+
*/
|
|
2131
|
+
private handleInterrupts(interrupts: Array<Interrupt>, emit = true): void {
|
|
1736
2132
|
const hasGeneric = interruptBatchHasGeneric(interrupts)
|
|
1737
2133
|
for (const interrupt of interrupts) {
|
|
1738
2134
|
const metadata =
|
|
@@ -1743,6 +2139,16 @@ export class StreamProcessor {
|
|
|
1743
2139
|
const toolCallId = interrupt.toolCallId
|
|
1744
2140
|
if (!toolCallId) continue
|
|
1745
2141
|
|
|
2142
|
+
const owner =
|
|
2143
|
+
interrupt.subagentRunId !== undefined
|
|
2144
|
+
? this.childOwning(interrupt.subagentRunId)
|
|
2145
|
+
: this.childOwningToolCall((part) => part.id === toolCallId)
|
|
2146
|
+
if (owner !== undefined) {
|
|
2147
|
+
this.updateChild(owner, (child) =>
|
|
2148
|
+
child.handleInterrupts([interrupt], false),
|
|
2149
|
+
)
|
|
2150
|
+
}
|
|
2151
|
+
|
|
1746
2152
|
const toolName =
|
|
1747
2153
|
typeof metadata.toolName === 'string'
|
|
1748
2154
|
? metadata.toolName
|
|
@@ -1750,6 +2156,17 @@ export class StreamProcessor {
|
|
|
1750
2156
|
const input = Object.hasOwn(metadata, 'input') ? metadata.input : {}
|
|
1751
2157
|
|
|
1752
2158
|
if (kind === 'approval' || interrupt.reason === 'approval_required') {
|
|
2159
|
+
if (owner !== undefined) {
|
|
2160
|
+
if (emit) {
|
|
2161
|
+
this.events.onApprovalRequest?.({
|
|
2162
|
+
toolCallId,
|
|
2163
|
+
toolName,
|
|
2164
|
+
input,
|
|
2165
|
+
approvalId: interrupt.id,
|
|
2166
|
+
})
|
|
2167
|
+
}
|
|
2168
|
+
continue
|
|
2169
|
+
}
|
|
1753
2170
|
// An interrupt terminal arrives after MESSAGES_SNAPSHOT, which may have
|
|
1754
2171
|
// rebuilt the message list without the live active-message/tool-call
|
|
1755
2172
|
// maps. Fall back to locating the assistant message that actually owns
|
|
@@ -1774,6 +2191,7 @@ export class StreamProcessor {
|
|
|
1774
2191
|
this.emitMessagesChange()
|
|
1775
2192
|
}
|
|
1776
2193
|
|
|
2194
|
+
if (!emit) continue
|
|
1777
2195
|
this.events.onApprovalRequest?.({
|
|
1778
2196
|
toolCallId,
|
|
1779
2197
|
toolName,
|
|
@@ -1786,7 +2204,7 @@ export class StreamProcessor {
|
|
|
1786
2204
|
if (kind === 'client_tool' || interrupt.reason === 'client_tool_input') {
|
|
1787
2205
|
// Generic interrupts in the same batch decide `toolResume`. Do not
|
|
1788
2206
|
// run client tools until that policy is `continue`.
|
|
1789
|
-
if (hasGeneric) continue
|
|
2207
|
+
if (hasGeneric || !emit) continue
|
|
1790
2208
|
this.events.onToolCall?.({
|
|
1791
2209
|
toolCallId,
|
|
1792
2210
|
toolName,
|
|
@@ -2617,6 +3035,8 @@ export class StreamProcessor {
|
|
|
2617
3035
|
reset(): void {
|
|
2618
3036
|
this.resetStreamState()
|
|
2619
3037
|
this.messages = []
|
|
3038
|
+
this.childProcessors.clear()
|
|
3039
|
+
this.childToolCalls.clear()
|
|
2620
3040
|
}
|
|
2621
3041
|
|
|
2622
3042
|
/**
|