@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.
Files changed (85) hide show
  1. package/dist/esm/activities/chat/agents/define-agent.d.ts +81 -0
  2. package/dist/esm/activities/chat/agents/define-agent.js +34 -0
  3. package/dist/esm/activities/chat/agents/define-agent.js.map +1 -0
  4. package/dist/esm/activities/chat/agents/route.d.ts +53 -0
  5. package/dist/esm/activities/chat/agents/route.js +59 -0
  6. package/dist/esm/activities/chat/agents/route.js.map +1 -0
  7. package/dist/esm/activities/chat/agents/spawn.d.ts +124 -0
  8. package/dist/esm/activities/chat/agents/spawn.js +490 -0
  9. package/dist/esm/activities/chat/agents/spawn.js.map +1 -0
  10. package/dist/esm/activities/chat/agents/turn.d.ts +36 -0
  11. package/dist/esm/activities/chat/agents/turn.js +78 -0
  12. package/dist/esm/activities/chat/agents/turn.js.map +1 -0
  13. package/dist/esm/activities/chat/index.d.ts +13 -3
  14. package/dist/esm/activities/chat/index.js +337 -13
  15. package/dist/esm/activities/chat/index.js.map +1 -1
  16. package/dist/esm/activities/chat/messages.d.ts +7 -1
  17. package/dist/esm/activities/chat/messages.js +94 -18
  18. package/dist/esm/activities/chat/messages.js.map +1 -1
  19. package/dist/esm/activities/chat/middleware/run-store.d.ts +43 -7
  20. package/dist/esm/activities/chat/middleware/run-store.js +8 -1
  21. package/dist/esm/activities/chat/middleware/run-store.js.map +1 -1
  22. package/dist/esm/activities/chat/middleware/types.d.ts +47 -1
  23. package/dist/esm/activities/chat/middleware/types.js.map +1 -1
  24. package/dist/esm/activities/chat/stream/processor.d.ts +46 -0
  25. package/dist/esm/activities/chat/stream/processor.js +280 -7
  26. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  27. package/dist/esm/activities/chat/tools/tool-calls.d.ts +17 -3
  28. package/dist/esm/activities/chat/tools/tool-calls.js +54 -5
  29. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  30. package/dist/esm/activities/generateAudio/index.js +1 -1
  31. package/dist/esm/activities/generateImage/index.js +1 -1
  32. package/dist/esm/activities/generateLiveVideo/index.js +1 -1
  33. package/dist/esm/activities/generateSpeech/index.js +1 -1
  34. package/dist/esm/activities/generateTranscription/index.js +1 -1
  35. package/dist/esm/activities/generateVoice/index.js +1 -1
  36. package/dist/esm/activities/generateWorld/index.js +1 -1
  37. package/dist/esm/activities/index.d.ts +3 -0
  38. package/dist/esm/activities/index.js +5 -3
  39. package/dist/esm/activities/summarize/index.js +1 -1
  40. package/dist/esm/client.d.ts +5 -36
  41. package/dist/esm/client.js +4 -37
  42. package/dist/esm/client.js.map +1 -1
  43. package/dist/esm/index.d.ts +5 -0
  44. package/dist/esm/index.js +6 -3
  45. package/dist/esm/middlewares/content-guard.js.map +1 -1
  46. package/dist/esm/types.d.ts +110 -88
  47. package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
  48. package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
  49. package/dist/esm/utilities/ag-ui-usage.js +66 -3
  50. package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
  51. package/dist/esm/utilities/ag-ui-wire.js +56 -0
  52. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  53. package/dist/esm/utilities/normalize-stream-chunk.js +7 -2
  54. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
  55. package/dist/esm/utilities/spec-event-keys.js +13 -8
  56. package/dist/esm/utilities/spec-event-keys.js.map +1 -1
  57. package/dist/esm/utilities/subagent-wire.d.ts +36 -0
  58. package/dist/esm/utilities/subagent-wire.js +131 -0
  59. package/dist/esm/utilities/subagent-wire.js.map +1 -0
  60. package/package.json +2 -2
  61. package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
  62. package/skills/ai-core/media-generation/SKILL.md +2 -2
  63. package/skills/ai-core/middleware/SKILL.md +7 -4
  64. package/src/activities/chat/agents/define-agent.ts +121 -0
  65. package/src/activities/chat/agents/route.ts +115 -0
  66. package/src/activities/chat/agents/spawn.ts +806 -0
  67. package/src/activities/chat/agents/turn.ts +151 -0
  68. package/src/activities/chat/index.ts +529 -11
  69. package/src/activities/chat/messages.ts +125 -15
  70. package/src/activities/chat/middleware/run-store.ts +56 -7
  71. package/src/activities/chat/middleware/types.ts +47 -0
  72. package/src/activities/chat/stream/processor.ts +428 -8
  73. package/src/activities/chat/tools/tool-calls.ts +77 -11
  74. package/src/activities/index.ts +15 -0
  75. package/src/client.ts +22 -35
  76. package/src/index.ts +23 -0
  77. package/src/middlewares/content-guard.ts +7 -5
  78. package/src/types.ts +154 -94
  79. package/src/utilities/adapter-yield-chunk.ts +10 -2
  80. package/src/utilities/ag-ui-usage.test.ts +38 -0
  81. package/src/utilities/ag-ui-usage.ts +98 -11
  82. package/src/utilities/ag-ui-wire.ts +74 -0
  83. package/src/utilities/normalize-stream-chunk.ts +10 -2
  84. package/src/utilities/spec-event-keys.ts +34 -7
  85. 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 as Record<string, unknown>
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 as Record<string, unknown>
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
- [EventType.RUN_STARTED, keys('threadId', 'runId', 'parentRunId', 'input')],
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
+ }