@tanstack/ai 0.58.0 → 0.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/esm/activities/chat/agents/define-agent.d.ts +81 -0
- package/dist/esm/activities/chat/agents/define-agent.js +34 -0
- package/dist/esm/activities/chat/agents/define-agent.js.map +1 -0
- package/dist/esm/activities/chat/agents/route.d.ts +53 -0
- package/dist/esm/activities/chat/agents/route.js +59 -0
- package/dist/esm/activities/chat/agents/route.js.map +1 -0
- package/dist/esm/activities/chat/agents/spawn.d.ts +124 -0
- package/dist/esm/activities/chat/agents/spawn.js +490 -0
- package/dist/esm/activities/chat/agents/spawn.js.map +1 -0
- package/dist/esm/activities/chat/agents/turn.d.ts +36 -0
- package/dist/esm/activities/chat/agents/turn.js +78 -0
- package/dist/esm/activities/chat/agents/turn.js.map +1 -0
- package/dist/esm/activities/chat/index.d.ts +13 -3
- package/dist/esm/activities/chat/index.js +337 -13
- package/dist/esm/activities/chat/index.js.map +1 -1
- package/dist/esm/activities/chat/messages.d.ts +7 -1
- package/dist/esm/activities/chat/messages.js +94 -18
- package/dist/esm/activities/chat/messages.js.map +1 -1
- package/dist/esm/activities/chat/middleware/run-store.d.ts +43 -7
- package/dist/esm/activities/chat/middleware/run-store.js +8 -1
- package/dist/esm/activities/chat/middleware/run-store.js.map +1 -1
- package/dist/esm/activities/chat/middleware/types.d.ts +47 -1
- package/dist/esm/activities/chat/middleware/types.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +46 -0
- package/dist/esm/activities/chat/stream/processor.js +280 -7
- package/dist/esm/activities/chat/stream/processor.js.map +1 -1
- package/dist/esm/activities/chat/tools/tool-calls.d.ts +17 -3
- package/dist/esm/activities/chat/tools/tool-calls.js +54 -5
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/generateAudio/index.js +1 -1
- package/dist/esm/activities/generateImage/index.js +1 -1
- package/dist/esm/activities/generateLiveVideo/index.js +1 -1
- package/dist/esm/activities/generateSpeech/index.js +1 -1
- package/dist/esm/activities/generateTranscription/index.js +1 -1
- package/dist/esm/activities/generateVoice/index.js +1 -1
- package/dist/esm/activities/generateWorld/index.js +1 -1
- package/dist/esm/activities/index.d.ts +3 -0
- package/dist/esm/activities/index.js +5 -3
- package/dist/esm/activities/summarize/index.js +1 -1
- package/dist/esm/client.d.ts +5 -36
- package/dist/esm/client.js +4 -37
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +5 -0
- package/dist/esm/index.js +6 -3
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/types.d.ts +110 -88
- package/dist/esm/utilities/adapter-yield-chunk.d.ts +5 -1
- package/dist/esm/utilities/ag-ui-usage.d.ts +9 -9
- package/dist/esm/utilities/ag-ui-usage.js +66 -3
- package/dist/esm/utilities/ag-ui-usage.js.map +1 -1
- package/dist/esm/utilities/ag-ui-wire.js +56 -0
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/normalize-stream-chunk.js +7 -2
- package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -1
- package/dist/esm/utilities/spec-event-keys.js +13 -8
- package/dist/esm/utilities/spec-event-keys.js.map +1 -1
- package/dist/esm/utilities/subagent-wire.d.ts +36 -0
- package/dist/esm/utilities/subagent-wire.js +131 -0
- package/dist/esm/utilities/subagent-wire.js.map +1 -0
- package/package.json +2 -2
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
- package/skills/ai-core/media-generation/SKILL.md +2 -2
- package/skills/ai-core/middleware/SKILL.md +7 -4
- package/src/activities/chat/agents/define-agent.ts +121 -0
- package/src/activities/chat/agents/route.ts +115 -0
- package/src/activities/chat/agents/spawn.ts +806 -0
- package/src/activities/chat/agents/turn.ts +151 -0
- package/src/activities/chat/index.ts +529 -11
- package/src/activities/chat/messages.ts +125 -15
- package/src/activities/chat/middleware/run-store.ts +56 -7
- package/src/activities/chat/middleware/types.ts +47 -0
- package/src/activities/chat/stream/processor.ts +428 -8
- package/src/activities/chat/tools/tool-calls.ts +77 -11
- package/src/activities/index.ts +15 -0
- package/src/client.ts +22 -35
- package/src/index.ts +23 -0
- package/src/middlewares/content-guard.ts +7 -5
- package/src/types.ts +154 -94
- package/src/utilities/adapter-yield-chunk.ts +10 -2
- package/src/utilities/ag-ui-usage.test.ts +38 -0
- package/src/utilities/ag-ui-usage.ts +98 -11
- package/src/utilities/ag-ui-wire.ts +74 -0
- package/src/utilities/normalize-stream-chunk.ts +10 -2
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
|
@@ -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
|
-
|
|
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*
|
|
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.
|