@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,131 @@
|
|
|
1
|
+
//#region src/utilities/subagent-wire.ts
|
|
2
|
+
function isRecord(value) {
|
|
3
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4
|
+
}
|
|
5
|
+
function wireSubagentRunId(message) {
|
|
6
|
+
if (!isRecord(message) || "parts" in message) return void 0;
|
|
7
|
+
const id = message.subagentRunId;
|
|
8
|
+
return typeof id === "string" && id !== "" ? id : void 0;
|
|
9
|
+
}
|
|
10
|
+
function wireSubagentInfo(message) {
|
|
11
|
+
if (!isRecord(message) || !isRecord(message.metadata)) return void 0;
|
|
12
|
+
const tanstack = message.metadata.tanstack;
|
|
13
|
+
if (!isRecord(tanstack) || !isRecord(tanstack.subagent)) return void 0;
|
|
14
|
+
const { name, status, error, interruptIds } = tanstack.subagent;
|
|
15
|
+
if (typeof name !== "string" || !isStatus(status)) return void 0;
|
|
16
|
+
const { description, parentSubagentRunId, parentToolCallId, metadata, placeholder } = tanstack.subagent;
|
|
17
|
+
return {
|
|
18
|
+
name,
|
|
19
|
+
status,
|
|
20
|
+
...typeof description === "string" && { description },
|
|
21
|
+
...isRecord(error) && typeof error.message === "string" && { error: {
|
|
22
|
+
message: error.message,
|
|
23
|
+
...typeof error.code === "string" && { code: error.code }
|
|
24
|
+
} },
|
|
25
|
+
...Array.isArray(interruptIds) && { interruptIds: interruptIds.filter((id) => typeof id === "string") },
|
|
26
|
+
...typeof parentSubagentRunId === "string" && { parentSubagentRunId },
|
|
27
|
+
...typeof parentToolCallId === "string" && { parentToolCallId },
|
|
28
|
+
...isRecord(metadata) && { metadata },
|
|
29
|
+
...placeholder === true && { placeholder: true }
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
function isStatus(value) {
|
|
33
|
+
return value === "running" || value === "finished" || value === "error" || value === "suspended";
|
|
34
|
+
}
|
|
35
|
+
function untag(message) {
|
|
36
|
+
if (!isRecord(message)) return message;
|
|
37
|
+
const { subagentRunId: _id, ...rest } = message;
|
|
38
|
+
const metadata = isRecord(rest.metadata) ? rest.metadata : void 0;
|
|
39
|
+
const tanstack = isRecord(metadata?.tanstack) ? metadata.tanstack : void 0;
|
|
40
|
+
if (!metadata || !tanstack || !("subagent" in tanstack)) return rest;
|
|
41
|
+
const { subagent: _info, ...tanstackRest } = tanstack;
|
|
42
|
+
return {
|
|
43
|
+
...rest,
|
|
44
|
+
metadata: {
|
|
45
|
+
...metadata,
|
|
46
|
+
tanstack: tanstackRest
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Split wire messages into the parent's own messages and one group per direct
|
|
52
|
+
* child. A nested child's messages stay inside its parent's group.
|
|
53
|
+
*/
|
|
54
|
+
function splitSubagentWire(messages) {
|
|
55
|
+
const parentOf = /* @__PURE__ */ new Map();
|
|
56
|
+
for (const message of messages) {
|
|
57
|
+
const id = wireSubagentRunId(message);
|
|
58
|
+
if (id !== void 0 && !parentOf.has(id)) parentOf.set(id, wireSubagentInfo(message)?.parentSubagentRunId);
|
|
59
|
+
}
|
|
60
|
+
const rootOf = (id) => {
|
|
61
|
+
let current = id;
|
|
62
|
+
for (let depth = 0; depth < 64; depth++) {
|
|
63
|
+
const parent = parentOf.get(current);
|
|
64
|
+
if (parent === void 0 || !parentOf.has(parent)) return current;
|
|
65
|
+
current = parent;
|
|
66
|
+
}
|
|
67
|
+
return current;
|
|
68
|
+
};
|
|
69
|
+
const top = [];
|
|
70
|
+
const groups = /* @__PURE__ */ new Map();
|
|
71
|
+
for (const message of messages) {
|
|
72
|
+
const id = wireSubagentRunId(message);
|
|
73
|
+
if (id === void 0) {
|
|
74
|
+
top.push(message);
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
const root = rootOf(id);
|
|
78
|
+
let group = groups.get(root);
|
|
79
|
+
if (!group) {
|
|
80
|
+
group = {
|
|
81
|
+
id: root,
|
|
82
|
+
info: {
|
|
83
|
+
name: "subagent",
|
|
84
|
+
status: "finished"
|
|
85
|
+
},
|
|
86
|
+
messages: [],
|
|
87
|
+
hostIndex: top.length - 1
|
|
88
|
+
};
|
|
89
|
+
groups.set(root, group);
|
|
90
|
+
}
|
|
91
|
+
if (id !== root) {
|
|
92
|
+
group.messages.push(message);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
const info = wireSubagentInfo(message);
|
|
96
|
+
if (info) group.info = info;
|
|
97
|
+
if (!info?.placeholder) group.messages.push(untag(message));
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
top,
|
|
101
|
+
groups: [...groups.values()]
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
/** Text a child wrote, for the parent model and for a later child. */
|
|
105
|
+
function subagentWireText(messages) {
|
|
106
|
+
const blocks = [];
|
|
107
|
+
for (const message of messages) {
|
|
108
|
+
if (!isRecord(message) || message.role !== "assistant") continue;
|
|
109
|
+
if (wireSubagentRunId(message) !== void 0) continue;
|
|
110
|
+
const content = message.content;
|
|
111
|
+
if (typeof content === "string" && content.trim() !== "") blocks.push(content.trim());
|
|
112
|
+
}
|
|
113
|
+
const nested = splitSubagentWire(messages).groups;
|
|
114
|
+
for (const group of nested) {
|
|
115
|
+
const text = subagentWireText(group.messages);
|
|
116
|
+
if (text !== "") blocks.push(`${group.info.name}:\n${text}`);
|
|
117
|
+
}
|
|
118
|
+
return blocks.join("\n\n");
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* The id of the parent assistant message that hosts a routed turn's cards.
|
|
122
|
+
* The persistence recorder writes that message, and a handoff run passes the
|
|
123
|
+
* same id so the stored thread keeps one copy.
|
|
124
|
+
*/
|
|
125
|
+
function subagentHostMessageId(runId) {
|
|
126
|
+
return `assistant:${runId}`;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
export { splitSubagentWire, subagentHostMessageId, subagentWireText, wireSubagentInfo, wireSubagentRunId };
|
|
130
|
+
|
|
131
|
+
//# sourceMappingURL=subagent-wire.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"subagent-wire.js","names":[],"sources":["../../../src/utilities/subagent-wire.ts"],"sourcesContent":["import type { SubagentInfo as AGUISubagentInfo } from '@ag-ui/core'\nimport type { SubagentHandleData } from '../types'\n\n/**\n * Card data that travels on each child wire message, in\n * `metadata.tanstack.subagent`. The messages carry the AG-UI `subagentRunId`.\n */\nexport interface SubagentWireInfo\n extends\n AGUISubagentInfo,\n Pick<\n SubagentHandleData,\n | 'status'\n | 'error'\n | 'interruptIds'\n | 'parentSubagentRunId'\n | 'parentToolCallId'\n | 'metadata'\n > {\n /** The child has no messages yet. This wire message only holds the card. */\n placeholder?: true\n}\n\nexport interface SubagentWireGroup<T> {\n id: string\n info: SubagentWireInfo\n /** The child's messages without its own tag. Nested children keep theirs. */\n messages: Array<T>\n /** Index in `top` of the last message before this child, or -1. */\n hostIndex: number\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nexport function wireSubagentRunId(message: unknown): string | undefined {\n if (!isRecord(message) || 'parts' in message) return undefined\n const id = message.subagentRunId\n return typeof id === 'string' && id !== '' ? id : undefined\n}\n\nexport function wireSubagentInfo(\n message: unknown,\n): SubagentWireInfo | undefined {\n if (!isRecord(message) || !isRecord(message.metadata)) return undefined\n const tanstack = message.metadata.tanstack\n if (!isRecord(tanstack) || !isRecord(tanstack.subagent)) return undefined\n // Wire data comes from the client. Keep only well-formed fields.\n const { name, status, error, interruptIds } = tanstack.subagent\n if (typeof name !== 'string' || !isStatus(status)) return undefined\n const {\n description,\n parentSubagentRunId,\n parentToolCallId,\n metadata,\n placeholder,\n } = tanstack.subagent\n return {\n name,\n status,\n ...(typeof description === 'string' && { description }),\n ...(isRecord(error) &&\n typeof error.message === 'string' && {\n error: {\n message: error.message,\n ...(typeof error.code === 'string' && { code: error.code }),\n },\n }),\n ...(Array.isArray(interruptIds) && {\n interruptIds: interruptIds.filter(\n (id): id is string => typeof id === 'string',\n ),\n }),\n ...(typeof parentSubagentRunId === 'string' && { parentSubagentRunId }),\n ...(typeof parentToolCallId === 'string' && { parentToolCallId }),\n ...(isRecord(metadata) && { metadata }),\n ...(placeholder === true && { placeholder: true as const }),\n }\n}\n\nfunction isStatus(value: unknown): value is SubagentWireInfo['status'] {\n return (\n value === 'running' ||\n value === 'finished' ||\n value === 'error' ||\n value === 'suspended'\n )\n}\n\nfunction untag<T>(message: T): T {\n if (!isRecord(message)) return message\n const { subagentRunId: _id, ...rest } = message\n void _id\n const metadata = isRecord(rest.metadata) ? rest.metadata : undefined\n const tanstack = isRecord(metadata?.tanstack) ? metadata.tanstack : undefined\n if (!metadata || !tanstack || !('subagent' in tanstack)) return rest as T\n const { subagent: _info, ...tanstackRest } = tanstack\n void _info\n return { ...rest, metadata: { ...metadata, tanstack: tanstackRest } } as T\n}\n\n/**\n * Split wire messages into the parent's own messages and one group per direct\n * child. A nested child's messages stay inside its parent's group.\n */\nexport function splitSubagentWire<T>(messages: ReadonlyArray<T>): {\n top: Array<T>\n groups: Array<SubagentWireGroup<T>>\n} {\n const parentOf = new Map<string, string | undefined>()\n for (const message of messages) {\n const id = wireSubagentRunId(message)\n if (id !== undefined && !parentOf.has(id)) {\n parentOf.set(id, wireSubagentInfo(message)?.parentSubagentRunId)\n }\n }\n const rootOf = (id: string) => {\n let current = id\n for (let depth = 0; depth < 64; depth++) {\n const parent = parentOf.get(current)\n if (parent === undefined || !parentOf.has(parent)) return current\n current = parent\n }\n return current\n }\n\n const top: Array<T> = []\n const groups = new Map<string, SubagentWireGroup<T>>()\n for (const message of messages) {\n const id = wireSubagentRunId(message)\n if (id === undefined) {\n top.push(message)\n continue\n }\n const root = rootOf(id)\n let group = groups.get(root)\n if (!group) {\n group = {\n id: root,\n info: { name: 'subagent', status: 'finished' },\n messages: [],\n hostIndex: top.length - 1,\n }\n groups.set(root, group)\n }\n if (id !== root) {\n group.messages.push(message)\n continue\n }\n const info = wireSubagentInfo(message)\n if (info) group.info = info\n if (!info?.placeholder) group.messages.push(untag(message))\n }\n return { top, groups: [...groups.values()] }\n}\n\n/** Text a child wrote, for the parent model and for a later child. */\nexport function subagentWireText(messages: ReadonlyArray<unknown>): string {\n const blocks: Array<string> = []\n for (const message of messages) {\n if (!isRecord(message) || message.role !== 'assistant') continue\n if (wireSubagentRunId(message) !== undefined) continue\n const content = message.content\n if (typeof content === 'string' && content.trim() !== '') {\n blocks.push(content.trim())\n }\n }\n const nested = splitSubagentWire(messages).groups\n for (const group of nested) {\n const text = subagentWireText(group.messages)\n if (text !== '') blocks.push(`${group.info.name}:\\n${text}`)\n }\n return blocks.join('\\n\\n')\n}\n\n/**\n * The id of the parent assistant message that hosts a routed turn's cards.\n * The persistence recorder writes that message, and a handoff run passes the\n * same id so the stored thread keeps one copy.\n */\nexport function subagentHostMessageId(runId: string) {\n return `assistant:${runId}`\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.59.0",
|
|
4
4
|
"description": "Type-safe TypeScript AI SDK for streaming chat, tool calling, agents, structured outputs, and multimodal generation.",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"tanstack-intent"
|
|
86
86
|
],
|
|
87
87
|
"dependencies": {
|
|
88
|
-
"@ag-ui/core": "
|
|
88
|
+
"@ag-ui/core": "1.0.0",
|
|
89
89
|
"@standard-schema/spec": "^1.1.0",
|
|
90
90
|
"partial-json": "^0.1.7",
|
|
91
91
|
"@tanstack/ai-event-client": "^0.12.1",
|
|
@@ -31,7 +31,7 @@ import { grokImage } from '@tanstack/ai-grok'
|
|
|
31
31
|
| `grok-build-0.1` | 256K | Code-specialized; `reasoning` option is not accepted |
|
|
32
32
|
|
|
33
33
|
`GROK_CHAT_MODELS` is exactly these four ids. Image models
|
|
34
|
-
(`GROK_IMAGE_MODELS`): `grok-
|
|
34
|
+
(`GROK_IMAGE_MODELS`): `grok-imagine-image`,
|
|
35
35
|
`grok-imagine-image-2.0`, `grok-imagine-image-quality`.
|
|
36
36
|
|
|
37
37
|
## Provider-Specific modelOptions
|
|
@@ -312,7 +312,7 @@ with `allowUrlFetch: true` on the adapter config
|
|
|
312
312
|
| OpenAI | gpt-image-2 / gpt-image-1 / -mini → `images.edit()` (up to 16). dall-e-2 → edit (1). dall-e-3 throws. | Sora-2 / -pro → `input_reference` (single). Throws if >1. |
|
|
313
313
|
| Gemini | Native (gemini-\*-flash-image, "nano-banana") → multimodal `contents`. Imagen throws. | Veo → first un-roled / `'start_frame'` image is the input image; `'end_frame'` → `lastFrame`; `'reference'` / `'character'` → `referenceImages`. Omni Flash sends image/video parts as interaction content blocks (no role routing). |
|
|
314
314
|
| fal | Per-endpoint field names from a generated map (`pnpm generate:fal-image-fields`). Defaults: 1 input → `image_url`; >1 → `image_urls`; roles → `mask_url` / `control_image_url` / `reference_image_urls`. | Per-endpoint map (e.g. Kling i2v start frame → `image_url`). Defaults: 1 input → `image_url`; `start_frame`/`end_frame` → `start_image_url`/`end_image_url`; `reference` → `reference_image_urls`. |
|
|
315
|
-
| Grok | grok-imagine models → `/v1/images/edits` JSON endpoint (≤3 sources, addressed by xAI in request order; prompt sent verbatim; mask/control throw).
|
|
315
|
+
| Grok | grok-imagine models → `/v1/images/edits` JSON endpoint (≤3 sources, addressed by xAI in request order; prompt sent verbatim; mask/control throw). | Un-roled / `'start_frame'` image → starting frame; `'reference'` / `'character'` → `reference_images` (1.5). On 1.5 a starting frame can be combined with reference inputs (it pins the first frame). A `video` part + `modelOptions.mode: 'edit' \| 'extend'` routes to `/videos/edits` / `/videos/extensions` on `grok-imagine-video` only. |
|
|
316
316
|
| OpenRouter | Prompt parts map 1:1 onto multimodal `text` / `image_url` content parts, preserving interleaved order. | Dedicated async API (`openRouterVideo`): `start_frame`/`end_frame` → `frame_images[]` (`first_frame`/`last_frame`); `reference`/`character` → `input_references[]`; an unroled image defaults to the start frame. Frame roles validated against the model's `supported_frame_images` metadata. |
|
|
317
317
|
| Anthropic | n/a (no image generation API). | n/a |
|
|
318
318
|
|
|
@@ -1174,7 +1174,7 @@ generateSpeech({
|
|
|
1174
1174
|
|
|
1175
1175
|
Not every model accepts image-conditioned prompts. The `prompt` type is
|
|
1176
1176
|
narrowed per model, so passing an image part to a text-only model
|
|
1177
|
-
(dall-e-3, Imagen
|
|
1177
|
+
(dall-e-3, Imagen) is a **compile-time error**; adapters
|
|
1178
1178
|
also throw a clear runtime error as a backstop, so users learn at call
|
|
1179
1179
|
time rather than getting silently wrong output.
|
|
1180
1180
|
|
|
@@ -55,7 +55,7 @@ export async function POST(request: Request) {
|
|
|
55
55
|
|
|
56
56
|
Every hook receives a `ChatMiddlewareContext` as its first argument, which provides
|
|
57
57
|
`requestId`, `streamId`, `phase`, `iteration`, `chunkIndex`, `model`, `provider`,
|
|
58
|
-
`signal`, `abort()`, `defer()`, and more.
|
|
58
|
+
`signal`, `abort()`, `defer()`, and more. `parentRunId` names the run this one continues. `subagentRunId` is set only inside a subagent and names its card.
|
|
59
59
|
|
|
60
60
|
| Hook | When | Second Argument |
|
|
61
61
|
| -------------------------- | -------------------------------------------------------------------------------------------------------- | --------------------------------------------------- |
|
|
@@ -609,9 +609,12 @@ driver, or nothing fences a dead host's writes.
|
|
|
609
609
|
a bare string: `message` is the provider's prose, `code` is the stable,
|
|
610
610
|
machine-branchable classification a consumer switches on. Only
|
|
611
611
|
`createOrResume`, `update`, `get`, and `findActiveRun` are required on a
|
|
612
|
-
`RunStore
|
|
613
|
-
leave
|
|
614
|
-
(`store.listReclaimable?.(opts)`).
|
|
612
|
+
`RunStore`. `listByThread`, `listByParentRun`, and `listReclaimable` are
|
|
613
|
+
optional, so a backend can leave any of them out and callers feature-detect
|
|
614
|
+
(`store.listReclaimable?.(opts)`). A subagent child run also stores
|
|
615
|
+
`parentRunId`, `subagentRunId`, and `name`. `createOrResume` writes them on
|
|
616
|
+
the first insert and leaves them unchanged on resume. `reconstructChat` calls
|
|
617
|
+
`listByParentRun` to put the child cards back. Shape your own store with
|
|
615
618
|
`defineRunStore` for autocomplete without a separate `: RunStore` annotation,
|
|
616
619
|
matching `defineLock`; `defineRunStore<const T extends RunStore>(store: T): T`
|
|
617
620
|
returns the argument's own type, so an optional method your store implements
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { SubagentInfo as AGUISubagentInfo } from '@ag-ui/core'
|
|
2
|
+
import type { InterruptDefinition } from '../../../interrupt-definition'
|
|
3
|
+
import type {
|
|
4
|
+
AnyTool,
|
|
5
|
+
ModelMessage,
|
|
6
|
+
RunAgentResumeItem,
|
|
7
|
+
SchemaInput,
|
|
8
|
+
StreamChunk,
|
|
9
|
+
UIMessage,
|
|
10
|
+
} from '../../../types'
|
|
11
|
+
import type { AnyClientTool } from '../tools/tool-definition'
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Context the library passes into {@link defineAgent} `run`.
|
|
15
|
+
*/
|
|
16
|
+
export interface SubagentRunContext {
|
|
17
|
+
messages: Array<UIMessage | ModelMessage>
|
|
18
|
+
abortSignal?: AbortSignal
|
|
19
|
+
threadId: string
|
|
20
|
+
/** Run id for the child `chat()`. */
|
|
21
|
+
runId: string
|
|
22
|
+
/**
|
|
23
|
+
* The run this child run continues. It is the parent chat run on the first
|
|
24
|
+
* run, and the interrupted parent run on a resume. Pass it to the child
|
|
25
|
+
* `chat()`.
|
|
26
|
+
*/
|
|
27
|
+
parentRunId: string
|
|
28
|
+
/** Answers to this child's interrupts. Pass it to the child `chat()`. */
|
|
29
|
+
resume?: Array<RunAgentResumeItem>
|
|
30
|
+
/**
|
|
31
|
+
* The child's AG-UI run id. Stays the same when an interrupted child
|
|
32
|
+
* continues. Pass it to the child `chat()` so its middleware sees
|
|
33
|
+
* `ctx.subagentRunId`.
|
|
34
|
+
*/
|
|
35
|
+
subagentRunId: string
|
|
36
|
+
parentSubagentRunId?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A tool a child agent can carry into client part types.
|
|
41
|
+
* Server tools and client tools both qualify.
|
|
42
|
+
*/
|
|
43
|
+
export type SubagentTool = AnyTool | AnyClientTool
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* A named child agent. `run` is a `chat()` call (or any stream of AG-UI chunks).
|
|
47
|
+
* `TTools` and `TSchema` stay on the object so `useChat({ subagents })` can
|
|
48
|
+
* type that child's parts.
|
|
49
|
+
*/
|
|
50
|
+
export interface DefinedAgent<
|
|
51
|
+
TName extends string = string,
|
|
52
|
+
TTools extends ReadonlyArray<SubagentTool> = ReadonlyArray<SubagentTool>,
|
|
53
|
+
TSchema extends SchemaInput | undefined = SchemaInput | undefined,
|
|
54
|
+
TInterrupts extends ReadonlyArray<InterruptDefinition<any, any, any, any>> =
|
|
55
|
+
ReadonlyArray<InterruptDefinition<any, any, any, any>>,
|
|
56
|
+
> extends AGUISubagentInfo {
|
|
57
|
+
name: TName
|
|
58
|
+
/** Required here: the router and the synthetic tool both read it. */
|
|
59
|
+
description: string
|
|
60
|
+
run: (
|
|
61
|
+
ctx: SubagentRunContext,
|
|
62
|
+
) => AsyncIterable<StreamChunk> | Promise<AsyncIterable<StreamChunk>>
|
|
63
|
+
tools?: TTools
|
|
64
|
+
interrupts?: TInterrupts
|
|
65
|
+
outputSchema?: TSchema
|
|
66
|
+
subagents?: unknown
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Choice options for a `decide()` router. `main` is required plus every agent name.
|
|
71
|
+
*/
|
|
72
|
+
export type SubagentChoiceOptions<TAgents extends ReadonlyArray<DefinedAgent>> =
|
|
73
|
+
{ main: string } & {
|
|
74
|
+
[K in TAgents[number]['name']]: string
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Define a named child agent. Pass the same object to `chat({ subagents })`.
|
|
79
|
+
* Pass the agents array to `useChat({ subagents })` when you render parts
|
|
80
|
+
* yourself. The hook uses it for types only. It does not call `run`.
|
|
81
|
+
*
|
|
82
|
+
* @example
|
|
83
|
+
* ```ts
|
|
84
|
+
* const researcher = defineAgent({
|
|
85
|
+
* name: 'researcher',
|
|
86
|
+
* description: 'Looks up facts',
|
|
87
|
+
* run: (ctx) =>
|
|
88
|
+
* chat({
|
|
89
|
+
* adapter: openaiText('gpt-5.6'),
|
|
90
|
+
* messages: ctx.messages,
|
|
91
|
+
* threadId: ctx.threadId,
|
|
92
|
+
* runId: ctx.runId,
|
|
93
|
+
* parentRunId: ctx.parentRunId,
|
|
94
|
+
* subagentRunId: ctx.subagentRunId,
|
|
95
|
+
* resume: ctx.resume,
|
|
96
|
+
* }),
|
|
97
|
+
* })
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export function defineAgent<
|
|
101
|
+
const TName extends string,
|
|
102
|
+
const TTools extends ReadonlyArray<SubagentTool> = readonly [],
|
|
103
|
+
TSchema extends SchemaInput | undefined = undefined,
|
|
104
|
+
const TInterrupts extends ReadonlyArray<
|
|
105
|
+
InterruptDefinition<any, any, any, any>
|
|
106
|
+
> = readonly [],
|
|
107
|
+
>(agent: DefinedAgent<TName, TTools, TSchema, TInterrupts>) {
|
|
108
|
+
if (agent.name.trim() === '') {
|
|
109
|
+
throw new Error('defineAgent requires a non-empty name')
|
|
110
|
+
}
|
|
111
|
+
// A router returns 'main' to keep the turn on the parent.
|
|
112
|
+
if (agent.name.trim() === 'main') {
|
|
113
|
+
throw new Error(
|
|
114
|
+
"defineAgent cannot use the name 'main'. A router uses it for the parent.",
|
|
115
|
+
)
|
|
116
|
+
}
|
|
117
|
+
if (agent.description.trim() === '') {
|
|
118
|
+
throw new Error('defineAgent requires a non-empty description')
|
|
119
|
+
}
|
|
120
|
+
return agent
|
|
121
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { boolean, choice } from '../../evaluate/index'
|
|
2
|
+
import type { DefinedAgent } from './define-agent'
|
|
3
|
+
import type { SubagentOrder, SubagentRouterPick } from './spawn'
|
|
4
|
+
|
|
5
|
+
const ORDER_KEY = 'order'
|
|
6
|
+
|
|
7
|
+
export interface SubagentRouteOptions<
|
|
8
|
+
TAgents extends ReadonlyArray<DefinedAgent>,
|
|
9
|
+
> {
|
|
10
|
+
/**
|
|
11
|
+
* Question text for every agent. The key is the agent name.
|
|
12
|
+
* Omit this and each question uses that agent's description.
|
|
13
|
+
*/
|
|
14
|
+
when?: { [K in TAgents[number]['name']]: string }
|
|
15
|
+
/**
|
|
16
|
+
* Agents that run after the other selected agents. The lead group starts
|
|
17
|
+
* together. The `then` agents then run one after another in list order, and
|
|
18
|
+
* each reads the text so far. Used only when the router picks at least one
|
|
19
|
+
* agent from each group. Otherwise `pick` returns `{ names, order }`.
|
|
20
|
+
*/
|
|
21
|
+
then?: ReadonlyArray<TAgents[number]['name']>
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
type RouteQuestions<TAgents extends ReadonlyArray<DefinedAgent>> = {
|
|
25
|
+
[K in TAgents[number]['name']]: ReturnType<typeof boolean>
|
|
26
|
+
} & {
|
|
27
|
+
order: ReturnType<
|
|
28
|
+
typeof choice<{
|
|
29
|
+
parallel: string
|
|
30
|
+
sequence: string
|
|
31
|
+
}>
|
|
32
|
+
>
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
type RouteAnswers<TAgents extends ReadonlyArray<DefinedAgent>> = {
|
|
36
|
+
[K in TAgents[number]['name']]: { value: boolean }
|
|
37
|
+
} & {
|
|
38
|
+
order: { value: SubagentOrder }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Build `decide()` questions for a subagent router.
|
|
43
|
+
*
|
|
44
|
+
* One yes/no question per agent, plus an `order` choice.
|
|
45
|
+
* `pick` returns `main`, one name, `{ names, order }`, or `{ steps }`.
|
|
46
|
+
* Names follow the `agents` array order.
|
|
47
|
+
* `{ names, order }` overrides `subagents.order` for that turn.
|
|
48
|
+
* `then`: agents that run after the other selected agents. The lead group
|
|
49
|
+
* starts together. The `then` agents then run one after another in list
|
|
50
|
+
* order, and each reads the text so far. Used only when the router picks at
|
|
51
|
+
* least one agent from each group. Otherwise `pick` returns `{ names, order }`.
|
|
52
|
+
*/
|
|
53
|
+
export function subagentRoute<
|
|
54
|
+
const TAgents extends ReadonlyArray<DefinedAgent>,
|
|
55
|
+
>(agents: TAgents, options?: SubagentRouteOptions<TAgents>) {
|
|
56
|
+
const namesInList = new Set(agents.map((agent) => agent.name))
|
|
57
|
+
for (const agent of agents) {
|
|
58
|
+
if (agent.name === ORDER_KEY) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
'subagentRoute cannot use an agent named "order". Rename that agent.',
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
for (const name of options?.then ?? []) {
|
|
65
|
+
if (!namesInList.has(name)) {
|
|
66
|
+
throw new Error(`subagentRoute then includes unknown agent "${name}".`)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const questions: Record<string, unknown> = {
|
|
71
|
+
order: choice({
|
|
72
|
+
instructions: 'When more than one agent runs, how must they run?',
|
|
73
|
+
options: {
|
|
74
|
+
parallel:
|
|
75
|
+
'Start them together. Use this when no agent must read text from another agent. Working on the same topic is not a reason to wait.',
|
|
76
|
+
sequence:
|
|
77
|
+
'Run them in agent-list order. Use this only when a later agent must read the earlier agent text, such as research notes and then a draft article.',
|
|
78
|
+
},
|
|
79
|
+
}),
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
for (const agent of agents) {
|
|
83
|
+
const when = options?.when?.[agent.name as TAgents[number]['name']]
|
|
84
|
+
questions[agent.name] = boolean({
|
|
85
|
+
instructions: when ?? agent.description,
|
|
86
|
+
})
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function pick(result: RouteAnswers<TAgents>): SubagentRouterPick {
|
|
90
|
+
const names = agents
|
|
91
|
+
.map((agent) => agent.name)
|
|
92
|
+
.filter((name) => result[name as TAgents[number]['name']].value)
|
|
93
|
+
if (names.length === 0) return 'main'
|
|
94
|
+
const only = names.length === 1 ? names[0] : undefined
|
|
95
|
+
if (only !== undefined) return only
|
|
96
|
+
const later = new Set(options?.then ?? [])
|
|
97
|
+
const lead = names.filter((name) => !later.has(name))
|
|
98
|
+
const tail = names.filter((name) => later.has(name))
|
|
99
|
+
if (lead.length === 0 || tail.length === 0) {
|
|
100
|
+
return { names, order: result.order.value }
|
|
101
|
+
}
|
|
102
|
+
return {
|
|
103
|
+
steps: [
|
|
104
|
+
lead.length > 1
|
|
105
|
+
? { names: lead, order: 'parallel' as const }
|
|
106
|
+
: { names: lead },
|
|
107
|
+
tail.length > 1
|
|
108
|
+
? { names: tail, order: 'sequence' as const }
|
|
109
|
+
: { names: tail },
|
|
110
|
+
],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return { questions: questions as RouteQuestions<TAgents>, pick }
|
|
115
|
+
}
|