@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
@@ -8,8 +8,10 @@ import type {
8
8
  ContentPart,
9
9
  CustomEvent,
10
10
  EmitCustomEventOptions,
11
+ Interrupt,
11
12
  ModelMessage,
12
13
  RunFinishedEvent,
14
+ StreamChunk,
13
15
  Tool,
14
16
  ToolCall,
15
17
  ToolCallArgsEvent,
@@ -38,6 +40,25 @@ function safeJsonParse(value: string): unknown {
38
40
  }
39
41
  }
40
42
 
43
+ /** Marks the synthetic tool that runs a subagent. */
44
+ export const SUBAGENT_TOOL = Symbol.for('tanstack.ai.subagentTool')
45
+
46
+ /** Set on the tool context of a subagent tool. Streams a child chunk live. */
47
+ export const EMIT_STREAM_CHUNK = Symbol.for('tanstack.ai.emitStreamChunk')
48
+
49
+ /** What a subagent tool's `execute` returns. */
50
+ export interface SubagentToolOutcome {
51
+ subagentRunId: string
52
+ text: string
53
+ error?: string
54
+ /** Set when the child stopped for outside input. The tool call stays open. */
55
+ interrupts?: Array<Interrupt>
56
+ }
57
+
58
+ function isSubagentTool(tool: AnyTool): boolean {
59
+ return (tool as { [SUBAGENT_TOOL]?: true })[SUBAGENT_TOOL] === true
60
+ }
61
+
41
62
  /**
42
63
  * MCP Apps metadata attached to a server tool at discovery (see
43
64
  * `@tanstack/ai-mcp` discovery + `MCPManager.discover()`).
@@ -505,6 +526,8 @@ interface ExecuteToolCallsResult {
505
526
  needsApproval: Array<ApprovalRequest>
506
527
  /** Tools that need client-side execution */
507
528
  needsClientExecution: Array<ClientToolRequest>
529
+ /** Interrupts raised by subagents that run as tools */
530
+ subagentInterrupts: Array<Interrupt>
508
531
  }
509
532
 
510
533
  /**
@@ -514,8 +537,8 @@ interface ExecuteToolCallsResult {
514
537
  */
515
538
  async function* executeWithEventPolling<T>(
516
539
  executionPromise: Promise<T>,
517
- pendingEvents: Array<CustomEvent>,
518
- ): AsyncGenerator<CustomEvent, T, void> {
540
+ pendingEvents: Array<CustomEvent | StreamChunk>,
541
+ ): AsyncGenerator<CustomEvent | StreamChunk, T, void> {
519
542
  // Use an object to track mutable state across the async boundary
520
543
  const state = { done: false, result: undefined as T }
521
544
  const executionWithFlag = executionPromise.then((r) => {
@@ -532,14 +555,14 @@ async function* executeWithEventPolling<T>(
532
555
  ])
533
556
 
534
557
  // Flush any pending events
535
- let event: CustomEvent | undefined
558
+ let event: CustomEvent | StreamChunk | undefined
536
559
  while ((event = pendingEvents.shift()) !== undefined) {
537
560
  yield event
538
561
  }
539
562
  }
540
563
 
541
564
  // Final flush in case events were emitted right at completion
542
- let event: CustomEvent | undefined
565
+ let event: CustomEvent | StreamChunk | undefined
543
566
  while ((event = pendingEvents.shift()) !== undefined) {
544
567
  yield event
545
568
  }
@@ -612,19 +635,59 @@ export async function* executeServerTool<TContext = unknown>(
612
635
  toolName: string,
613
636
  input: unknown,
614
637
  context: ToolExecutionContext<TContext>,
615
- pendingEvents: Array<CustomEvent>,
638
+ pendingEvents: Array<CustomEvent | StreamChunk>,
616
639
  results: Array<ToolResult>,
617
640
  middlewareHooks?: ToolExecutionMiddlewareHooks,
618
- ): AsyncGenerator<CustomEvent, void, void> {
641
+ subagentInterrupts?: Array<Interrupt>,
642
+ ): AsyncGenerator<CustomEvent | StreamChunk, void, void> {
619
643
  const startTime = Date.now()
620
644
  try {
621
645
  if (!tool.execute) {
622
646
  throw new Error(`Tool ${toolName} has no execute() implementation`)
623
647
  }
648
+ const subagent = isSubagentTool(tool)
649
+ if (subagent) {
650
+ Object.assign(context, {
651
+ [EMIT_STREAM_CHUNK]: (chunk: StreamChunk) => pendingEvents.push(chunk),
652
+ })
653
+ }
624
654
  const executionPromise = Promise.resolve(tool.execute(input, context))
625
655
  let result = yield* executeWithEventPolling(executionPromise, pendingEvents)
626
656
  const duration = Date.now() - startTime
627
657
 
658
+ if (subagent) {
659
+ const outcome = result as SubagentToolOutcome
660
+ if (outcome.interrupts?.length) {
661
+ // The child waits for outside input. Leave the call open so the
662
+ // resume run executes it again and continues the same child.
663
+ subagentInterrupts?.push(...outcome.interrupts)
664
+ return
665
+ }
666
+ const modelResult = outcome.error
667
+ ? { subagentRunId: outcome.subagentRunId, error: outcome.error }
668
+ : { subagentRunId: outcome.subagentRunId, result: outcome.text }
669
+ results.push({
670
+ toolCallId: toolCall.id,
671
+ toolName,
672
+ result: modelResult,
673
+ input,
674
+ output: modelResult,
675
+ duration,
676
+ ...(outcome.error ? { state: 'output-error' as const } : {}),
677
+ })
678
+ await middlewareHooks?.onAfterToolCall?.({
679
+ toolCall,
680
+ tool,
681
+ toolName,
682
+ toolCallId: toolCall.id,
683
+ duration,
684
+ ...(outcome.error
685
+ ? { ok: false as const, error: new Error(outcome.error) }
686
+ : { ok: true as const, result: modelResult }),
687
+ })
688
+ return
689
+ }
690
+
628
691
  // MCP Apps: if this tool links a ui:// resource, eagerly read it and queue
629
692
  // a `ui-resource` CUSTOM event. The MCP source stays live until the run
630
693
  // drains (MCPManager's `connection:'close'` policy disposes on completion),
@@ -633,7 +696,7 @@ export async function* executeServerTool<TContext = unknown>(
633
696
  await emitUiResourceIfLinked(tool, context)
634
697
 
635
698
  // Flush remaining events (including any queued ui-resource event)
636
- let pendingEvent: CustomEvent | undefined
699
+ let pendingEvent: CustomEvent | StreamChunk | undefined
637
700
  while ((pendingEvent = pendingEvents.shift()) !== undefined) {
638
701
  yield pendingEvent
639
702
  }
@@ -671,7 +734,7 @@ export async function* executeServerTool<TContext = unknown>(
671
734
  const duration = Date.now() - startTime
672
735
 
673
736
  // Flush remaining events
674
- let pendingEvent: CustomEvent | undefined
737
+ let pendingEvent: CustomEvent | StreamChunk | undefined
675
738
  while ((pendingEvent = pendingEvents.shift()) !== undefined) {
676
739
  yield pendingEvent
677
740
  }
@@ -767,10 +830,11 @@ export async function* executeToolCalls<TContext = unknown>(
767
830
  userContext?: TContext,
768
831
  abortSignal?: AbortSignal,
769
832
  resumeState?: ToolResumeExecutionState,
770
- ): AsyncGenerator<CustomEvent, ExecuteToolCallsResult, void> {
833
+ ): AsyncGenerator<CustomEvent | StreamChunk, ExecuteToolCallsResult, void> {
771
834
  const results: Array<ToolResult> = []
772
835
  const needsApproval: Array<ApprovalRequest> = []
773
836
  const needsClientExecution: Array<ClientToolRequest> = []
837
+ const subagentInterrupts: Array<Interrupt> = []
774
838
 
775
839
  // Create tool lookup map
776
840
  const toolMap = new Map<string, AnyTool>()
@@ -871,7 +935,7 @@ export async function* executeToolCalls<TContext = unknown>(
871
935
  }
872
936
 
873
937
  // Create a ToolExecutionContext for this tool call with event emission
874
- const pendingEvents: Array<CustomEvent> = []
938
+ const pendingEvents: Array<CustomEvent | StreamChunk> = []
875
939
  const context = {
876
940
  toolCallId: toolCall.id,
877
941
  context: userContext,
@@ -1007,6 +1071,7 @@ export async function* executeToolCalls<TContext = unknown>(
1007
1071
  pendingEvents,
1008
1072
  results,
1009
1073
  middlewareHooks,
1074
+ subagentInterrupts,
1010
1075
  )
1011
1076
  } else {
1012
1077
  // User declined
@@ -1055,8 +1120,9 @@ export async function* executeToolCalls<TContext = unknown>(
1055
1120
  pendingEvents,
1056
1121
  results,
1057
1122
  middlewareHooks,
1123
+ subagentInterrupts,
1058
1124
  )
1059
1125
  }
1060
1126
 
1061
- return { results, needsApproval, needsClientExecution }
1127
+ return { results, needsApproval, needsClientExecution, subagentInterrupts }
1062
1128
  }
@@ -39,6 +39,21 @@ export {
39
39
  type TextActivityResult,
40
40
  } from './chat/index'
41
41
 
42
+ export {
43
+ defineAgent,
44
+ type DefinedAgent,
45
+ type SubagentChoiceOptions,
46
+ type SubagentRunContext,
47
+ } from './chat/agents/define-agent'
48
+ export { subagentRoute, type SubagentRouteOptions } from './chat/agents/route'
49
+ export type {
50
+ SubagentOrder,
51
+ SubagentRouterPick,
52
+ SubagentRouterPlan,
53
+ SubagentStep,
54
+ SubagentStepsPlan,
55
+ } from './chat/agents/spawn'
56
+
42
57
  export {
43
58
  BaseTextAdapter,
44
59
  type AnyTextAdapter,
package/src/client.ts CHANGED
@@ -224,41 +224,25 @@ export async function generationParamsFromRequest<TKind extends GenerationKind>(
224
224
  return generationParamsFromBody(kind, body)
225
225
  }
226
226
 
227
- export enum EventType {
228
- TEXT_MESSAGE_START = 'TEXT_MESSAGE_START',
229
- TEXT_MESSAGE_CONTENT = 'TEXT_MESSAGE_CONTENT',
230
- TEXT_MESSAGE_END = 'TEXT_MESSAGE_END',
231
- TEXT_MESSAGE_CHUNK = 'TEXT_MESSAGE_CHUNK',
232
- TOOL_CALL_START = 'TOOL_CALL_START',
233
- TOOL_CALL_ARGS = 'TOOL_CALL_ARGS',
234
- TOOL_CALL_END = 'TOOL_CALL_END',
235
- TOOL_CALL_CHUNK = 'TOOL_CALL_CHUNK',
236
- TOOL_CALL_RESULT = 'TOOL_CALL_RESULT',
237
- THINKING_START = 'THINKING_START',
238
- THINKING_END = 'THINKING_END',
239
- THINKING_TEXT_MESSAGE_START = 'THINKING_TEXT_MESSAGE_START',
240
- THINKING_TEXT_MESSAGE_CONTENT = 'THINKING_TEXT_MESSAGE_CONTENT',
241
- THINKING_TEXT_MESSAGE_END = 'THINKING_TEXT_MESSAGE_END',
242
- STATE_SNAPSHOT = 'STATE_SNAPSHOT',
243
- STATE_DELTA = 'STATE_DELTA',
244
- MESSAGES_SNAPSHOT = 'MESSAGES_SNAPSHOT',
245
- ACTIVITY_SNAPSHOT = 'ACTIVITY_SNAPSHOT',
246
- ACTIVITY_DELTA = 'ACTIVITY_DELTA',
247
- RAW = 'RAW',
248
- CUSTOM = 'CUSTOM',
249
- RUN_STARTED = 'RUN_STARTED',
250
- RUN_FINISHED = 'RUN_FINISHED',
251
- RUN_ERROR = 'RUN_ERROR',
252
- STEP_STARTED = 'STEP_STARTED',
253
- STEP_FINISHED = 'STEP_FINISHED',
254
- REASONING_START = 'REASONING_START',
255
- REASONING_MESSAGE_START = 'REASONING_MESSAGE_START',
256
- REASONING_MESSAGE_CONTENT = 'REASONING_MESSAGE_CONTENT',
257
- REASONING_MESSAGE_END = 'REASONING_MESSAGE_END',
258
- REASONING_MESSAGE_CHUNK = 'REASONING_MESSAGE_CHUNK',
259
- REASONING_END = 'REASONING_END',
260
- REASONING_ENCRYPTED_VALUE = 'REASONING_ENCRYPTED_VALUE',
261
- }
227
+ export { EventType } from '@ag-ui/core'
228
+
229
+ export {
230
+ defineAgent,
231
+ type DefinedAgent,
232
+ type SubagentChoiceOptions,
233
+ type SubagentRunContext,
234
+ } from './activities/chat/agents/define-agent'
235
+ export {
236
+ subagentRoute,
237
+ type SubagentRouteOptions,
238
+ } from './activities/chat/agents/route'
239
+ export type {
240
+ SubagentOrder,
241
+ SubagentRouterPick,
242
+ SubagentRouterPlan,
243
+ SubagentStep,
244
+ SubagentStepsPlan,
245
+ } from './activities/chat/agents/spawn'
262
246
 
263
247
  export {
264
248
  toolDefinition,
@@ -349,6 +333,9 @@ export type {
349
333
  AudioPart,
350
334
  ContentPart,
351
335
  ContentPartDataSource,
336
+ ContentPartFileSource,
337
+ SubagentHandleData,
338
+ SubagentStatus,
352
339
  ContentPartSource,
353
340
  ContentPartUrlSource,
354
341
  CustomEvent,
package/src/index.ts CHANGED
@@ -78,6 +78,24 @@ export { BaseRerankAdapter } from './activities/rerank/adapter'
78
78
  // Evaluate adapter base + types
79
79
  export { BaseEvaluateAdapter } from './activities/evaluate/adapter'
80
80
 
81
+ export {
82
+ defineAgent,
83
+ type DefinedAgent,
84
+ type SubagentChoiceOptions,
85
+ type SubagentRunContext,
86
+ } from './activities/chat/agents/define-agent'
87
+ export {
88
+ subagentRoute,
89
+ type SubagentRouteOptions,
90
+ } from './activities/chat/agents/route'
91
+ export type {
92
+ SubagentOrder,
93
+ SubagentRouterPick,
94
+ SubagentRouterPlan,
95
+ SubagentStep,
96
+ SubagentStepsPlan,
97
+ } from './activities/chat/agents/spawn'
98
+
81
99
  // Tool definition
82
100
  export {
83
101
  toolDefinition,
@@ -494,6 +512,11 @@ export { generationParamsFromBody, generationParamsFromRequest } from './client'
494
512
 
495
513
  // AG-UI wire serialization (used internally by @tanstack/ai-client)
496
514
  export { uiMessagesToWire } from './utilities/ag-ui-wire'
515
+ export type { SubagentWireInfo } from './utilities/subagent-wire'
516
+ export {
517
+ subagentHostMessageId,
518
+ wireSubagentInfo,
519
+ } from './utilities/subagent-wire'
497
520
  export { mergeMetadata, withTanstackMetadata } from './utilities/merge-metadata'
498
521
  export { fromSpecTokenUsage, toSpecTokenUsage } from './utilities/ag-ui-usage'
499
522
  export type { SpecTokenUsage } from './utilities/ag-ui-usage'
@@ -3,6 +3,7 @@ import type {
3
3
  ChatMiddlewareContext,
4
4
  } from '../activities/chat/middleware/types'
5
5
  import type { StreamChunk } from '../types'
6
+ import type { AdapterYieldChunk } from '../utilities/adapter-yield-chunk'
6
7
 
7
8
  /**
8
9
  * A content guard rule — either a regex pattern with replacement, or a transform function.
@@ -142,11 +143,12 @@ function createDeltaStrategy(
142
143
 
143
144
  if (blockOnMatch) return null // drop chunk
144
145
 
145
- // Strip out the previous `content` field by destructuring it away — with
146
- // `exactOptionalPropertyTypes` we can't assign `content: undefined`
147
- // against `content?: string`. The replacement event carries only the
148
- // filtered delta.
149
- const { content: _strippedContent, ...rest } = chunk
146
+ // Drop the adapter-era content field; the replacement event carries only
147
+ // the filtered delta.
148
+ const { content: _strippedContent, ...rest } = chunk as Extract<
149
+ AdapterYieldChunk,
150
+ { type: 'TEXT_MESSAGE_CONTENT' }
151
+ >
150
152
  void _strippedContent
151
153
  return {
152
154
  ...rest,