@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
@@ -0,0 +1,78 @@
1
+ import { splitSubagentWire, subagentWireText, wireSubagentRunId } from "../../../utilities/subagent-wire.js";
2
+ //#region src/activities/chat/agents/turn.ts
3
+ /** Key of the router plan in `SUBAGENT_STARTED` metadata (`metadata.tanstack`). */
4
+ var SUBAGENT_PLAN_KEY = "subagentPlan";
5
+ function planOf(metadata) {
6
+ const tanstack = metadata?.tanstack;
7
+ if (typeof tanstack !== "object" || tanstack === null) return void 0;
8
+ return tanstack[SUBAGENT_PLAN_KEY];
9
+ }
10
+ function uiText(messages) {
11
+ return messages.flatMap((message) => message.role === "assistant" ? message.parts.flatMap((part) => part.type === "text" && part.content.trim() !== "" ? [part.content.trim()] : []) : []).join("\n\n");
12
+ }
13
+ function partInterruptIds(part) {
14
+ return [...part.subagent.interruptIds ?? [], ...part.subagent.messages.flatMap((message) => message.parts.flatMap((nested) => nested.type === "subagent" ? partInterruptIds(nested) : []))];
15
+ }
16
+ function groupInterruptIds(group) {
17
+ return [...group.info.interruptIds ?? [], ...splitSubagentWire(group.messages).groups.flatMap(groupInterruptIds)];
18
+ }
19
+ /**
20
+ * Find the children of the trailing assistant turn that `resume` answers.
21
+ * Returns undefined when no child owns a resume entry.
22
+ */
23
+ function readSubagentTurn(messages, resume) {
24
+ if (!resume || resume.length === 0) return void 0;
25
+ const lastUser = messages.findLastIndex((message) => message.role === "user" && wireSubagentRunId(message) === void 0);
26
+ const before = messages.slice(0, lastUser + 1);
27
+ const turn = messages.slice(lastUser + 1);
28
+ const found = [];
29
+ let plan;
30
+ for (const message of turn) {
31
+ if (!("parts" in message)) continue;
32
+ for (const part of message.parts) {
33
+ if (part.type !== "subagent") continue;
34
+ plan ??= planOf(part.subagent.metadata);
35
+ found.push({
36
+ subagentRunId: part.subagent.id,
37
+ name: part.subagent.name,
38
+ status: part.subagent.status,
39
+ ...part.subagent.parentToolCallId !== void 0 && { parentToolCallId: part.subagent.parentToolCallId },
40
+ messages: part.subagent.messages,
41
+ text: uiText(part.subagent.messages),
42
+ ids: new Set(partInterruptIds(part))
43
+ });
44
+ }
45
+ }
46
+ for (const group of splitSubagentWire(turn).groups) {
47
+ plan ??= planOf(group.info.metadata);
48
+ found.push({
49
+ subagentRunId: group.id,
50
+ name: group.info.name,
51
+ status: group.info.status,
52
+ ...group.info.parentToolCallId !== void 0 && { parentToolCallId: group.info.parentToolCallId },
53
+ messages: group.messages,
54
+ text: subagentWireText(group.messages),
55
+ ids: new Set(groupInterruptIds(group))
56
+ });
57
+ }
58
+ const owned = /* @__PURE__ */ new Set();
59
+ const children = found.map(({ ids, ...child }) => {
60
+ const entries = resume.filter((entry) => ids.has(entry.interruptId));
61
+ for (const entry of entries) owned.add(entry.interruptId);
62
+ return {
63
+ ...child,
64
+ resume: entries
65
+ };
66
+ });
67
+ if (owned.size === 0) return void 0;
68
+ return {
69
+ before,
70
+ children,
71
+ rest: resume.filter((entry) => !owned.has(entry.interruptId)),
72
+ ...plan !== void 0 && { plan }
73
+ };
74
+ }
75
+ //#endregion
76
+ export { SUBAGENT_PLAN_KEY, readSubagentTurn };
77
+
78
+ //# sourceMappingURL=turn.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"turn.js","names":[],"sources":["../../../../../src/activities/chat/agents/turn.ts"],"sourcesContent":["import {\n splitSubagentWire,\n subagentWireText,\n wireSubagentRunId,\n} from '../../../utilities/subagent-wire'\nimport type { SubagentWireGroup } from '../../../utilities/subagent-wire'\nimport type {\n ModelMessage,\n RunAgentResumeItem,\n SubagentPart,\n SubagentStatus,\n UIMessage,\n} from '../../../types'\n\ntype AnyMessage = UIMessage | ModelMessage\n\nexport interface SubagentTurnChild {\n subagentRunId: string\n name: string\n status: SubagentStatus\n parentToolCallId?: string\n /** The child's own messages from the earlier run. */\n messages: Array<AnyMessage>\n /** Text the child wrote. */\n text: string\n /** Resume entries for this child and its nested children. */\n resume: Array<RunAgentResumeItem>\n}\n\n/** The interrupted turn that a resume continues. */\nexport interface SubagentTurn {\n /**\n * Messages up to and including the last top-level user message. Child wire\n * messages do not count.\n */\n before: Array<AnyMessage>\n /** Direct children of the parent in that turn. */\n children: Array<SubagentTurnChild>\n /** Resume entries that no child owns. They belong to the parent run. */\n rest: Array<RunAgentResumeItem>\n /** The router plan of the earlier run, from the children's metadata. */\n plan?: unknown\n}\n\n/** Key of the router plan in `SUBAGENT_STARTED` metadata (`metadata.tanstack`). */\nexport const SUBAGENT_PLAN_KEY = 'subagentPlan'\n\nfunction planOf(metadata: Record<string, unknown> | undefined): unknown {\n const tanstack = metadata?.tanstack\n if (typeof tanstack !== 'object' || tanstack === null) return undefined\n return (tanstack as Record<string, unknown>)[SUBAGENT_PLAN_KEY]\n}\n\nfunction uiText(messages: ReadonlyArray<UIMessage>) {\n return messages\n .flatMap((message) =>\n message.role === 'assistant'\n ? message.parts.flatMap((part) =>\n part.type === 'text' && part.content.trim() !== ''\n ? [part.content.trim()]\n : [],\n )\n : [],\n )\n .join('\\n\\n')\n}\n\nfunction partInterruptIds(part: SubagentPart): Array<string> {\n return [\n ...(part.subagent.interruptIds ?? []),\n ...part.subagent.messages.flatMap((message) =>\n message.parts.flatMap((nested) =>\n nested.type === 'subagent' ? partInterruptIds(nested) : [],\n ),\n ),\n ]\n}\n\nfunction groupInterruptIds(group: SubagentWireGroup<unknown>): Array<string> {\n return [\n ...(group.info.interruptIds ?? []),\n ...splitSubagentWire(group.messages).groups.flatMap(groupInterruptIds),\n ]\n}\n\n/**\n * Find the children of the trailing assistant turn that `resume` answers.\n * Returns undefined when no child owns a resume entry.\n */\nexport function readSubagentTurn(\n messages: ReadonlyArray<AnyMessage>,\n resume: ReadonlyArray<RunAgentResumeItem> | undefined,\n): SubagentTurn | undefined {\n if (!resume || resume.length === 0) return undefined\n const lastUser = messages.findLastIndex(\n (message) =>\n message.role === 'user' && wireSubagentRunId(message) === undefined,\n )\n const before = messages.slice(0, lastUser + 1)\n const turn = messages.slice(lastUser + 1)\n\n const found: Array<Omit<SubagentTurnChild, 'resume'> & { ids: Set<string> }> =\n []\n let plan: unknown\n for (const message of turn) {\n if (!('parts' in message)) continue\n for (const part of message.parts) {\n if (part.type !== 'subagent') continue\n plan ??= planOf(part.subagent.metadata)\n found.push({\n subagentRunId: part.subagent.id,\n name: part.subagent.name,\n status: part.subagent.status,\n ...(part.subagent.parentToolCallId !== undefined && {\n parentToolCallId: part.subagent.parentToolCallId,\n }),\n messages: part.subagent.messages,\n text: uiText(part.subagent.messages),\n ids: new Set(partInterruptIds(part)),\n })\n }\n }\n for (const group of splitSubagentWire(turn).groups) {\n plan ??= planOf(group.info.metadata)\n found.push({\n subagentRunId: group.id,\n name: group.info.name,\n status: group.info.status,\n ...(group.info.parentToolCallId !== undefined && {\n parentToolCallId: group.info.parentToolCallId,\n }),\n messages: group.messages,\n text: subagentWireText(group.messages),\n ids: new Set(groupInterruptIds(group)),\n })\n }\n\n const owned = new Set<string>()\n const children = found.map(({ ids, ...child }) => {\n const entries = resume.filter((entry) => ids.has(entry.interruptId))\n for (const entry of entries) owned.add(entry.interruptId)\n return { ...child, resume: entries }\n })\n if (owned.size === 0) return undefined\n return {\n before,\n children,\n rest: resume.filter((entry) => !owned.has(entry.interruptId)),\n ...(plan !== undefined && { plan }),\n }\n}\n"],"mappings":"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"}
@@ -1,4 +1,6 @@
1
1
  import { InterruptDefinition } from '../../interrupt-definition.js';
2
+ import { DefinedAgent } from './agents/define-agent.js';
3
+ import { SubagentsBag } from './agents/spawn.js';
2
4
  import { AnyTextAdapter } from './adapter.js';
3
5
  import { AnyTool, ChatStream, ConstrainedModelMessage, InferSchemaType, LazyToolsConfig, ModelMessage, ProviderTool, SchemaInput, StructuredOutputStream, TextOptions, UIMessage } from '../../types.js';
4
6
  import { AnyChatMiddleware, ChatMiddleware } from './middleware/types.js';
@@ -45,7 +47,7 @@ type RuntimeContextOption<TTools, TMiddleware, TContext> = [
45
47
  };
46
48
  type ExactMiddlewareOption<TTools, TContext, TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>>, TMiddleware extends Array<unknown> | undefined> = [TMiddleware] extends [undefined] ? Array<RegistryChatMiddleware<InlineChatContext<TTools, TContext>, NoInfer<TInterrupts>>> : TMiddleware & (TMiddleware extends Array<RegistryChatMiddleware<InlineChatContext<TTools, NoInfer<TContext>>, NoInfer<TInterrupts>>> ? Array<RegistryChatMiddleware<InlineChatContext<TTools, TContext>, NoInfer<TInterrupts>>> : CheckInterruptRegistry<TInterrupts, TMiddleware>) & CheckCoverage<Extract<TMiddleware, ReadonlyArray<AnyChatMiddleware>>>;
47
49
  type TextActivityOptionsWithContext<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined, TStream extends boolean, TTools extends TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'], TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = [
48
- ], TContext = unknown, TMiddleware extends Array<unknown> | undefined = undefined> = Omit<TextActivityOptions<TAdapter, TSchema, TStream, any>, 'tools' | 'middleware' | 'context' | 'interrupts'> & {
50
+ ], TContext = unknown, TMiddleware extends Array<unknown> | undefined = undefined, TAgents extends ReadonlyArray<DefinedAgent> = ReadonlyArray<DefinedAgent>> = Omit<TextActivityOptions<TAdapter, TSchema, TStream, any, TAgents>, 'tools' | 'middleware' | 'context' | 'interrupts'> & {
49
51
  tools?: TTools;
50
52
  interrupts?: TInterrupts & CheckUniqueInterruptDefinitions<TInterrupts>;
51
53
  middleware?: ExactMiddlewareOption<TTools, TContext, TInterrupts, TMiddleware>;
@@ -59,7 +61,7 @@ type TextActivityOptionsWithContext<TAdapter extends AnyTextAdapter, TSchema ext
59
61
  * @template TStream - Whether to stream the output (default: true)
60
62
  * @template TContext - Runtime context value threaded to middleware hooks and server tools
61
63
  */
62
- export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined, TStream extends boolean, TContext = unknown> {
64
+ export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined, TStream extends boolean, TContext = unknown, TAgents extends ReadonlyArray<DefinedAgent> = ReadonlyArray<DefinedAgent>> {
63
65
  /** The text adapter to use (created by a provider function like openaiText('gpt-5.5')) */
64
66
  adapter: TAdapter;
65
67
  /**
@@ -125,6 +127,8 @@ export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema ex
125
127
  runId?: TextOptions['runId'];
126
128
  /** Parent run ID for AG-UI protocol nested run correlation. */
127
129
  parentRunId?: TextOptions['parentRunId'];
130
+ /** Subagent run id when this chat runs as a child. See `defineAgent`. */
131
+ subagentRunId?: TextOptions['subagentRunId'];
128
132
  /** Application state mirrored in a STATE_SNAPSHOT before an interrupt terminal. */
129
133
  state?: TextOptions['state'];
130
134
  /**
@@ -132,6 +136,12 @@ export interface TextActivityOptions<TAdapter extends AnyTextAdapter, TSchema ex
132
136
  * before accepting new input on a thread with pending interrupts.
133
137
  */
134
138
  resume?: TextOptions['resume'];
139
+ /**
140
+ * Named child agents. When `router` is set, the library spawns that agent
141
+ * directly. When `router` is omitted, the main model gets one synthetic
142
+ * server tool per agent and picks the child.
143
+ */
144
+ subagents?: SubagentsBag<TAgents>;
135
145
  /**
136
146
  * Optional Standard Schema for structured output.
137
147
  * When provided, the activity will:
@@ -299,6 +309,6 @@ export type TextActivityResult<TSchema extends SchemaInput | undefined, TStream
299
309
  * // result is { summary: string, keyPoints: string[] }
300
310
  * ```
301
311
  */
302
- export declare function chat<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined = undefined, TStream extends boolean = boolean, const TTools extends TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'] = TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'], const TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = [], TContext = unknown, const TMiddleware extends Array<unknown> | undefined = undefined>(options: TextActivityOptionsWithContext<TAdapter, TSchema, TStream, TTools, TInterrupts, TContext, TMiddleware>): TextActivityResult<TSchema, TStream, TTools>;
312
+ export declare function chat<TAdapter extends AnyTextAdapter, TSchema extends SchemaInput | undefined = undefined, TStream extends boolean = boolean, const TTools extends TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'] = TextActivityOptions<TAdapter, TSchema, TStream, any>['tools'], const TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> = [], TContext = unknown, const TMiddleware extends Array<unknown> | undefined = undefined, const TAgents extends ReadonlyArray<DefinedAgent> = ReadonlyArray<DefinedAgent>>(options: TextActivityOptionsWithContext<TAdapter, TSchema, TStream, TTools, TInterrupts, TContext, TMiddleware, TAgents>): TextActivityResult<TSchema, TStream, TTools>;
303
313
  export type { TextAdapter, TextAdapterConfig, StructuredOutputOptions, StructuredOutputResult, } from './adapter.js';
304
314
  export { BaseTextAdapter } from './adapter.js';
@@ -18,6 +18,7 @@ import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, readInt
18
18
  import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, rehydrateInterruptRequest } from "../../interrupt-definition.js";
19
19
  import { readGenericInterruptContinuation } from "../../generic-interrupt-continuation.js";
20
20
  import { normalizeToolResult } from "../../utilities/tool-result.js";
21
+ import { subagentHostMessageId } from "../../utilities/subagent-wire.js";
21
22
  import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
22
23
  import { appendUiResourceToModelMessages, convertMessagesToModelMessages, generateMessageId, modelMessagesToUIMessages, safeJsonStringify, uiResourcePartFromCustomValue } from "./messages.js";
23
24
  import { uiMessagesToWire } from "../../utilities/ag-ui-wire.js";
@@ -25,6 +26,8 @@ import { restorePublicUsage } from "../../utilities/restore-inbound-chunk.js";
25
26
  import { LazyToolManager } from "./tools/lazy-tool-manager.js";
26
27
  import { assertUniqueToolNames } from "./tools/unique-tool-names.js";
27
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";
28
31
  import { maxIterations } from "./agent-loop-strategies.js";
29
32
  import { MiddlewareRunner } from "./middleware/compose.js";
30
33
  import { provideSandboxRuntime } from "./middleware/sandbox-runtime.js";
@@ -45,6 +48,14 @@ import { undoNullWidening } from "@tanstack/ai-utils";
45
48
  /** The adapter kind this activity handles */
46
49
  var kind = "text";
47
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
+ }
48
59
  function isInterruptSubmissionError(value) {
49
60
  if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
50
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;
@@ -155,6 +166,7 @@ var TextEngine = class {
155
166
  threadId;
156
167
  runIdOverride;
157
168
  parentRunIdOverride;
169
+ subagentRunIdOverride;
158
170
  middlewareRunner;
159
171
  middlewareCtx;
160
172
  sandboxFileQueue = [];
@@ -210,6 +222,7 @@ var TextEngine = class {
210
222
  this.threadId = config.params.threadId || config.params.conversationId || this.createId("thread");
211
223
  this.runIdOverride = config.params.runId;
212
224
  this.parentRunIdOverride = config.params.parentRunId;
225
+ this.subagentRunIdOverride = config.params.subagentRunId;
213
226
  const allMiddleware = [devtoolsMiddleware(), ...config.middleware || []];
214
227
  this.middlewareRunner = new MiddlewareRunner(allMiddleware, logger);
215
228
  this.middlewareAbortController = new AbortController();
@@ -219,6 +232,7 @@ var TextEngine = class {
219
232
  streamId: this.streamId,
220
233
  runId: this.runIdOverride ?? this.requestId,
221
234
  parentRunId: this.parentRunIdOverride,
235
+ subagentRunId: this.subagentRunIdOverride,
222
236
  threadId: this.threadId,
223
237
  conversationId: this.threadId,
224
238
  phase: "init",
@@ -517,6 +531,7 @@ var TextEngine = class {
517
531
  const { approvals } = this.collectClientState();
518
532
  const adapterApprovals = /* @__PURE__ */ new Map();
519
533
  for (const [approvalId, resolution] of approvals) adapterApprovals.set(approvalId, typeof resolution === "boolean" ? resolution : resolution.approved);
534
+ assertNoFileSources(this.adapter.name, this.messages);
520
535
  for await (const raw of this.adapter.chatStream({
521
536
  model: this.params.model,
522
537
  messages: this.providerMessages,
@@ -835,10 +850,10 @@ var TextEngine = class {
835
850
  needsApproval: executionResult.needsApproval,
836
851
  needsClientExecution: executionResult.needsClientExecution
837
852
  }));
838
- if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
853
+ if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
839
854
  this.discardDeferredToolCallRunFinishedChunks();
840
855
  if (allResults.length > 0) for (const chunk of this.buildToolResultChunks(allResults, finishEvent)) yield* this.pipeThroughMiddleware(chunk);
841
- const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
856
+ const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
842
857
  this.setToolPhase(emitted ? "wait" : "stop");
843
858
  return emitted ? "wait" : "stop";
844
859
  }
@@ -936,9 +951,9 @@ var TextEngine = class {
936
951
  this.setToolPhase("wait");
937
952
  return;
938
953
  }
939
- if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0) {
954
+ if (executionResult.needsApproval.length > 0 || executionResult.needsClientExecution.length > 0 || executionResult.subagentInterrupts.length > 0) {
940
955
  if (allResults.length > 0) for (const chunk of afterToolBoundaryChunks) yield* this.pipeThroughMiddleware(chunk);
941
- const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution);
956
+ const emitted = yield* this.emitActionableInterruptBoundary(finishEvent, executionResult.needsApproval, executionResult.needsClientExecution, [], executionResult.subagentInterrupts);
942
957
  this.setToolPhase(emitted ? "wait" : "stop");
943
958
  return;
944
959
  }
@@ -1110,7 +1125,7 @@ var TextEngine = class {
1110
1125
  genericInterruptId() {
1111
1126
  return this.createId("interrupt");
1112
1127
  }
1113
- buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = []) {
1128
+ buildActionableInterrupts(approvals, clientRequests, genericRequests = [], genericInterruptIds = [], childInterrupts = []) {
1114
1129
  const interrupts = [];
1115
1130
  for (const approval of approvals) {
1116
1131
  const tool = this.tools.find((candidate) => candidate.name === approval.toolName);
@@ -1191,6 +1206,7 @@ var TextEngine = class {
1191
1206
  }
1192
1207
  });
1193
1208
  }
1209
+ interrupts.push(...childInterrupts);
1194
1210
  const ids = /* @__PURE__ */ new Set();
1195
1211
  for (const interrupt of interrupts) {
1196
1212
  if (ids.has(interrupt.id)) throw new Error(`Duplicate interrupt id in final batch: ${interrupt.id}`);
@@ -1198,13 +1214,13 @@ var TextEngine = class {
1198
1214
  }
1199
1215
  return interrupts;
1200
1216
  }
1201
- buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds) {
1217
+ buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests = [], genericInterruptIds, childInterrupts) {
1202
1218
  return {
1203
1219
  ...finishEvent,
1204
1220
  timestamp: Date.now(),
1205
1221
  outcome: {
1206
1222
  type: "interrupt",
1207
- interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds)
1223
+ interrupts: this.buildActionableInterrupts(approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts)
1208
1224
  }
1209
1225
  };
1210
1226
  }
@@ -1298,10 +1314,10 @@ var TextEngine = class {
1298
1314
  };
1299
1315
  yield* this.pipeThroughMiddleware(this.buildInterruptRunErrorChunk(error));
1300
1316
  }
1301
- async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = []) {
1317
+ async *emitActionableInterruptBoundary(finishEvent, approvals, clientRequests, genericRequests = [], childInterrupts = []) {
1302
1318
  yield* this.emitSyntheticRunStarted(finishEvent);
1303
1319
  const genericInterruptIds = genericRequests.map(() => this.genericInterruptId());
1304
- const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds));
1320
+ const terminal = this.completeEphemeralInterruptBindings(this.buildInterruptFinishedChunk(finishEvent, approvals, clientRequests, genericRequests, genericInterruptIds, childInterrupts));
1305
1321
  let terminalOutputs;
1306
1322
  try {
1307
1323
  terminalOutputs = [...this.emitPublicChunks(await this.middlewareRunner.runOnChunk(this.middlewareCtx, terminal))];
@@ -1666,6 +1682,7 @@ var TextEngine = class {
1666
1682
  tools: baseConfig.tools
1667
1683
  }));
1668
1684
  this.applyMiddlewareConfig(postOnConfig);
1685
+ assertNoFileSources(this.adapter.name, this.messages);
1669
1686
  const structuredCallOptions = {
1670
1687
  chatOptions: {
1671
1688
  model: this.params.model,
@@ -1907,7 +1924,16 @@ var TextEngine = class {
1907
1924
  modelOptions: this.params.modelOptions
1908
1925
  };
1909
1926
  }
1910
- 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
+ };
1911
1937
  if ((config.resume?.length ?? 0) === 0) return;
1912
1938
  const interruptedRunId = this.parentRunIdOverride;
1913
1939
  if (!interruptedRunId) throw new InterruptResumeValidationError([{
@@ -2026,7 +2052,7 @@ var TextEngine = class {
2026
2052
  const pending = [];
2027
2053
  const ids = /* @__PURE__ */ new Set();
2028
2054
  const batchIndexes = /* @__PURE__ */ new Set();
2029
- for (const resumeItem of this.params.resume ?? []) {
2055
+ for (const resumeItem of this.ownResume(this.params.resume) ?? []) {
2030
2056
  const parsed = readGenericInterruptContinuation(resumeItem.metadata);
2031
2057
  if (parsed.status === "absent") continue;
2032
2058
  if (parsed.status === "invalid") return fail(parsed.message);
@@ -2300,6 +2326,7 @@ function chat(options) {
2300
2326
  validateCapabilities(readRuntimeMiddleware(options.middleware) ?? [], options.adapter);
2301
2327
  if (options.tools) assertUniqueToolNames(options.tools);
2302
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.");
2303
2330
  if (outputSchema && stream === true) return runStreamingStructuredOutput(toRuntimeTextActivityOptions(options, {
2304
2331
  outputSchema,
2305
2332
  stream: true
@@ -2368,8 +2395,8 @@ function runStreamingText(options) {
2368
2395
  publishDeliverySeams(stream, engineRef);
2369
2396
  return stream;
2370
2397
  }
2371
- async function* streamTextChunks(options, engineRef) {
2372
- 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;
2373
2400
  const model = adapter.model;
2374
2401
  const logger = resolveDebugOption(debug);
2375
2402
  const mcpManager = MCPManager.from(mcp);
@@ -2392,6 +2419,303 @@ async function* streamTextChunks(options, engineRef) {
2392
2419
  await mcpManager.dispose();
2393
2420
  }
2394
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
+ }
2395
2719
  /**
2396
2720
  * Run non-streaming text - collects all content and returns as a string.
2397
2721
  * Runs the full agentic loop (if tools are provided) but returns collected text.