@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.
Files changed (95) 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 +345 -19
  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 +50 -3
  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 +56 -7
  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 +7 -4
  45. package/dist/esm/middlewares/content-guard.js.map +1 -1
  46. package/dist/esm/stream-to-response.js +12 -6
  47. package/dist/esm/stream-to-response.js.map +1 -1
  48. package/dist/esm/strip-to-spec-middleware.js +2 -1
  49. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  50. package/dist/esm/types.d.ts +128 -89
  51. package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
  52. package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
  53. package/dist/esm/utilities/ag-ui-usage.js +66 -3
  54. package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
  55. package/dist/esm/utilities/ag-ui-wire.js +56 -0
  56. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  57. package/dist/esm/utilities/durability-batch.d.ts +8 -0
  58. package/dist/esm/utilities/durability-batch.js +45 -0
  59. package/dist/esm/utilities/durability-batch.js.map +1 -0
  60. package/dist/esm/utilities/normalize-stream-chunk.js +7 -2
  61. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
  62. package/dist/esm/utilities/spec-event-keys.js +13 -8
  63. package/dist/esm/utilities/spec-event-keys.js.map +1 -1
  64. package/dist/esm/utilities/subagent-wire.d.ts +36 -0
  65. package/dist/esm/utilities/subagent-wire.js +131 -0
  66. package/dist/esm/utilities/subagent-wire.js.map +1 -0
  67. package/package.json +4 -4
  68. package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
  69. package/skills/ai-core/media-generation/SKILL.md +2 -2
  70. package/skills/ai-core/middleware/SKILL.md +7 -4
  71. package/src/activities/chat/agents/define-agent.ts +121 -0
  72. package/src/activities/chat/agents/route.ts +115 -0
  73. package/src/activities/chat/agents/spawn.ts +806 -0
  74. package/src/activities/chat/agents/turn.ts +151 -0
  75. package/src/activities/chat/index.ts +540 -16
  76. package/src/activities/chat/messages.ts +125 -15
  77. package/src/activities/chat/middleware/run-store.ts +56 -7
  78. package/src/activities/chat/middleware/types.ts +55 -2
  79. package/src/activities/chat/stream/processor.ts +428 -8
  80. package/src/activities/chat/tools/tool-calls.ts +92 -16
  81. package/src/activities/index.ts +15 -0
  82. package/src/client.ts +22 -35
  83. package/src/index.ts +23 -0
  84. package/src/middlewares/content-guard.ts +7 -5
  85. package/src/stream-to-response.ts +16 -6
  86. package/src/strip-to-spec-middleware.ts +2 -1
  87. package/src/types.ts +177 -95
  88. package/src/utilities/adapter-yield-chunk.ts +10 -2
  89. package/src/utilities/ag-ui-usage.test.ts +38 -0
  90. package/src/utilities/ag-ui-usage.ts +98 -11
  91. package/src/utilities/ag-ui-wire.ts +74 -0
  92. package/src/utilities/durability-batch.ts +48 -0
  93. package/src/utilities/normalize-stream-chunk.ts +10 -2
  94. package/src/utilities/spec-event-keys.ts +34 -7
  95. package/src/utilities/subagent-wire.ts +184 -0
@@ -6,6 +6,7 @@ import { publishRunDisconnectHandler } from "../../delivery-disconnect.js";
6
6
  import { EventType } from "../../types.js";
7
7
  import { rebuildTokenUsage } from "../../utilities/ag-ui-usage.js";
8
8
  import { tanstackMetadata, withTanstackMetadata } from "../../utilities/merge-metadata.js";
9
+ import { withDurabilityBatchHint } from "../../utilities/durability-batch.js";
9
10
  import { normalizeStreamChunk } from "../../utilities/normalize-stream-chunk.js";
10
11
  import { resolveDebugOption } from "../../logger/resolve.js";
11
12
  import { streamToText } from "../../stream-to-response.js";
@@ -17,6 +18,7 @@ import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInt
17
18
  import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
18
19
  import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
19
20
  import { normalizeToolResult } from "../../utilities/tool-result.js";
21
+ import { subagentHostMessageId } from "../../utilities/subagent-wire.js";
20
22
  import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
21
23
  import { appendUiResourceToModelMessages, convertMessagesToModelMessages, generateMessageId, modelMessagesToUIMessages, safeJsonStringify, uiResourcePartFromCustomValue } from "./messages.js";
22
24
  import { uiMessagesToWire } from "../../utilities/ag-ui-wire.js";
@@ -24,6 +26,8 @@ import { restorePublicUsage } from "../../utilities/restore-inbound-chunk.js";
24
26
  import { LazyToolManager } from "./tools/lazy-tool-manager.js";
25
27
  import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
26
28
  import { MiddlewareAbortError, ToolCallManager, executeToolCalls } from "./tools/tool-calls.js";
29
+ import { collectNamedText, createSubagentSink, createSyntheticSubagentTools, normalizeRouterPick, rebindInterrupts, spawnNamedAgents, subagentCallMessages, withChildUsage } from "./agents/spawn.js";
30
+ import { SUBAGENT_PLAN_KEY, readSubagentTurn } from "./agents/turn.js";
27
31
  import { maxIterations } from "./agent-loop-strategies.js";
28
32
  import { MiddlewareRunner } from "./middleware/compose.js";
29
33
  import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
@@ -44,6 +48,14 @@ import { undoNullWidening } from "@tanstack/ai-utils";
44
48
  /** The adapter kind this activity handles */
45
49
  var kind = "text";
46
50
  var interruptBindingMetadataKey = INTERRUPT_BINDING_METADATA_KEY;
51
+ /** Resume entries a subagent tool call owns. The parent run skips them. */
52
+ var CHILD_RESUME_IDS = Symbol("tanstack.ai.childResumeIds");
53
+ function assertNoFileSources(adapterName, messages) {
54
+ for (const message of messages) {
55
+ if (!Array.isArray(message.content)) continue;
56
+ for (const part of message.content) if ("source" in part && part.source.type === "file") throw new Error(`${adapterName} does not support provider file-handle sources ({ type: 'file' }). Pass a data or url source.`);
57
+ }
58
+ }
47
59
  function isInterruptSubmissionError(value) {
48
60
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
49
61
  if (!("scope" in value) || !("code" in value) || !("message" in value) || !("source" in value) || !("retryable" in value) || !("threadId" in value) || !("interruptedRunId" in value) || !("generation" in value) || typeof value.code !== "string" || typeof value.message !== "string" || typeof value.retryable !== "boolean" || typeof value.threadId !== "string" || typeof value.interruptedRunId !== "string" || typeof value.generation !== "number") return false;
@@ -154,6 +166,7 @@ var TextEngine = class {
154
166
  threadId;
155
167
  runIdOverride;
156
168
  parentRunIdOverride;
169
+ subagentRunIdOverride;
157
170
  middlewareRunner;
158
171
  middlewareCtx;
159
172
  sandboxFileQueue = [];
@@ -209,6 +222,7 @@ var TextEngine = class {
209
222
  this.threadId = config.params.threadId || config.params.conversationId || this.createId("thread");
210
223
  this.runIdOverride = config.params.runId;
211
224
  this.parentRunIdOverride = config.params.parentRunId;
225
+ this.subagentRunIdOverride = config.params.subagentRunId;
212
226
  const allMiddleware = [devtoolsMiddleware(), ...config.middleware || []];
213
227
  this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger);
214
228
  this.middlewareAbortController = new AbortController();
@@ -218,6 +232,7 @@ var TextEngine = class {
218
232
  streamId: this.streamId,
219
233
  runId: this.runIdOverride ?? this.requestId,
220
234
  parentRunId: this.parentRunIdOverride,
235
+ subagentRunId: this.subagentRunIdOverride,
221
236
  threadId: this.threadId,
222
237
  conversationId: this.threadId,
223
238
  phase: "init",
@@ -228,8 +243,8 @@ var TextEngine = class {
228
243
  this.abortReason = reason;
229
244
  this.middlewareAbortController?.abort(reason);
230
245
  },
231
- emitCustomEvent: (name, value) => {
232
- this.middlewareCustomQueue.push(this.createCustomEventChunk(name, value));
246
+ emitCustomEvent: (name, value, options) => {
247
+ this.middlewareCustomQueue.push(this.createCustomEventChunk(name, value, options));
233
248
  const waiters = this.middlewareCustomWaiters;
234
249
  this.middlewareCustomWaiters = [];
235
250
  for (const waiter of waiters) waiter();
@@ -516,6 +531,7 @@ var TextEngine = class {
516
531
  const { approvals } = this.collectClientState();
517
532
  const adapterApprovals = /* @__PURE__ */ new Map();
518
533
  for (const [approvalId, resolution] of approvals) adapterApprovals.set(approvalId, typeof resolution === "boolean" ? resolution : resolution.approved);
534
+ assertNoFileSources(this.adapter.name, this.messages);
519
535
  for await (const raw of this.adapter.chatStream({
520
536
  model: this.params.model,
521
537
  messages: this.providerMessages,
@@ -796,7 +812,7 @@ var TextEngine = class {
796
812
  return "wait";
797
813
  }
798
814
  const { approvals, clientToolResults } = this.collectClientState();
799
- const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
815
+ const generator = executeToolCalls(executablePendingCalls, this.resolveExecutableTools(executablePendingCalls), approvals, clientToolResults, (eventName, data, options) => this.createCustomEventChunk(eventName, data, options), {
800
816
  onBeforeToolCall: async (toolCall, tool, args) => {
801
817
  this.logger.tools(`phase=before name=${toolCall.function.name}`, {
802
818
  name: toolCall.function.name,
@@ -834,10 +850,10 @@ var TextEngine = class {
834
850
  needsApproval: executionResult.needsApproval,
835
851
  needsClientExecution: executionResult.needsClientExecution
836
852
  }));
837
- if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
853
+ if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
838
854
  this.discardDeferredToolCallRunFinishedChunks();
839
855
  if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
840
- const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
856
+ const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
841
857
  this.setToolPhase(emitted ? "wait" : "stop");
842
858
  return emitted ? "wait" : "stop";
843
859
  }
@@ -888,7 +904,7 @@ var TextEngine = class {
888
904
  return;
889
905
  }
890
906
  const { approvals, clientToolResults } = this.collectClientState();
891
- const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data) => this.createCustomEventChunk(eventName, data), {
907
+ const generator = executeToolCalls(executableToolCalls, this.resolveExecutableTools(executableToolCalls), approvals, clientToolResults, (eventName, data, options) => this.createCustomEventChunk(eventName, data, options), {
892
908
  onBeforeToolCall: async (toolCall, tool, args) => {
893
909
  this.logger.tools(`phase=before name=${toolCall.function.name}`, {
894
910
  name: toolCall.function.name,
@@ -935,9 +951,9 @@ var TextEngine = class {
935
951
  this.setToolPhase("wait");
936
952
  return;
937
953
  }
938
- if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
954
+ if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
939
955
  if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
940
- const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
956
+ const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
941
957
  this.setToolPhase(emitted ? "wait" : "stop");
942
958
  return;
943
959
  }
@@ -1109,7 +1125,7 @@ var TextEngine = class {
1109
1125
  genericInterruptId() {
1110
1126
  return this.createId("interrupt");
1111
1127
  }
1112
- buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
1128
+ buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = [], childInterrupts = []) {
1113
1129
  const interrupts = [];
1114
1130
  for (const approval of approvals) {
1115
1131
  const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
@@ -1190,6 +1206,7 @@ var TextEngine = class {
1190
1206
  }
1191
1207
  });
1192
1208
  }
1209
+ interrupts.push(...childInterrupts);
1193
1210
  const ids = /* @__PURE__ */ new Set();
1194
1211
  for (const interrupt of interrupts) {
1195
1212
  if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
@@ -1197,13 +1214,13 @@ var TextEngine = class {
1197
1214
  }
1198
1215
  return interrupts;
1199
1216
  }
1200
- buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
1217
+ buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds, childInterrupts) {
1201
1218
  return {
1202
1219
  ...finishEvent,
1203
1220
  timestamp: Date.now(),
1204
1221
  outcome: {
1205
1222
  type: "interrupt",
1206
- interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
1223
+ interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts)
1207
1224
  }
1208
1225
  };
1209
1226
  }
@@ -1297,10 +1314,10 @@ var TextEngine = class {
1297
1314
  };
1298
1315
  yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
1299
1316
  }
1300
- async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
1317
+ async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = [], childInterrupts = []) {
1301
1318
  yield* this.emitSyntheticRunStarted(finishEvent);
1302
1319
  const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
1303
- const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
1320
+ const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts));
1304
1321
  let terminalOutputs;
1305
1322
  try {
1306
1323
  terminalOutputs = [...this.emitPublicChunks(await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal))];
@@ -1665,6 +1682,7 @@ var TextEngine = class {
1665
1682
  tools: baseConfig.tools
1666
1683
  }));
1667
1684
  this.applyMiddlewareConfig(postOnConfig);
1685
+ assertNoFileSources(this.adapter.name, this.messages);
1668
1686
  const structuredCallOptions = {
1669
1687
  chatOptions: {
1670
1688
  model: this.params.model,
@@ -1906,7 +1924,16 @@ var TextEngine = class {
1906
1924
  modelOptions: this.params.modelOptions
1907
1925
  };
1908
1926
  }
1909
- async applyEphemeralInterruptResume(config) {
1927
+ /** Resume entries this run answers itself. A subagent tool owns the rest. */
1928
+ ownResume(resume) {
1929
+ const childIds = this.params[CHILD_RESUME_IDS];
1930
+ return childIds ? resume?.filter((entry) => !childIds.has(entry.interruptId)) : resume;
1931
+ }
1932
+ async applyEphemeralInterruptResume(middlewareConfig) {
1933
+ const config = {
1934
+ ...middlewareConfig,
1935
+ resume: this.ownResume(middlewareConfig.resume)
1936
+ };
1910
1937
  if ((config.resume?.length ?? 0) === 0) return;
1911
1938
  const interruptedRunId = this.parentRunIdOverride;
1912
1939
  if (!interruptedRunId) throw new InterruptResumeValidationError([{
@@ -2025,7 +2052,7 @@ var TextEngine = class {
2025
2052
  const pending = [];
2026
2053
  const ids = /* @__PURE__ */ new Set();
2027
2054
  const batchIndexes = /* @__PURE__ */ new Set();
2028
- for (const resumeItem of this.params.resume ?? []) {
2055
+ for (const resumeItem of this.ownResume(this.params.resume) ?? []) {
2029
2056
  const parsed = readGenericInterruptContinuation(resumeItem.metadata);
2030
2057
  if (parsed.status === "absent") continue;
2031
2058
  if (parsed.status === "invalid") return fail(parsed.message);
@@ -2220,13 +2247,14 @@ var TextEngine = class {
2220
2247
  pending = generator.next();
2221
2248
  }
2222
2249
  }
2223
- createCustomEventChunk(eventName, value) {
2224
- return {
2250
+ createCustomEventChunk(eventName, value, options) {
2251
+ const chunk = {
2225
2252
  type: EventType.CUSTOM,
2226
2253
  timestamp: Date.now(),
2227
2254
  name: eventName,
2228
2255
  value
2229
2256
  };
2257
+ return options?.batch ? withDurabilityBatchHint(chunk) : chunk;
2230
2258
  }
2231
2259
  createId(prefix) {
2232
2260
  return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
@@ -2298,6 +2326,7 @@ function chat(options) {
2298
2326
  validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
2299
2327
  if (options.tools) assertUniqueToolNames(options.tools);
2300
2328
  const { outputSchema, stream } = options;
2329
+ if (outputSchema && (options.subagents?.agents.length ?? 0) > 0) throw new Error("chat() does not support subagents together with outputSchema. Put outputSchema on a child chat() instead.");
2301
2330
  if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
2302
2331
  outputSchema,
2303
2332
  stream: true
@@ -2366,8 +2395,8 @@ function runStreamingText(options) {
2366
2395
  publishDeliverySeams(stream, engineRef);
2367
2396
  return stream;
2368
2397
  }
2369
- async function* streamTextChunks(options, engineRef) {
2370
- const { adapter, middleware, context, debug, mcp, ...textOptions } = options;
2398
+ async function* runChatEngine(options, engineRef) {
2399
+ const { adapter, middleware, context, debug, mcp, subagents: _subagents, ...textOptions } = options;
2371
2400
  const model = adapter.model;
2372
2401
  const logger = resolveDebugOption(debug);
2373
2402
  const mcpManager = MCPManager.from(mcp);
@@ -2390,6 +2419,303 @@ async function* streamTextChunks(options, engineRef) {
2390
2419
  await mcpManager.dispose();
2391
2420
  }
2392
2421
  }
2422
+ async function* streamTextChunks(options, engineRef) {
2423
+ const bag = options.subagents;
2424
+ const agents = bag?.agents ?? [];
2425
+ if (bag && agents.length > 0 && bag.router) {
2426
+ yield* runRoutedSubagents(options, engineRef);
2427
+ return;
2428
+ }
2429
+ if (bag && agents.length > 0) {
2430
+ const threadId = options.threadId ?? `thread-${Date.now()}`;
2431
+ const runId = options.runId ?? `run-${Date.now()}`;
2432
+ const messages = options.messages ?? [];
2433
+ const turn = readSubagentTurn(messages, options.resume);
2434
+ const sink = createSubagentSink();
2435
+ const calls = subagentCallMessages(new Set(bag.agents.map((agent) => agent.name)));
2436
+ const synthetic = createSyntheticSubagentTools(bag, {
2437
+ messages: turn?.before ?? messages,
2438
+ messagesFor: calls.messagesFor,
2439
+ threadId,
2440
+ runId,
2441
+ ...options.parentRunId !== void 0 && { interruptedRunId: options.parentRunId },
2442
+ ...options.abortController && { abortSignal: options.abortController.signal },
2443
+ ...turn && { turn },
2444
+ sink
2445
+ });
2446
+ yield* streamWithChildUsage(runChatEngine({
2447
+ ...options,
2448
+ threadId,
2449
+ runId,
2450
+ ...turn && { [CHILD_RESUME_IDS]: new Set(turn.children.flatMap((child) => child.resume.map((entry) => entry.interruptId))) },
2451
+ tools: [...options.tools ?? [], ...synthetic],
2452
+ middleware: [...options.middleware ?? [], calls.middleware]
2453
+ }, engineRef), sink);
2454
+ return;
2455
+ }
2456
+ yield* runChatEngine(options, engineRef);
2457
+ }
2458
+ /** Add child usage to the next RUN_FINISHED of the parent. */
2459
+ async function* streamWithChildUsage(stream, sink) {
2460
+ for await (const chunk of stream) yield chunk.type === EventType.RUN_FINISHED ? withChildUsage(chunk, sink) : chunk;
2461
+ }
2462
+ async function* runRoutedSubagents(options, engineRef) {
2463
+ const bag = options.subagents;
2464
+ if (!bag?.router) {
2465
+ yield* runChatEngine(options, engineRef);
2466
+ return;
2467
+ }
2468
+ const threadId = options.threadId ?? `thread-${Date.now()}`;
2469
+ const runId = options.runId ?? `run-${Date.now()}`;
2470
+ const messages = options.messages ?? [];
2471
+ const turn = readSubagentTurn(messages, options.resume);
2472
+ if (!turn && (options.resume?.length ?? 0) > 0) {
2473
+ yield* runChatEngine({
2474
+ ...options,
2475
+ threadId,
2476
+ runId,
2477
+ subagents: void 0
2478
+ }, engineRef);
2479
+ return;
2480
+ }
2481
+ const abortSignal = options.abortController?.signal;
2482
+ const turnMessages = turn?.before ?? messages;
2483
+ const subagentPersistence = readRoutedSubagentPersistence(options.middleware);
2484
+ await subagentPersistence?.start({
2485
+ threadId,
2486
+ runId,
2487
+ messages,
2488
+ ...options.resume && { resume: options.resume }
2489
+ });
2490
+ try {
2491
+ const plan = savedPlan(turn?.plan, bag.agents, {
2492
+ threadId,
2493
+ interruptedRunId: options.parentRunId ?? runId,
2494
+ interruptIds: options.resume?.map((entry) => entry.interruptId) ?? []
2495
+ }) ?? normalizeRouterPick(await bag.router({
2496
+ messages: turnMessages,
2497
+ agents: bag.agents,
2498
+ ...abortSignal && { abortSignal }
2499
+ }), bag.agents);
2500
+ if (abortSignal?.aborted) {
2501
+ const error = /* @__PURE__ */ new Error("Aborted");
2502
+ error.name = "AbortError";
2503
+ await subagentPersistence?.abort({
2504
+ threadId,
2505
+ runId,
2506
+ error
2507
+ });
2508
+ return;
2509
+ }
2510
+ const onlyStep = plan.steps.length === 1 ? plan.steps[0] : void 0;
2511
+ if (onlyStep?.names.length === 1 && onlyStep.names[0] === "main") {
2512
+ if (turn) await subagentPersistence?.finish({
2513
+ threadId,
2514
+ runId
2515
+ });
2516
+ yield* runChatEngine({
2517
+ ...options,
2518
+ threadId,
2519
+ runId,
2520
+ subagents: void 0,
2521
+ ...turn && { resume: turn.rest }
2522
+ }, engineRef);
2523
+ return;
2524
+ }
2525
+ yield {
2526
+ type: EventType.RUN_STARTED,
2527
+ threadId,
2528
+ runId,
2529
+ timestamp: Date.now()
2530
+ };
2531
+ const earlier = [...turn?.children ?? []];
2532
+ const takeEarlier = (name) => {
2533
+ const index = earlier.findIndex((child) => child.name === name);
2534
+ return index === -1 ? void 0 : earlier.splice(index, 1)[0];
2535
+ };
2536
+ const sink = createSubagentSink();
2537
+ const stepTexts = [];
2538
+ let stepMessages = turnMessages;
2539
+ let failure;
2540
+ for (const step of plan.steps) {
2541
+ const entries = [];
2542
+ const textByName = /* @__PURE__ */ new Map();
2543
+ for (const name of step.names) {
2544
+ const prior = takeEarlier(name);
2545
+ if (prior?.status === "finished") {
2546
+ textByName.set(name, prior.text);
2547
+ continue;
2548
+ }
2549
+ entries.push(prior?.status === "suspended" ? {
2550
+ name,
2551
+ resume: {
2552
+ subagentRunId: prior.subagentRunId,
2553
+ messages: prior.messages,
2554
+ entries: prior.resume,
2555
+ text: prior.text
2556
+ }
2557
+ } : { name });
2558
+ }
2559
+ const stepChunks = [];
2560
+ if (entries.length > 0) for await (const chunk of spawnNamedAgents(entries, {
2561
+ ...bag,
2562
+ order: step.order ?? bag.order
2563
+ }, {
2564
+ messages: stepMessages,
2565
+ ...abortSignal && { abortSignal },
2566
+ threadId,
2567
+ parentRunId: runId,
2568
+ ...options.parentRunId !== void 0 && { interruptedRunId: options.parentRunId }
2569
+ }, sink)) {
2570
+ const tagged = withPlan(chunk, plan);
2571
+ stepChunks.push(tagged);
2572
+ await subagentPersistence?.chunk({
2573
+ threadId,
2574
+ runId,
2575
+ chunk: tagged
2576
+ });
2577
+ yield tagged;
2578
+ }
2579
+ failure = stepChunks.find((chunk) => chunk.type === EventType.SUBAGENT_ERROR);
2580
+ if (failure) break;
2581
+ if (sink.interrupts.length > 0) break;
2582
+ for (const entry of entries) {
2583
+ const text = collectNamedText(stepChunks, [entry.name]);
2584
+ textByName.set(entry.name, [entry.resume?.text, text].filter(Boolean).join("\n\n"));
2585
+ }
2586
+ const text = step.names.map((name) => textByName.get(name)?.trim() ?? "").filter((block) => block !== "").join("\n\n");
2587
+ if (text) {
2588
+ stepTexts.push(text);
2589
+ stepMessages = [...stepMessages, {
2590
+ role: "assistant",
2591
+ content: text
2592
+ }];
2593
+ }
2594
+ }
2595
+ if (abortSignal?.aborted) {
2596
+ const error = /* @__PURE__ */ new Error("Aborted");
2597
+ error.name = "AbortError";
2598
+ await subagentPersistence?.abort({
2599
+ threadId,
2600
+ runId,
2601
+ error
2602
+ });
2603
+ yield withChildUsage({
2604
+ type: EventType.RUN_FINISHED,
2605
+ threadId,
2606
+ runId,
2607
+ outcome: { type: "cancelled" },
2608
+ timestamp: Date.now()
2609
+ }, sink);
2610
+ return;
2611
+ }
2612
+ if (failure) {
2613
+ const message = failure.message || "A subagent failed";
2614
+ await subagentPersistence?.abort({
2615
+ threadId,
2616
+ runId,
2617
+ error: new Error(message)
2618
+ });
2619
+ yield withChildUsage({
2620
+ type: EventType.RUN_ERROR,
2621
+ threadId,
2622
+ runId,
2623
+ message,
2624
+ ...failure.code !== void 0 ? { code: failure.code } : {},
2625
+ timestamp: Date.now()
2626
+ }, sink);
2627
+ return;
2628
+ }
2629
+ if (sink.interrupts.length > 0) {
2630
+ const interrupts = rebindInterrupts(sink.interrupts, runId);
2631
+ await subagentPersistence?.suspend?.({
2632
+ threadId,
2633
+ runId,
2634
+ interrupts
2635
+ });
2636
+ yield withChildUsage({
2637
+ type: EventType.RUN_FINISHED,
2638
+ threadId,
2639
+ runId,
2640
+ outcome: {
2641
+ type: "interrupt",
2642
+ interrupts
2643
+ },
2644
+ timestamp: Date.now()
2645
+ }, sink);
2646
+ return;
2647
+ }
2648
+ if ((bag.strategy ?? "exclusive") === "handoff") {
2649
+ const childText = stepTexts.join("\n\n");
2650
+ await subagentPersistence?.finish({
2651
+ threadId,
2652
+ runId
2653
+ });
2654
+ yield* streamWithChildUsage(runChatEngine({
2655
+ ...options,
2656
+ threadId,
2657
+ runId,
2658
+ subagents: void 0,
2659
+ ...turn && { resume: turn.rest },
2660
+ messages: [...turnMessages, {
2661
+ id: subagentHostMessageId(runId),
2662
+ role: "assistant",
2663
+ content: childText || "Subagent finished.",
2664
+ metadata: { tanstack: { runId } }
2665
+ }]
2666
+ }, engineRef), sink);
2667
+ return;
2668
+ }
2669
+ await subagentPersistence?.finish({
2670
+ threadId,
2671
+ runId
2672
+ });
2673
+ yield withChildUsage({
2674
+ type: EventType.RUN_FINISHED,
2675
+ threadId,
2676
+ runId,
2677
+ timestamp: Date.now()
2678
+ }, sink);
2679
+ } catch (error) {
2680
+ await subagentPersistence?.abort({
2681
+ threadId,
2682
+ runId,
2683
+ error
2684
+ });
2685
+ throw error;
2686
+ }
2687
+ }
2688
+ /**
2689
+ * The plan from a resumed turn, or undefined when it is absent. A plan that
2690
+ * names agents this chat does not have is stale: re-routing would strand the
2691
+ * suspended child that owns the answers, so fail the resume instead.
2692
+ */
2693
+ function savedPlan(plan, agents, run) {
2694
+ if (typeof plan !== "object" || plan === null || !("steps" in plan)) return;
2695
+ try {
2696
+ return normalizeRouterPick(plan, agents);
2697
+ } catch (error) {
2698
+ throw new InterruptResumeValidationError([{
2699
+ scope: "batch",
2700
+ threadId: run.threadId,
2701
+ interruptedRunId: run.interruptedRunId,
2702
+ generation: 0,
2703
+ interruptIds: run.interruptIds,
2704
+ code: "stale",
2705
+ message: `The saved subagent plan does not match the agents of this chat. ${error instanceof Error ? error.message : String(error)}`,
2706
+ source: "server",
2707
+ retryable: false
2708
+ }]);
2709
+ }
2710
+ }
2711
+ /** Put the router plan on a direct child's SUBAGENT_STARTED metadata. */
2712
+ function withPlan(chunk, plan) {
2713
+ if (chunk.type !== EventType.SUBAGENT_STARTED || chunk.parentSubagentRunId !== void 0) return chunk;
2714
+ return withTanstackMetadata(chunk, { [SUBAGENT_PLAN_KEY]: plan });
2715
+ }
2716
+ function readRoutedSubagentPersistence(middleware) {
2717
+ for (const item of middleware ?? []) if (item.routedSubagentPersistence) return item.routedSubagentPersistence;
2718
+ }
2393
2719
  /**
2394
2720
  * Run non-streaming text - collects all content and returns as a string.
2395
2721
  * Runs the full agentic loop (if tools are provided) but returns collected text.