@tanstack/ai 0.58.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 +337 -13
- 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 +47 -1
- 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 +54 -5
- 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 +6 -3
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/types.d.ts +110 -88
- 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/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 +2 -2
- 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 +529 -11
- 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 +47 -0
- package/src/activities/chat/stream/processor.ts +428 -8
- package/src/activities/chat/tools/tool-calls.ts +77 -11
- 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/types.ts +154 -94
- 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/normalize-stream-chunk.ts +10 -2
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
|
@@ -12,6 +12,7 @@ import type {
|
|
|
12
12
|
MessagePart,
|
|
13
13
|
ModelMessage,
|
|
14
14
|
StructuredOutputPart,
|
|
15
|
+
SubagentPart,
|
|
15
16
|
TanStackMessageMetadata,
|
|
16
17
|
UIMessage,
|
|
17
18
|
UIResourcePart,
|
|
@@ -19,6 +20,8 @@ import type {
|
|
|
19
20
|
import type { MetadataRecord } from './merge-metadata'
|
|
20
21
|
import { tanstackMetadata } from './merge-metadata'
|
|
21
22
|
import { normalizeToolResult } from './tool-result'
|
|
23
|
+
import { wireSubagentInfo, wireSubagentRunId } from './subagent-wire'
|
|
24
|
+
import type { SubagentWireInfo } from './subagent-wire'
|
|
22
25
|
import {
|
|
23
26
|
coerceCreatedAt,
|
|
24
27
|
modelMessageToUIMessage,
|
|
@@ -263,6 +266,10 @@ export function uiMessagesToWire(
|
|
|
263
266
|
})
|
|
264
267
|
}
|
|
265
268
|
}
|
|
269
|
+
|
|
270
|
+
for (const part of parts) {
|
|
271
|
+
if (part.type === 'subagent') wire.push(...subagentToWire(part, options))
|
|
272
|
+
}
|
|
266
273
|
}
|
|
267
274
|
|
|
268
275
|
return wire
|
|
@@ -332,6 +339,8 @@ function messageMetadata(
|
|
|
332
339
|
const tanstack: TanStackMessageMetadata = {}
|
|
333
340
|
if (previousTanstack?.model !== undefined)
|
|
334
341
|
tanstack.model = previousTanstack.model
|
|
342
|
+
if (previousTanstack?.runId !== undefined)
|
|
343
|
+
tanstack.runId = previousTanstack.runId
|
|
335
344
|
if (previousTanstack?.signature !== undefined)
|
|
336
345
|
tanstack.signature = previousTanstack.signature
|
|
337
346
|
const createdAt = coerceCreatedAt(msg.createdAt)
|
|
@@ -424,6 +433,71 @@ function collectText(parts: ReadonlyArray<MessagePart>): string {
|
|
|
424
433
|
return out.join('')
|
|
425
434
|
}
|
|
426
435
|
|
|
436
|
+
/**
|
|
437
|
+
* A child's messages, tagged with its AG-UI `subagentRunId`. The card data
|
|
438
|
+
* rides in `metadata.tanstack.subagent`, so the other side can rebuild the
|
|
439
|
+
* card and continue a suspended child.
|
|
440
|
+
*/
|
|
441
|
+
function subagentToWire(
|
|
442
|
+
part: SubagentPart,
|
|
443
|
+
options?: { includeSnapshotStructuredOutput: boolean },
|
|
444
|
+
): Array<WireMessage> {
|
|
445
|
+
const { subagent } = part
|
|
446
|
+
const info: SubagentWireInfo = {
|
|
447
|
+
name: subagent.name,
|
|
448
|
+
status: subagent.status,
|
|
449
|
+
...(subagent.description !== undefined && {
|
|
450
|
+
description: subagent.description,
|
|
451
|
+
}),
|
|
452
|
+
...(subagent.error !== undefined && { error: subagent.error }),
|
|
453
|
+
...(subagent.interruptIds !== undefined && {
|
|
454
|
+
interruptIds: subagent.interruptIds,
|
|
455
|
+
}),
|
|
456
|
+
...(subagent.parentSubagentRunId !== undefined && {
|
|
457
|
+
parentSubagentRunId: subagent.parentSubagentRunId,
|
|
458
|
+
}),
|
|
459
|
+
...(subagent.parentToolCallId !== undefined && {
|
|
460
|
+
parentToolCallId: subagent.parentToolCallId,
|
|
461
|
+
}),
|
|
462
|
+
...(subagent.metadata !== undefined && { metadata: subagent.metadata }),
|
|
463
|
+
}
|
|
464
|
+
const child = uiMessagesToWire(subagent.messages, options)
|
|
465
|
+
const own = child.some((message) => wireSubagentRunId(message) === undefined)
|
|
466
|
+
const messages: Array<WireMessage> = own
|
|
467
|
+
? child
|
|
468
|
+
: [{ id: `subagent:${subagent.id}`, role: 'assistant' }, ...child]
|
|
469
|
+
return messages.map((message, index) => {
|
|
470
|
+
const nestedId = wireSubagentRunId(message)
|
|
471
|
+
if (nestedId !== undefined) {
|
|
472
|
+
const nested = wireSubagentInfo(message)
|
|
473
|
+
if (!nested || nested.parentSubagentRunId !== undefined) return message
|
|
474
|
+
return withSubagentInfo(message, nestedId, {
|
|
475
|
+
...nested,
|
|
476
|
+
parentSubagentRunId: subagent.id,
|
|
477
|
+
})
|
|
478
|
+
}
|
|
479
|
+
return withSubagentInfo(
|
|
480
|
+
message,
|
|
481
|
+
subagent.id,
|
|
482
|
+
!own && index === 0 ? { ...info, placeholder: true } : info,
|
|
483
|
+
)
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function withSubagentInfo(
|
|
488
|
+
message: WireMessage,
|
|
489
|
+
subagentRunId: string,
|
|
490
|
+
info: SubagentWireInfo,
|
|
491
|
+
): WireMessage {
|
|
492
|
+
const metadata = isRecord(message.metadata) ? message.metadata : {}
|
|
493
|
+
const tanstack = isRecord(metadata.tanstack) ? metadata.tanstack : {}
|
|
494
|
+
return {
|
|
495
|
+
...message,
|
|
496
|
+
subagentRunId,
|
|
497
|
+
metadata: { ...metadata, tanstack: { ...tanstack, subagent: info } },
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
|
|
427
501
|
function collectUserContent(
|
|
428
502
|
parts: ReadonlyArray<MessagePart>,
|
|
429
503
|
): string | Array<InputContent> {
|
|
@@ -11,6 +11,14 @@ function stringField(value: unknown): string | undefined {
|
|
|
11
11
|
return typeof value === 'string' && value !== '' ? value : undefined
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
+
function chunkFields(chunk: object) {
|
|
15
|
+
const fields: Record<string, unknown> = {}
|
|
16
|
+
for (const key of Object.keys(chunk)) {
|
|
17
|
+
fields[key] = Reflect.get(chunk, key)
|
|
18
|
+
}
|
|
19
|
+
return fields
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
function encryptedValueExtras(chunk: AdapterYieldChunk): Array<StreamChunk> {
|
|
15
23
|
const extras: Array<StreamChunk> = []
|
|
16
24
|
const timestamp =
|
|
@@ -19,7 +27,7 @@ function encryptedValueExtras(chunk: AdapterYieldChunk): Array<StreamChunk> {
|
|
|
19
27
|
: undefined
|
|
20
28
|
|
|
21
29
|
if (typeof chunk.signature === 'string' && chunk.signature !== '') {
|
|
22
|
-
const source = chunk
|
|
30
|
+
const source = chunkFields(chunk)
|
|
23
31
|
const toolCallId = stringField(source.toolCallId)
|
|
24
32
|
const entityId =
|
|
25
33
|
stringField(chunk.stepId) ??
|
|
@@ -62,7 +70,7 @@ export function normalizeStreamChunk(
|
|
|
62
70
|
chunk: AdapterYieldChunk,
|
|
63
71
|
): Array<StreamChunk> {
|
|
64
72
|
const specKeys = specKeysFor(chunk.type)
|
|
65
|
-
const source = chunk
|
|
73
|
+
const source = chunkFields(chunk)
|
|
66
74
|
const specChunk: Record<string, unknown> & {
|
|
67
75
|
metadata?: MetadataRecord | null
|
|
68
76
|
} = {}
|
|
@@ -30,11 +30,6 @@ const SPEC_KEYS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
|
|
|
30
30
|
EventType.TOOL_CALL_RESULT,
|
|
31
31
|
keys('messageId', 'toolCallId', 'content', 'role'),
|
|
32
32
|
],
|
|
33
|
-
[EventType.THINKING_START, keys('title')],
|
|
34
|
-
[EventType.THINKING_END, SHARED],
|
|
35
|
-
[EventType.THINKING_TEXT_MESSAGE_START, SHARED],
|
|
36
|
-
[EventType.THINKING_TEXT_MESSAGE_CONTENT, keys('delta')],
|
|
37
|
-
[EventType.THINKING_TEXT_MESSAGE_END, SHARED],
|
|
38
33
|
[EventType.STATE_SNAPSHOT, keys('snapshot')],
|
|
39
34
|
[EventType.STATE_DELTA, keys('delta')],
|
|
40
35
|
[EventType.MESSAGES_SNAPSHOT, keys('messages')],
|
|
@@ -45,7 +40,10 @@ const SPEC_KEYS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
|
|
|
45
40
|
[EventType.ACTIVITY_DELTA, keys('messageId', 'activityType', 'patch')],
|
|
46
41
|
[EventType.RAW, keys('event', 'source')],
|
|
47
42
|
[EventType.CUSTOM, keys('name', 'value')],
|
|
48
|
-
[
|
|
43
|
+
[
|
|
44
|
+
EventType.RUN_STARTED,
|
|
45
|
+
keys('threadId', 'runId', 'protocolVersion', 'parentRunId', 'input'),
|
|
46
|
+
],
|
|
49
47
|
[
|
|
50
48
|
EventType.RUN_FINISHED,
|
|
51
49
|
keys('threadId', 'runId', 'result', 'outcome', 'usage'),
|
|
@@ -63,10 +61,39 @@ const SPEC_KEYS: ReadonlyMap<string, ReadonlySet<string>> = new Map([
|
|
|
63
61
|
EventType.REASONING_ENCRYPTED_VALUE,
|
|
64
62
|
keys('subtype', 'entityId', 'encryptedValue'),
|
|
65
63
|
],
|
|
64
|
+
[
|
|
65
|
+
EventType.SUBAGENT_STARTED,
|
|
66
|
+
keys(
|
|
67
|
+
'subagentRunId',
|
|
68
|
+
'name',
|
|
69
|
+
'description',
|
|
70
|
+
'parentSubagentRunId',
|
|
71
|
+
'parentToolCallId',
|
|
72
|
+
'parentMessageId',
|
|
73
|
+
),
|
|
74
|
+
],
|
|
75
|
+
[EventType.SUBAGENT_FINISHED, keys('subagentRunId', 'result', 'outcome')],
|
|
76
|
+
[EventType.SUBAGENT_ERROR, keys('subagentRunId', 'message', 'code')],
|
|
66
77
|
])
|
|
67
78
|
|
|
79
|
+
const RUN_SCOPED = new Set<string>([
|
|
80
|
+
EventType.RUN_STARTED,
|
|
81
|
+
EventType.RUN_FINISHED,
|
|
82
|
+
EventType.RUN_ERROR,
|
|
83
|
+
EventType.MESSAGES_SNAPSHOT,
|
|
84
|
+
])
|
|
85
|
+
|
|
86
|
+
const ATTRIBUTABLE_KEYS = new Map(
|
|
87
|
+
[...SPEC_KEYS]
|
|
88
|
+
.filter(([type]) => !RUN_SCOPED.has(type))
|
|
89
|
+
.map(
|
|
90
|
+
([type, fields]) =>
|
|
91
|
+
[type, new Set([...fields, 'subagentRunId'])] as const,
|
|
92
|
+
),
|
|
93
|
+
)
|
|
94
|
+
|
|
68
95
|
export function specKeysFor(type: string): ReadonlySet<string> {
|
|
69
|
-
return SPEC_KEYS.get(type) ?? SHARED
|
|
96
|
+
return ATTRIBUTABLE_KEYS.get(type) ?? SPEC_KEYS.get(type) ?? SHARED
|
|
70
97
|
}
|
|
71
98
|
|
|
72
99
|
export function isSpecTopLevelKey(type: string, key: string): boolean {
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import type { SubagentInfo as AGUISubagentInfo } from '@ag-ui/core'
|
|
2
|
+
import type { SubagentHandleData } from '../types'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Card data that travels on each child wire message, in
|
|
6
|
+
* `metadata.tanstack.subagent`. The messages carry the AG-UI `subagentRunId`.
|
|
7
|
+
*/
|
|
8
|
+
export interface SubagentWireInfo
|
|
9
|
+
extends
|
|
10
|
+
AGUISubagentInfo,
|
|
11
|
+
Pick<
|
|
12
|
+
SubagentHandleData,
|
|
13
|
+
| 'status'
|
|
14
|
+
| 'error'
|
|
15
|
+
| 'interruptIds'
|
|
16
|
+
| 'parentSubagentRunId'
|
|
17
|
+
| 'parentToolCallId'
|
|
18
|
+
| 'metadata'
|
|
19
|
+
> {
|
|
20
|
+
/** The child has no messages yet. This wire message only holds the card. */
|
|
21
|
+
placeholder?: true
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface SubagentWireGroup<T> {
|
|
25
|
+
id: string
|
|
26
|
+
info: SubagentWireInfo
|
|
27
|
+
/** The child's messages without its own tag. Nested children keep theirs. */
|
|
28
|
+
messages: Array<T>
|
|
29
|
+
/** Index in `top` of the last message before this child, or -1. */
|
|
30
|
+
hostIndex: number
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
34
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function wireSubagentRunId(message: unknown): string | undefined {
|
|
38
|
+
if (!isRecord(message) || 'parts' in message) return undefined
|
|
39
|
+
const id = message.subagentRunId
|
|
40
|
+
return typeof id === 'string' && id !== '' ? id : undefined
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function wireSubagentInfo(
|
|
44
|
+
message: unknown,
|
|
45
|
+
): SubagentWireInfo | undefined {
|
|
46
|
+
if (!isRecord(message) || !isRecord(message.metadata)) return undefined
|
|
47
|
+
const tanstack = message.metadata.tanstack
|
|
48
|
+
if (!isRecord(tanstack) || !isRecord(tanstack.subagent)) return undefined
|
|
49
|
+
// Wire data comes from the client. Keep only well-formed fields.
|
|
50
|
+
const { name, status, error, interruptIds } = tanstack.subagent
|
|
51
|
+
if (typeof name !== 'string' || !isStatus(status)) return undefined
|
|
52
|
+
const {
|
|
53
|
+
description,
|
|
54
|
+
parentSubagentRunId,
|
|
55
|
+
parentToolCallId,
|
|
56
|
+
metadata,
|
|
57
|
+
placeholder,
|
|
58
|
+
} = tanstack.subagent
|
|
59
|
+
return {
|
|
60
|
+
name,
|
|
61
|
+
status,
|
|
62
|
+
...(typeof description === 'string' && { description }),
|
|
63
|
+
...(isRecord(error) &&
|
|
64
|
+
typeof error.message === 'string' && {
|
|
65
|
+
error: {
|
|
66
|
+
message: error.message,
|
|
67
|
+
...(typeof error.code === 'string' && { code: error.code }),
|
|
68
|
+
},
|
|
69
|
+
}),
|
|
70
|
+
...(Array.isArray(interruptIds) && {
|
|
71
|
+
interruptIds: interruptIds.filter(
|
|
72
|
+
(id): id is string => typeof id === 'string',
|
|
73
|
+
),
|
|
74
|
+
}),
|
|
75
|
+
...(typeof parentSubagentRunId === 'string' && { parentSubagentRunId }),
|
|
76
|
+
...(typeof parentToolCallId === 'string' && { parentToolCallId }),
|
|
77
|
+
...(isRecord(metadata) && { metadata }),
|
|
78
|
+
...(placeholder === true && { placeholder: true as const }),
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function isStatus(value: unknown): value is SubagentWireInfo['status'] {
|
|
83
|
+
return (
|
|
84
|
+
value === 'running' ||
|
|
85
|
+
value === 'finished' ||
|
|
86
|
+
value === 'error' ||
|
|
87
|
+
value === 'suspended'
|
|
88
|
+
)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function untag<T>(message: T): T {
|
|
92
|
+
if (!isRecord(message)) return message
|
|
93
|
+
const { subagentRunId: _id, ...rest } = message
|
|
94
|
+
void _id
|
|
95
|
+
const metadata = isRecord(rest.metadata) ? rest.metadata : undefined
|
|
96
|
+
const tanstack = isRecord(metadata?.tanstack) ? metadata.tanstack : undefined
|
|
97
|
+
if (!metadata || !tanstack || !('subagent' in tanstack)) return rest as T
|
|
98
|
+
const { subagent: _info, ...tanstackRest } = tanstack
|
|
99
|
+
void _info
|
|
100
|
+
return { ...rest, metadata: { ...metadata, tanstack: tanstackRest } } as T
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Split wire messages into the parent's own messages and one group per direct
|
|
105
|
+
* child. A nested child's messages stay inside its parent's group.
|
|
106
|
+
*/
|
|
107
|
+
export function splitSubagentWire<T>(messages: ReadonlyArray<T>): {
|
|
108
|
+
top: Array<T>
|
|
109
|
+
groups: Array<SubagentWireGroup<T>>
|
|
110
|
+
} {
|
|
111
|
+
const parentOf = new Map<string, string | undefined>()
|
|
112
|
+
for (const message of messages) {
|
|
113
|
+
const id = wireSubagentRunId(message)
|
|
114
|
+
if (id !== undefined && !parentOf.has(id)) {
|
|
115
|
+
parentOf.set(id, wireSubagentInfo(message)?.parentSubagentRunId)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
const rootOf = (id: string) => {
|
|
119
|
+
let current = id
|
|
120
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
121
|
+
const parent = parentOf.get(current)
|
|
122
|
+
if (parent === undefined || !parentOf.has(parent)) return current
|
|
123
|
+
current = parent
|
|
124
|
+
}
|
|
125
|
+
return current
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const top: Array<T> = []
|
|
129
|
+
const groups = new Map<string, SubagentWireGroup<T>>()
|
|
130
|
+
for (const message of messages) {
|
|
131
|
+
const id = wireSubagentRunId(message)
|
|
132
|
+
if (id === undefined) {
|
|
133
|
+
top.push(message)
|
|
134
|
+
continue
|
|
135
|
+
}
|
|
136
|
+
const root = rootOf(id)
|
|
137
|
+
let group = groups.get(root)
|
|
138
|
+
if (!group) {
|
|
139
|
+
group = {
|
|
140
|
+
id: root,
|
|
141
|
+
info: { name: 'subagent', status: 'finished' },
|
|
142
|
+
messages: [],
|
|
143
|
+
hostIndex: top.length - 1,
|
|
144
|
+
}
|
|
145
|
+
groups.set(root, group)
|
|
146
|
+
}
|
|
147
|
+
if (id !== root) {
|
|
148
|
+
group.messages.push(message)
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
const info = wireSubagentInfo(message)
|
|
152
|
+
if (info) group.info = info
|
|
153
|
+
if (!info?.placeholder) group.messages.push(untag(message))
|
|
154
|
+
}
|
|
155
|
+
return { top, groups: [...groups.values()] }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/** Text a child wrote, for the parent model and for a later child. */
|
|
159
|
+
export function subagentWireText(messages: ReadonlyArray<unknown>): string {
|
|
160
|
+
const blocks: Array<string> = []
|
|
161
|
+
for (const message of messages) {
|
|
162
|
+
if (!isRecord(message) || message.role !== 'assistant') continue
|
|
163
|
+
if (wireSubagentRunId(message) !== undefined) continue
|
|
164
|
+
const content = message.content
|
|
165
|
+
if (typeof content === 'string' && content.trim() !== '') {
|
|
166
|
+
blocks.push(content.trim())
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
const nested = splitSubagentWire(messages).groups
|
|
170
|
+
for (const group of nested) {
|
|
171
|
+
const text = subagentWireText(group.messages)
|
|
172
|
+
if (text !== '') blocks.push(`${group.info.name}:\n${text}`)
|
|
173
|
+
}
|
|
174
|
+
return blocks.join('\n\n')
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* The id of the parent assistant message that hosts a routed turn's cards.
|
|
179
|
+
* The persistence recorder writes that message, and a handoff run passes the
|
|
180
|
+
* same id so the stored thread keeps one copy.
|
|
181
|
+
*/
|
|
182
|
+
export function subagentHostMessageId(runId: string) {
|
|
183
|
+
return `assistant:${runId}`
|
|
184
|
+
}
|