@tanstack/ai 0.58.0 → 0.61.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/adapter.d.ts +9 -0
- package/dist/esm/activities/chat/adapter.js +1 -0
- package/dist/esm/activities/chat/adapter.js.map +1 -1
- 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 +462 -26
- 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 +99 -19
- 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/message-updaters.js +9 -2
- package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
- package/dist/esm/activities/chat/stream/processor.d.ts +46 -1
- package/dist/esm/activities/chat/stream/processor.js +294 -18
- 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 +56 -5
- package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
- package/dist/esm/activities/embed/adapter.d.ts +7 -0
- package/dist/esm/activities/embed/adapter.js +1 -0
- package/dist/esm/activities/embed/adapter.js.map +1 -1
- package/dist/esm/activities/embed/index.js +2 -0
- package/dist/esm/activities/embed/index.js.map +1 -1
- package/dist/esm/activities/files/adapter.d.ts +97 -0
- package/dist/esm/activities/files/adapter.js +45 -0
- package/dist/esm/activities/files/adapter.js.map +1 -0
- package/dist/esm/activities/files/index.d.ts +66 -0
- package/dist/esm/activities/files/index.js +78 -0
- package/dist/esm/activities/files/index.js.map +1 -0
- package/dist/esm/activities/generateAudio/index.js +1 -1
- package/dist/esm/activities/generateImage/adapter.d.ts +8 -0
- package/dist/esm/activities/generateImage/adapter.js +1 -0
- package/dist/esm/activities/generateImage/adapter.js.map +1 -1
- package/dist/esm/activities/generateImage/index.js +3 -1
- package/dist/esm/activities/generateImage/index.js.map +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/generateVideo/adapter.d.ts +8 -0
- package/dist/esm/activities/generateVideo/adapter.js +1 -0
- package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
- package/dist/esm/activities/generateVideo/index.js +3 -0
- package/dist/esm/activities/generateVideo/index.js.map +1 -1
- package/dist/esm/activities/generateVoice/index.js +1 -1
- package/dist/esm/activities/generateWorld/adapter.d.ts +4 -2
- package/dist/esm/activities/generateWorld/adapter.js.map +1 -1
- package/dist/esm/activities/generateWorld/index.d.ts +4 -3
- package/dist/esm/activities/generateWorld/index.js +6 -5
- package/dist/esm/activities/generateWorld/index.js.map +1 -1
- package/dist/esm/activities/index.d.ts +9 -3
- package/dist/esm/activities/index.js +17 -13
- package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -0
- package/dist/esm/activities/summarize/chat-stream-summarize.js +8 -8
- package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
- package/dist/esm/activities/summarize/index.js +1 -1
- package/dist/esm/client.d.ts +7 -36
- package/dist/esm/client.js +5 -37
- package/dist/esm/client.js.map +1 -1
- package/dist/esm/index.d.ts +8 -2
- package/dist/esm/index.js +9 -4
- package/dist/esm/middlewares/content-guard.js.map +1 -1
- package/dist/esm/types.d.ts +179 -98
- 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 +90 -13
- package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
- package/dist/esm/utilities/content-source.d.ts +60 -0
- package/dist/esm/utilities/content-source.js +85 -0
- package/dist/esm/utilities/content-source.js.map +1 -0
- 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/provider-executed.d.ts +7 -0
- package/dist/esm/utilities/provider-executed.js +10 -1
- package/dist/esm/utilities/provider-executed.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/dist/esm/utilities/tool-result.d.ts +12 -2
- package/dist/esm/utilities/tool-result.js +23 -3
- package/dist/esm/utilities/tool-result.js.map +1 -1
- package/package.json +3 -3
- package/skills/ai-core/adapter-configuration/SKILL.md +62 -0
- package/skills/ai-core/adapter-configuration/references/grok-adapter.md +1 -1
- package/skills/ai-core/chat-experience/SKILL.md +14 -0
- package/skills/ai-core/media-generation/SKILL.md +10 -2
- package/skills/ai-core/middleware/SKILL.md +7 -4
- package/src/activities/chat/adapter.ts +10 -0
- 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 +734 -30
- package/src/activities/chat/messages.ts +137 -16
- package/src/activities/chat/middleware/run-store.ts +56 -7
- package/src/activities/chat/middleware/types.ts +47 -0
- package/src/activities/chat/stream/message-updaters.ts +24 -2
- package/src/activities/chat/stream/processor.ts +452 -30
- package/src/activities/chat/tools/tool-calls.ts +83 -11
- package/src/activities/embed/adapter.ts +7 -0
- package/src/activities/embed/index.ts +5 -0
- package/src/activities/files/adapter.ts +120 -0
- package/src/activities/files/index.ts +113 -0
- package/src/activities/generateImage/adapter.ts +8 -0
- package/src/activities/generateImage/index.ts +4 -0
- package/src/activities/generateVideo/adapter.ts +8 -0
- package/src/activities/generateVideo/index.ts +7 -0
- package/src/activities/generateWorld/adapter.ts +4 -2
- package/src/activities/generateWorld/index.ts +7 -6
- package/src/activities/index.ts +40 -1
- package/src/activities/summarize/chat-stream-summarize.ts +22 -12
- package/src/client.ts +29 -35
- package/src/index.ts +39 -0
- package/src/middlewares/content-guard.ts +7 -5
- package/src/types.ts +226 -103
- 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 +134 -16
- package/src/utilities/content-source.ts +138 -0
- package/src/utilities/normalize-stream-chunk.ts +10 -2
- package/src/utilities/provider-executed.ts +13 -0
- package/src/utilities/spec-event-keys.ts +34 -7
- package/src/utilities/subagent-wire.ts +184 -0
- package/src/utilities/tool-result.ts +38 -2
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { SubagentInfo as AGUISubagentInfo } from '@ag-ui/core';
|
|
2
|
+
import { SubagentHandleData } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* Card data that travels on each child wire message, in
|
|
5
|
+
* `metadata.tanstack.subagent`. The messages carry the AG-UI `subagentRunId`.
|
|
6
|
+
*/
|
|
7
|
+
export interface SubagentWireInfo extends AGUISubagentInfo, Pick<SubagentHandleData, 'status' | 'error' | 'interruptIds' | 'parentSubagentRunId' | 'parentToolCallId' | 'metadata'> {
|
|
8
|
+
/** The child has no messages yet. This wire message only holds the card. */
|
|
9
|
+
placeholder?: true;
|
|
10
|
+
}
|
|
11
|
+
export interface SubagentWireGroup<T> {
|
|
12
|
+
id: string;
|
|
13
|
+
info: SubagentWireInfo;
|
|
14
|
+
/** The child's messages without its own tag. Nested children keep theirs. */
|
|
15
|
+
messages: Array<T>;
|
|
16
|
+
/** Index in `top` of the last message before this child, or -1. */
|
|
17
|
+
hostIndex: number;
|
|
18
|
+
}
|
|
19
|
+
export declare function wireSubagentRunId(message: unknown): string | undefined;
|
|
20
|
+
export declare function wireSubagentInfo(message: unknown): SubagentWireInfo | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Split wire messages into the parent's own messages and one group per direct
|
|
23
|
+
* child. A nested child's messages stay inside its parent's group.
|
|
24
|
+
*/
|
|
25
|
+
export declare function splitSubagentWire<T>(messages: ReadonlyArray<T>): {
|
|
26
|
+
top: Array<T>;
|
|
27
|
+
groups: Array<SubagentWireGroup<T>>;
|
|
28
|
+
};
|
|
29
|
+
/** Text a child wrote, for the parent model and for a later child. */
|
|
30
|
+
export declare function subagentWireText(messages: ReadonlyArray<unknown>): string;
|
|
31
|
+
/**
|
|
32
|
+
* The id of the parent assistant message that hosts a routed turn's cards.
|
|
33
|
+
* The persistence recorder writes that message, and a handoff run passes the
|
|
34
|
+
* same id so the stored thread keeps one copy.
|
|
35
|
+
*/
|
|
36
|
+
export declare function subagentHostMessageId(runId: string): string;
|
|
@@ -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":";AAgCA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAgB,kBAAkB,SAAsC;CACtE,IAAI,CAAC,SAAS,OAAO,KAAK,WAAW,SAAS,OAAO,KAAA;CACrD,MAAM,KAAK,QAAQ;CACnB,OAAO,OAAO,OAAO,YAAY,OAAO,KAAK,KAAK,KAAA;AACpD;AAEA,SAAgB,iBACd,SAC8B;CAC9B,IAAI,CAAC,SAAS,OAAO,KAAK,CAAC,SAAS,QAAQ,QAAQ,GAAG,OAAO,KAAA;CAC9D,MAAM,WAAW,QAAQ,SAAS;CAClC,IAAI,CAAC,SAAS,QAAQ,KAAK,CAAC,SAAS,SAAS,QAAQ,GAAG,OAAO,KAAA;CAEhE,MAAM,EAAE,MAAM,QAAQ,OAAO,iBAAiB,SAAS;CACvD,IAAI,OAAO,SAAS,YAAY,CAAC,SAAS,MAAM,GAAG,OAAO,KAAA;CAC1D,MAAM,EACJ,aACA,qBACA,kBACA,UACA,gBACE,SAAS;CACb,OAAO;EACL;EACA;EACA,GAAI,OAAO,gBAAgB,YAAY,EAAE,YAAY;EACrD,GAAI,SAAS,KAAK,KAChB,OAAO,MAAM,YAAY,YAAY,EACnC,OAAO;GACL,SAAS,MAAM;GACf,GAAI,OAAO,MAAM,SAAS,YAAY,EAAE,MAAM,MAAM,KAAK;EAC3D,EACF;EACF,GAAI,MAAM,QAAQ,YAAY,KAAK,EACjC,cAAc,aAAa,QACxB,OAAqB,OAAO,OAAO,QACtC,EACF;EACA,GAAI,OAAO,wBAAwB,YAAY,EAAE,oBAAoB;EACrE,GAAI,OAAO,qBAAqB,YAAY,EAAE,iBAAiB;EAC/D,GAAI,SAAS,QAAQ,KAAK,EAAE,SAAS;EACrC,GAAI,gBAAgB,QAAQ,EAAE,aAAa,KAAc;CAC3D;AACF;AAEA,SAAS,SAAS,OAAqD;CACrE,OACE,UAAU,aACV,UAAU,cACV,UAAU,WACV,UAAU;AAEd;AAEA,SAAS,MAAS,SAAe;CAC/B,IAAI,CAAC,SAAS,OAAO,GAAG,OAAO;CAC/B,MAAM,EAAE,eAAe,KAAK,GAAG,SAAS;CAExC,MAAM,WAAW,SAAS,KAAK,QAAQ,IAAI,KAAK,WAAW,KAAA;CAC3D,MAAM,WAAW,SAAS,UAAU,QAAQ,IAAI,SAAS,WAAW,KAAA;CACpE,IAAI,CAAC,YAAY,CAAC,YAAY,EAAE,cAAc,WAAW,OAAO;CAChE,MAAM,EAAE,UAAU,OAAO,GAAG,iBAAiB;CAE7C,OAAO;EAAE,GAAG;EAAM,UAAU;GAAE,GAAG;GAAU,UAAU;EAAa;CAAE;AACtE;;;;;AAMA,SAAgB,kBAAqB,UAGnC;CACA,MAAM,2BAAW,IAAI,IAAgC;CACrD,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,KAAK,kBAAkB,OAAO;EACpC,IAAI,OAAO,KAAA,KAAa,CAAC,SAAS,IAAI,EAAE,GACtC,SAAS,IAAI,IAAI,iBAAiB,OAAO,CAAC,EAAE,mBAAmB;CAEnE;CACA,MAAM,UAAU,OAAe;EAC7B,IAAI,UAAU;EACd,KAAK,IAAI,QAAQ,GAAG,QAAQ,IAAI,SAAS;GACvC,MAAM,SAAS,SAAS,IAAI,OAAO;GACnC,IAAI,WAAW,KAAA,KAAa,CAAC,SAAS,IAAI,MAAM,GAAG,OAAO;GAC1D,UAAU;EACZ;EACA,OAAO;CACT;CAEA,MAAM,MAAgB,CAAC;CACvB,MAAM,yBAAS,IAAI,IAAkC;CACrD,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,KAAK,kBAAkB,OAAO;EACpC,IAAI,OAAO,KAAA,GAAW;GACpB,IAAI,KAAK,OAAO;GAChB;EACF;EACA,MAAM,OAAO,OAAO,EAAE;EACtB,IAAI,QAAQ,OAAO,IAAI,IAAI;EAC3B,IAAI,CAAC,OAAO;GACV,QAAQ;IACN,IAAI;IACJ,MAAM;KAAE,MAAM;KAAY,QAAQ;IAAW;IAC7C,UAAU,CAAC;IACX,WAAW,IAAI,SAAS;GAC1B;GACA,OAAO,IAAI,MAAM,KAAK;EACxB;EACA,IAAI,OAAO,MAAM;GACf,MAAM,SAAS,KAAK,OAAO;GAC3B;EACF;EACA,MAAM,OAAO,iBAAiB,OAAO;EACrC,IAAI,MAAM,MAAM,OAAO;EACvB,IAAI,CAAC,MAAM,aAAa,MAAM,SAAS,KAAK,MAAM,OAAO,CAAC;CAC5D;CACA,OAAO;EAAE;EAAK,QAAQ,CAAC,GAAG,OAAO,OAAO,CAAC;CAAE;AAC7C;;AAGA,SAAgB,iBAAiB,UAA0C;CACzE,MAAM,SAAwB,CAAC;CAC/B,KAAK,MAAM,WAAW,UAAU;EAC9B,IAAI,CAAC,SAAS,OAAO,KAAK,QAAQ,SAAS,aAAa;EACxD,IAAI,kBAAkB,OAAO,MAAM,KAAA,GAAW;EAC9C,MAAM,UAAU,QAAQ;EACxB,IAAI,OAAO,YAAY,YAAY,QAAQ,KAAK,MAAM,IACpD,OAAO,KAAK,QAAQ,KAAK,CAAC;CAE9B;CACA,MAAM,SAAS,kBAAkB,QAAQ,CAAC,CAAC;CAC3C,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,OAAO,iBAAiB,MAAM,QAAQ;EAC5C,IAAI,SAAS,IAAI,OAAO,KAAK,GAAG,MAAM,KAAK,KAAK,KAAK,MAAM;CAC7D;CACA,OAAO,OAAO,KAAK,MAAM;AAC3B;;;;;;AAOA,SAAgB,sBAAsB,OAAe;CACnD,OAAO,aAAa;AACtB"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { ContentPart } from '../types.js';
|
|
2
2
|
/**
|
|
3
3
|
* Structural check for a single `ContentPart`. A text part must carry a string
|
|
4
|
-
* `content
|
|
5
|
-
*
|
|
4
|
+
* `content`. Every other part carries a source with a string `value`; a file
|
|
5
|
+
* source's `value` is a non-empty opaque handle, and its optional `provider`
|
|
6
|
+
* is a string.
|
|
6
7
|
*/
|
|
7
8
|
export declare function isContentPart(value: unknown): value is ContentPart;
|
|
8
9
|
/**
|
|
@@ -12,6 +13,15 @@ export declare function isContentPart(value: unknown): value is ContentPart;
|
|
|
12
13
|
* footgun narrow.
|
|
13
14
|
*/
|
|
14
15
|
export declare function isContentPartArray(value: unknown): value is Array<ContentPart>;
|
|
16
|
+
/**
|
|
17
|
+
* Error text for a failed tool result: `output.error` when it is a string,
|
|
18
|
+
* else the output itself when it is a string, else a generic message.
|
|
19
|
+
* `StreamProcessor` and `chat()` history share it, so a reload shows the
|
|
20
|
+
* same text as the live stream.
|
|
21
|
+
*/
|
|
22
|
+
export declare function toolResultErrorText(output: unknown): string;
|
|
23
|
+
/** Parse tool result content as JSON. Plain text stays a string. */
|
|
24
|
+
export declare function parseToolOutput(content: string): unknown;
|
|
15
25
|
/**
|
|
16
26
|
* Normalize a tool's return value for transport:
|
|
17
27
|
* - string → unchanged
|
|
@@ -8,8 +8,9 @@ var CONTENT_PART_TYPES = /* @__PURE__ */ new Set([
|
|
|
8
8
|
]);
|
|
9
9
|
/**
|
|
10
10
|
* Structural check for a single `ContentPart`. A text part must carry a string
|
|
11
|
-
* `content
|
|
12
|
-
*
|
|
11
|
+
* `content`. Every other part carries a source with a string `value`; a file
|
|
12
|
+
* source's `value` is a non-empty opaque handle, and its optional `provider`
|
|
13
|
+
* is a string.
|
|
13
14
|
*/
|
|
14
15
|
function isContentPart(value) {
|
|
15
16
|
if (typeof value !== "object" || value === null) return false;
|
|
@@ -20,6 +21,7 @@ function isContentPart(value) {
|
|
|
20
21
|
if (typeof source !== "object" || source === null) return false;
|
|
21
22
|
const src = source;
|
|
22
23
|
if (typeof src.value !== "string") return false;
|
|
24
|
+
if (src.type === "file") return src.value.length > 0 && (src.provider === void 0 || typeof src.provider === "string");
|
|
23
25
|
if (src.type === "data") return typeof src.mimeType === "string";
|
|
24
26
|
return src.type === "url";
|
|
25
27
|
}
|
|
@@ -33,6 +35,24 @@ function isContentPartArray(value) {
|
|
|
33
35
|
return Array.isArray(value) && value.length > 0 && value.every(isContentPart);
|
|
34
36
|
}
|
|
35
37
|
/**
|
|
38
|
+
* Error text for a failed tool result: `output.error` when it is a string,
|
|
39
|
+
* else the output itself when it is a string, else a generic message.
|
|
40
|
+
* `StreamProcessor` and `chat()` history share it, so a reload shows the
|
|
41
|
+
* same text as the live stream.
|
|
42
|
+
*/
|
|
43
|
+
function toolResultErrorText(output) {
|
|
44
|
+
if (output && typeof output === "object" && "error" in output && typeof output.error === "string") return output.error;
|
|
45
|
+
return typeof output === "string" ? output : "Tool execution failed";
|
|
46
|
+
}
|
|
47
|
+
/** Parse tool result content as JSON. Plain text stays a string. */
|
|
48
|
+
function parseToolOutput(content) {
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(content);
|
|
51
|
+
} catch {
|
|
52
|
+
return content;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
36
56
|
* Normalize a tool's return value for transport:
|
|
37
57
|
* - string → unchanged
|
|
38
58
|
* - ContentPart array → unchanged (multimodal, passed through to the adapter)
|
|
@@ -44,6 +64,6 @@ function normalizeToolResult(result) {
|
|
|
44
64
|
return JSON.stringify(result);
|
|
45
65
|
}
|
|
46
66
|
//#endregion
|
|
47
|
-
export { isContentPart, isContentPartArray, normalizeToolResult };
|
|
67
|
+
export { isContentPart, isContentPartArray, normalizeToolResult, parseToolOutput, toolResultErrorText };
|
|
48
68
|
|
|
49
69
|
//# sourceMappingURL=tool-result.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-result.js","names":[],"sources":["../../../src/utilities/tool-result.ts"],"sourcesContent":["import type { ContentPart } from '../types'\n\nconst CONTENT_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n])\n\n/**\n * Structural check for a single `ContentPart`. A text part must carry a string\n * `content
|
|
1
|
+
{"version":3,"file":"tool-result.js","names":[],"sources":["../../../src/utilities/tool-result.ts"],"sourcesContent":["import type { ContentPart } from '../types'\n\nconst CONTENT_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n])\n\n/**\n * Structural check for a single `ContentPart`. A text part must carry a string\n * `content`. Every other part carries a source with a string `value`; a file\n * source's `value` is a non-empty opaque handle, and its optional `provider`\n * is a string.\n */\nexport function isContentPart(value: unknown): value is ContentPart {\n if (typeof value !== 'object' || value === null) return false\n const part = value as Record<string, unknown>\n if (typeof part.type !== 'string' || !CONTENT_PART_TYPES.has(part.type)) {\n return false\n }\n if (part.type === 'text') {\n return typeof part.content === 'string'\n }\n const source = part.source\n if (typeof source !== 'object' || source === null) return false\n const src = source as Record<string, unknown>\n if (typeof src.value !== 'string') return false\n // `file` sources carry an opaque handle in `value`; `provider`, when set,\n // names the issuer.\n if (src.type === 'file') {\n return (\n src.value.length > 0 &&\n (src.provider === undefined || typeof src.provider === 'string')\n )\n }\n // `data` sources require a mimeType (matches ContentPartDataSource); `url`\n // sources don't. Requiring it here keeps the runtime guard consistent with\n // the type and avoids emitting `data:undefined;base64,...` downstream.\n if (src.type === 'data') return typeof src.mimeType === 'string'\n return src.type === 'url'\n}\n\n/**\n * True iff `value` is a NON-EMPTY array whose every element is a valid\n * `ContentPart`. Empty arrays and mixed arrays return false so they continue\n * to be treated as ordinary (stringified) data — this keeps the auto-detection\n * footgun narrow.\n */\nexport function isContentPartArray(\n value: unknown,\n): value is Array<ContentPart> {\n return Array.isArray(value) && value.length > 0 && value.every(isContentPart)\n}\n\n/**\n * Error text for a failed tool result: `output.error` when it is a string,\n * else the output itself when it is a string, else a generic message.\n * `StreamProcessor` and `chat()` history share it, so a reload shows the\n * same text as the live stream.\n */\nexport function toolResultErrorText(output: unknown): string {\n if (\n output &&\n typeof output === 'object' &&\n 'error' in output &&\n typeof output.error === 'string'\n ) {\n return output.error\n }\n return typeof output === 'string' ? output : 'Tool execution failed'\n}\n\n/** Parse tool result content as JSON. Plain text stays a string. */\nexport function parseToolOutput(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\n }\n}\n\n/**\n * Normalize a tool's return value for transport:\n * - string → unchanged\n * - ContentPart array → unchanged (multimodal, passed through to the adapter)\n * - anything else → `JSON.stringify`\n */\nexport function normalizeToolResult(\n result: unknown,\n): string | Array<ContentPart> {\n if (typeof result === 'string') return result\n if (isContentPartArray(result)) return result\n return JSON.stringify(result)\n}\n"],"mappings":";AAEA,IAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;;;AAQD,SAAgB,cAAc,OAAsC;CAClE,IAAI,OAAO,UAAU,YAAY,UAAU,MAAM,OAAO;CACxD,MAAM,OAAO;CACb,IAAI,OAAO,KAAK,SAAS,YAAY,CAAC,mBAAmB,IAAI,KAAK,IAAI,GACpE,OAAO;CAET,IAAI,KAAK,SAAS,QAChB,OAAO,OAAO,KAAK,YAAY;CAEjC,MAAM,SAAS,KAAK;CACpB,IAAI,OAAO,WAAW,YAAY,WAAW,MAAM,OAAO;CAC1D,MAAM,MAAM;CACZ,IAAI,OAAO,IAAI,UAAU,UAAU,OAAO;CAG1C,IAAI,IAAI,SAAS,QACf,OACE,IAAI,MAAM,SAAS,MAClB,IAAI,aAAa,KAAA,KAAa,OAAO,IAAI,aAAa;CAM3D,IAAI,IAAI,SAAS,QAAQ,OAAO,OAAO,IAAI,aAAa;CACxD,OAAO,IAAI,SAAS;AACtB;;;;;;;AAQA,SAAgB,mBACd,OAC6B;CAC7B,OAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,aAAa;AAC9E;;;;;;;AAQA,SAAgB,oBAAoB,QAAyB;CAC3D,IACE,UACA,OAAO,WAAW,YAClB,WAAW,UACX,OAAO,OAAO,UAAU,UAExB,OAAO,OAAO;CAEhB,OAAO,OAAO,WAAW,WAAW,SAAS;AAC/C;;AAGA,SAAgB,gBAAgB,SAA0B;CACxD,IAAI;EACF,OAAO,KAAK,MAAM,OAAO;CAC3B,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,oBACd,QAC6B;CAC7B,IAAI,OAAO,WAAW,UAAU,OAAO;CACvC,IAAI,mBAAmB,MAAM,GAAG,OAAO;CACvC,OAAO,KAAK,UAAU,MAAM;AAC9B"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/ai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.61.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,10 +85,10 @@
|
|
|
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
|
-
"@tanstack/ai-event-client": "^0.
|
|
91
|
+
"@tanstack/ai-event-client": "^0.13.0",
|
|
92
92
|
"@tanstack/ai-utils": "^0.4.1"
|
|
93
93
|
},
|
|
94
94
|
"peerDependencies": {
|
|
@@ -456,6 +456,68 @@ compatible providers speak.
|
|
|
456
456
|
> Verify the provider's current `baseURL` and model ids against its live docs —
|
|
457
457
|
> they drift. See `docs/adapters/openai-compatible.md` for the full provider table.
|
|
458
458
|
|
|
459
|
+
### 7. Files Adapters (upload once, reference by handle)
|
|
460
|
+
|
|
461
|
+
Four providers expose a native Files/storage API as a tree-shakeable `files`
|
|
462
|
+
adapter: `openaiFiles()`, `anthropicFiles()`, `geminiFiles()` (each reads the
|
|
463
|
+
same env var as the provider's text adapter; `create*Files(apiKey)` variants
|
|
464
|
+
take an explicit key), and `falFiles(config)`. Upload media once with
|
|
465
|
+
`uploadFile()`, then reference the returned `FileHandle` in messages via a
|
|
466
|
+
`{ type: 'file' }` content source instead of re-sending base64 each request:
|
|
467
|
+
|
|
468
|
+
```typescript
|
|
469
|
+
import { chat, fileSourceFromHandle, uploadFile } from '@tanstack/ai'
|
|
470
|
+
import { openaiFiles, openaiText } from '@tanstack/ai-openai'
|
|
471
|
+
import { pdfBase64 } from './pdf-data'
|
|
472
|
+
|
|
473
|
+
const handle = await uploadFile({
|
|
474
|
+
adapter: openaiFiles(),
|
|
475
|
+
input: { data: pdfBase64, mimeType: 'application/pdf' },
|
|
476
|
+
})
|
|
477
|
+
|
|
478
|
+
chat({
|
|
479
|
+
adapter: openaiText('gpt-5.5'),
|
|
480
|
+
messages: [
|
|
481
|
+
{
|
|
482
|
+
role: 'user',
|
|
483
|
+
content: [
|
|
484
|
+
{ type: 'text', content: 'Summarize this document' },
|
|
485
|
+
{ type: 'document', source: fileSourceFromHandle(handle) },
|
|
486
|
+
],
|
|
487
|
+
},
|
|
488
|
+
],
|
|
489
|
+
})
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
Rules agents must respect:
|
|
493
|
+
|
|
494
|
+
- **The source is one opaque handle plus its issuer.** `fileSourceFromHandle`
|
|
495
|
+
builds `{ type: 'file', value: 'file-…', provider: 'openai' }`, matching the
|
|
496
|
+
AG-UI `FileSource` arm. A handle only resolves at the provider that issued
|
|
497
|
+
it, so an adapter throws when `provider` names a different adapter.
|
|
498
|
+
`provider` is optional, as on the AG-UI wire; a source without it is taken
|
|
499
|
+
as-is. To use the same bytes with two providers,
|
|
500
|
+
upload to each and send the matching handle.
|
|
501
|
+
- **Adapters declare `supportsFileSources`.** For adapters that don't (Groq, Bedrock, Mistral, OpenRouter, Ollama, BytePlus, Cohere, and anything
|
|
502
|
+
written before this feature), `chat()` / `generateImage()` /
|
|
503
|
+
`generateVideo()` / `embed()` reject file sources in preflight, before any
|
|
504
|
+
request is built — pass `data`/`url` sources there instead.
|
|
505
|
+
- **Lifecycle:** `getFile()` / `deleteFile()` work for OpenAI, Anthropic,
|
|
506
|
+
Gemini, and Grok, and accept the handle itself (provider-literal typed, so a
|
|
507
|
+
foreign handle is a compile error). fal storage is upload-only, so those
|
|
508
|
+
calls throw for `falFiles()`. `grokFiles().get()` mints the public URL again,
|
|
509
|
+
so do not call it after `revokePublicUrl()`.
|
|
510
|
+
- **Some endpoints need raw bytes even on supporting providers:** OpenAI
|
|
511
|
+
`images/edits` + Sora `input_reference`, Gemini Veo, and Chat Completions
|
|
512
|
+
image inputs throw endpoint-specific errors for file sources.
|
|
513
|
+
- **A file source crosses the chat wire.** A browser that holds a handle puts
|
|
514
|
+
`fileSourceFromHandle(handle)` straight into the `sendMessage` content, and
|
|
515
|
+
the server passes the messages to `chat()` as usual. `fileSourceFromHandle`
|
|
516
|
+
and the `FileHandle` type are exported from the browser-safe
|
|
517
|
+
`@tanstack/ai/client` entry.
|
|
518
|
+
|
|
519
|
+
See `docs/advanced/files-api.md` for the full guide.
|
|
520
|
+
|
|
459
521
|
## Behind a proxy or gateway
|
|
460
522
|
|
|
461
523
|
Every adapter's client config accepts `baseURL` and `defaultHeaders`. Use these
|
|
@@ -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
|
|
@@ -299,6 +299,8 @@ import type { UIMessage } from '@tanstack/ai-react'
|
|
|
299
299
|
|
|
300
300
|
function ImagePart({ part }: { part: UIMessage['parts'][number] }) {
|
|
301
301
|
if (part.type !== 'image') return null
|
|
302
|
+
// A provider file handle is an opaque id, so the browser cannot load it.
|
|
303
|
+
if (part.source.type === 'file') return null
|
|
302
304
|
const src =
|
|
303
305
|
part.source.type === 'url'
|
|
304
306
|
? part.source.value
|
|
@@ -307,6 +309,18 @@ function ImagePart({ part }: { part: UIMessage['parts'][number] }) {
|
|
|
307
309
|
}
|
|
308
310
|
```
|
|
309
311
|
|
|
312
|
+
For media reused across turns, upload once via a provider Files adapter
|
|
313
|
+
(`openaiFiles()`, `anthropicFiles()`, `geminiFiles()`, `grokFiles()`,
|
|
314
|
+
`falFiles()`) and send a `{ type: 'file' }` source built with
|
|
315
|
+
`fileSourceFromHandle(handle)` instead of re-sending base64 each request. The
|
|
316
|
+
source is `{ type: 'file', value, provider }`: an opaque handle and the adapter
|
|
317
|
+
that issued it. A different provider (or one without Files API support at all)
|
|
318
|
+
rejects it with a clear error before any request is sent.
|
|
319
|
+
The source crosses the chat wire, so the browser can put it straight into the
|
|
320
|
+
`sendMessage` content. Import `fileSourceFromHandle` from the browser-safe
|
|
321
|
+
`@tanstack/ai/client` entry. See `ai-core/adapter-configuration/SKILL.md` §7
|
|
322
|
+
and `docs/advanced/files-api.md`.
|
|
323
|
+
|
|
310
324
|
### 4. Sending Audio Messages (Browser Recording)
|
|
311
325
|
|
|
312
326
|
Use `useAudioRecorder` from `@tanstack/ai-react` (or `createAudioRecorder` in Svelte) to capture audio in the browser. The resolved `AudioRecording` includes a ready-to-use `part` that slots directly into `sendMessage`.
|
|
@@ -283,6 +283,14 @@ await generateVideo({
|
|
|
283
283
|
})
|
|
284
284
|
```
|
|
285
285
|
|
|
286
|
+
Reference images / start frames that are reused (or arrive as inline base64 on
|
|
287
|
+
memory-constrained runtimes) can instead be uploaded once via the provider's
|
|
288
|
+
Files adapter and referenced with `source: fileSourceFromHandle(handle)` —
|
|
289
|
+
supported for Gemini image generation (`geminiFiles()`) and fal image/video
|
|
290
|
+
inputs (`falFiles()`). Endpoints that require raw bytes (OpenAI `images/edits`,
|
|
291
|
+
Sora `input_reference`, Gemini Veo) reject file sources with a clear error.
|
|
292
|
+
See `ai-core/adapter-configuration/SKILL.md` §7.
|
|
293
|
+
|
|
286
294
|
**URL inputs that require an upload throw by default.** Most adapters pass a
|
|
287
295
|
`type: 'url'` source straight through to the provider. Three paths can't —
|
|
288
296
|
OpenAI `images.edit()`, OpenAI Sora `input_reference`, and Gemini **Veo** —
|
|
@@ -312,7 +320,7 @@ with `allowUrlFetch: true` on the adapter config
|
|
|
312
320
|
| 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
321
|
| 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
322
|
| 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).
|
|
323
|
+
| 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
324
|
| 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
325
|
| Anthropic | n/a (no image generation API). | n/a |
|
|
318
326
|
|
|
@@ -1174,7 +1182,7 @@ generateSpeech({
|
|
|
1174
1182
|
|
|
1175
1183
|
Not every model accepts image-conditioned prompts. The `prompt` type is
|
|
1176
1184
|
narrowed per model, so passing an image part to a text-only model
|
|
1177
|
-
(dall-e-3, Imagen
|
|
1185
|
+
(dall-e-3, Imagen) is a **compile-time error**; adapters
|
|
1178
1186
|
also throw a clear runtime error as a backstop, so users learn at call
|
|
1179
1187
|
time rather than getting silently wrong output.
|
|
1180
1188
|
|
|
@@ -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
|
|
@@ -89,6 +89,15 @@ export interface TextAdapter<
|
|
|
89
89
|
*/
|
|
90
90
|
readonly requires?: ReadonlyArray<CapabilityHandle>
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Declares that this adapter can consume `{ type: 'file' }` content sources
|
|
94
|
+
* (provider Files API references). `chat()` rejects file sources in preflight
|
|
95
|
+
* for adapters that don't declare this, so an adapter written before the
|
|
96
|
+
* file arm existed fails closed instead of silently mis-mapping a reference
|
|
97
|
+
* onto its URL/data branch.
|
|
98
|
+
*/
|
|
99
|
+
readonly supportsFileSources?: boolean
|
|
100
|
+
|
|
92
101
|
/**
|
|
93
102
|
* @internal Type-only properties for inference. Not assigned at runtime.
|
|
94
103
|
*/
|
|
@@ -209,6 +218,7 @@ export abstract class BaseTextAdapter<
|
|
|
209
218
|
abstract readonly name: string
|
|
210
219
|
readonly model: TModel
|
|
211
220
|
readonly requires?: ReadonlyArray<CapabilityHandle> = undefined
|
|
221
|
+
readonly supportsFileSources: boolean = false
|
|
212
222
|
|
|
213
223
|
// Type-only property - never assigned at runtime
|
|
214
224
|
declare '~types': {
|
|
@@ -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
|
+
}
|