@tanstack/ai 0.23.1 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/esm/activities/chat/adapter.d.ts +3 -1
  2. package/dist/esm/activities/chat/adapter.js.map +1 -1
  3. package/dist/esm/activities/chat/index.d.ts +33 -9
  4. package/dist/esm/activities/chat/index.js +19 -9
  5. package/dist/esm/activities/chat/index.js.map +1 -1
  6. package/dist/esm/activities/chat/messages.js +2 -1
  7. package/dist/esm/activities/chat/messages.js.map +1 -1
  8. package/dist/esm/activities/chat/middleware/compose.d.ts +14 -14
  9. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  10. package/dist/esm/activities/chat/middleware/types.d.ts +21 -21
  11. package/dist/esm/activities/chat/runtime-context-types.d.ts +43 -0
  12. package/dist/esm/activities/chat/stream/message-updaters.d.ts +2 -2
  13. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  14. package/dist/esm/activities/chat/stream/processor.d.ts +1 -0
  15. package/dist/esm/activities/chat/stream/processor.js +35 -12
  16. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  17. package/dist/esm/activities/chat/tools/tool-calls.d.ts +15 -5
  18. package/dist/esm/activities/chat/tools/tool-calls.js +59 -19
  19. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  20. package/dist/esm/activities/chat/tools/tool-definition.d.ts +12 -8
  21. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  22. package/dist/esm/activities/error-payload.d.ts +26 -0
  23. package/dist/esm/activities/error-payload.js +12 -1
  24. package/dist/esm/activities/error-payload.js.map +1 -1
  25. package/dist/esm/activities/generateAudio/index.js +9 -0
  26. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  27. package/dist/esm/activities/generateSpeech/index.js +9 -0
  28. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  29. package/dist/esm/adapter-internals.d.ts +1 -1
  30. package/dist/esm/adapter-internals.js +3 -2
  31. package/dist/esm/client.d.ts +1 -1
  32. package/dist/esm/client.js +3 -1
  33. package/dist/esm/client.js.map +1 -1
  34. package/dist/esm/index.d.ts +3 -1
  35. package/dist/esm/index.js +9 -1
  36. package/dist/esm/index.js.map +1 -1
  37. package/dist/esm/tool-registry.d.ts +7 -7
  38. package/dist/esm/tool-registry.js +1 -1
  39. package/dist/esm/tool-registry.js.map +1 -1
  40. package/dist/esm/types.d.ts +56 -59
  41. package/dist/esm/utilities/ag-ui-wire.js +1 -1
  42. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  43. package/dist/esm/utilities/chat-params.d.ts +8 -3
  44. package/dist/esm/utilities/chat-params.js +6 -2
  45. package/dist/esm/utilities/chat-params.js.map +1 -1
  46. package/dist/esm/utilities/tool-result.d.ts +21 -0
  47. package/dist/esm/utilities/tool-result.js +37 -0
  48. package/dist/esm/utilities/tool-result.js.map +1 -0
  49. package/dist/esm/utilities/usage.d.ts +31 -0
  50. package/dist/esm/utilities/usage.js +11 -0
  51. package/dist/esm/utilities/usage.js.map +1 -0
  52. package/package.json +2 -2
  53. package/src/activities/chat/adapter.ts +3 -0
  54. package/src/activities/chat/index.ts +219 -47
  55. package/src/activities/chat/messages.ts +2 -1
  56. package/src/activities/chat/middleware/compose.ts +23 -17
  57. package/src/activities/chat/middleware/types.ts +21 -21
  58. package/src/activities/chat/runtime-context-types.ts +68 -0
  59. package/src/activities/chat/stream/message-updaters.ts +2 -1
  60. package/src/activities/chat/stream/processor.ts +48 -8
  61. package/src/activities/chat/tools/tool-calls.ts +138 -43
  62. package/src/activities/chat/tools/tool-definition.ts +25 -31
  63. package/src/activities/error-payload.ts +44 -0
  64. package/src/activities/generateAudio/index.ts +10 -0
  65. package/src/activities/generateSpeech/index.ts +10 -0
  66. package/src/adapter-internals.ts +4 -1
  67. package/src/client.ts +5 -1
  68. package/src/index.ts +10 -0
  69. package/src/tool-registry.ts +16 -14
  70. package/src/types.ts +118 -79
  71. package/src/utilities/ag-ui-wire.ts +4 -1
  72. package/src/utilities/chat-params.ts +22 -7
  73. package/src/utilities/tool-result.ts +60 -0
  74. package/src/utilities/usage.ts +41 -0
@@ -1,5 +1,5 @@
1
1
  import { Context as AGUIContext } from '@ag-ui/core';
2
- import { JSONSchema, ModelMessage, Tool, UIMessage } from '../types.js';
2
+ import { JSONSchema, ModelMessage, SchemaInput, Tool, UIMessage } from '../types.js';
3
3
  /**
4
4
  * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.
5
5
  *
@@ -24,7 +24,12 @@ export declare function chatParamsFromRequestBody(body: unknown): Promise<{
24
24
  }>;
25
25
  forwardedProps: Record<string, unknown>;
26
26
  state: unknown;
27
+ /**
28
+ * @deprecated Use `aguiContext` instead. This alias will be removed in a
29
+ * future release.
30
+ */
27
31
  context: Array<AGUIContext>;
32
+ aguiContext: Array<AGUIContext>;
28
33
  }>;
29
34
  /**
30
35
  * Read an HTTP `Request`, parse its JSON body, and validate it as an
@@ -73,8 +78,8 @@ export declare function chatParamsFromRequest(req: Request): Promise<Awaited<Ret
73
78
  * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.
74
79
  * @returns A merged array suitable for `chat({ tools })`.
75
80
  */
76
- export declare function mergeAgentTools(serverTools: ReadonlyArray<Tool>, clientTools: ReadonlyArray<{
81
+ export declare function mergeAgentTools<TContext = unknown>(serverTools: ReadonlyArray<Tool<SchemaInput, SchemaInput, string, TContext>>, clientTools: ReadonlyArray<{
77
82
  name: string;
78
83
  description: string;
79
84
  parameters: JSONSchema;
80
- }>): Array<Tool>;
85
+ }>): Array<Tool<SchemaInput, SchemaInput, string, TContext>>;
@@ -28,6 +28,7 @@ function chatParamsFromRequestBody(body) {
28
28
  );
29
29
  }
30
30
  const parsed = parseResult.data;
31
+ const aguiContext = parsed.context;
31
32
  const rawMessages = body.messages ?? [];
32
33
  const messages = parsed.messages.map((m, i) => {
33
34
  const raw = rawMessages[i];
@@ -44,7 +45,8 @@ function chatParamsFromRequestBody(body) {
44
45
  tools: parsed.tools,
45
46
  forwardedProps: parsed.forwardedProps ?? {},
46
47
  state: parsed.state,
47
- context: parsed.context
48
+ context: aguiContext,
49
+ aguiContext
48
50
  });
49
51
  }
50
52
  async function chatParamsFromRequest(req) {
@@ -72,7 +74,9 @@ async function chatParamsFromRequest(req) {
72
74
  }
73
75
  function mergeAgentTools(serverTools, clientTools) {
74
76
  const seen = new Set(serverTools.map((t) => t.name));
75
- const merged = [...serverTools];
77
+ const merged = [
78
+ ...serverTools
79
+ ];
76
80
  for (const ct of clientTools) {
77
81
  if (seen.has(ct.name)) {
78
82
  continue;
@@ -1 +1 @@
1
- {"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type { JSONSchema, ModelMessage, Tool, UIMessage } from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n context: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: parsed.context,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools(\n serverTools: ReadonlyArray<Tool>,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): Array<Tool> {\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<Tool> = [...serverTools]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n } as Tool)\n }\n return merged\n}\n"],"names":[],"mappings":";AAIA,MAAM,uCAAuB,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,OAAkD;AACtE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,OAAQ,EAAyB;AACvC,QAAI,OAAO,SAAS,YAAY,CAAC,iBAAiB,IAAI,IAAI,EAAG,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MASvC;AACD,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,QAAQ;AAAA,MACb,IAAI;AAAA,QACF,gLAGwB,YAAY,MAAM,OAAO;AAAA,MAAA;AAAA,IACnD;AAAA,EAEJ;AAEA,QAAM,SAAS,YAAY;AAK3B,QAAM,cACH,KAAuD,YAAY,CAAA;AACtE,QAAM,WAAW,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM;AAC7C,UAAM,MAAM,YAAY,CAAC;AACzB,QACE,OACA,OAAO,QAAQ,YACf,WAAW,OACX,aAAa,IAAI,KAAK,GACtB;AACA,aAAO,EAAE,GAAG,GAAG,OAAO,IAAI,MAAA;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IAKd,gBAAiB,OAAO,kBAAkB,CAAA;AAAA,IAC1C,OAAO,OAAO;AAAA,IACd,SAAS,OAAO;AAAA,EAAA,CACjB;AACH;AA2BA,eAAsB,sBACpB,KACgE;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAA;AAAA,EACnB,SAAS,OAAO;AAGd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,MAAM,0BAA0B,IAAI;AAAA,EAC7C,SAAS,OAAO;AAKd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACF;AAuBO,SAAS,gBACd,aACA,aAKa;AACb,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,SAAsB,CAAC,GAAG,WAAW;AAC3C,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,IAAI,GAAG,IAAI,GAAG;AAErB;AAAA,IACF;AACA,SAAK,IAAI,GAAG,IAAI;AAChB,WAAO,KAAK;AAAA,MACV,MAAM,GAAG;AAAA,MACT,aAAa,GAAG;AAAA,MAChB,aAAa,GAAG;AAAA;AAAA;AAAA,IAAA,CAGT;AAAA,EACX;AACA,SAAO;AACT;"}
1
+ {"version":3,"file":"chat-params.js","sources":["../../../src/utilities/chat-params.ts"],"sourcesContent":["import { AGUIError, RunAgentInputSchema } from '@ag-ui/core'\nimport type { Context as AGUIContext } from '@ag-ui/core'\nimport type {\n JSONSchema,\n ModelMessage,\n SchemaInput,\n Tool,\n UIMessage,\n} from '../types'\n\nconst KNOWN_PART_TYPES = new Set([\n 'text',\n 'image',\n 'audio',\n 'video',\n 'document',\n 'tool-call',\n 'tool-result',\n 'thinking',\n])\n\nfunction isValidParts(value: unknown): value is Array<{ type: string }> {\n if (!Array.isArray(value)) return false\n for (const p of value) {\n if (!p || typeof p !== 'object') return false\n const type = (p as { type?: unknown }).type\n if (typeof type !== 'string' || !KNOWN_PART_TYPES.has(type)) return false\n }\n return true\n}\n\n/**\n * Parse and validate an HTTP request body as an AG-UI `RunAgentInput`.\n *\n * Returns a spread-friendly object whose `messages` field is suitable for\n * passing directly to `chat({ messages })`. The existing\n * `convertMessagesToModelMessages` handles AG-UI fan-out dedup and\n * reasoning/activity/developer-role normalization internally.\n *\n * @throws An error with a migration-pointing message when the body does\n * not conform to AG-UI 0.0.52 `RunAgentInputSchema`. Surface this as a\n * 400 Bad Request to the client.\n */\nexport function chatParamsFromRequestBody(body: unknown): Promise<{\n messages: Array<UIMessage | ModelMessage>\n threadId: string\n runId: string\n parentRunId?: string\n tools: Array<{ name: string; description: string; parameters: JSONSchema }>\n forwardedProps: Record<string, unknown>\n state: unknown\n /**\n * @deprecated Use `aguiContext` instead. This alias will be removed in a\n * future release.\n */\n context: Array<AGUIContext>\n aguiContext: Array<AGUIContext>\n}> {\n const parseResult = RunAgentInputSchema.safeParse(body)\n if (!parseResult.success) {\n return Promise.reject(\n new AGUIError(\n `Request body is not a valid AG-UI RunAgentInput. ` +\n `If you're upgrading from a previous @tanstack/ai-client release, ` +\n `see docs/migration/ag-ui-compliance.md. ` +\n `Validation errors: ${parseResult.error.message}`,\n ),\n )\n }\n\n const parsed = parseResult.data\n const aguiContext = parsed.context\n\n // AG-UI Zod uses `.strip()` so extra fields like `parts` on messages are\n // dropped during parse. We re-attach them from the original body so the\n // existing UIMessage path inside `chat()` can use them directly.\n const rawMessages =\n (body as { messages?: Array<Record<string, unknown>> }).messages ?? []\n const messages = parsed.messages.map((m, i) => {\n const raw = rawMessages[i]\n if (\n raw &&\n typeof raw === 'object' &&\n 'parts' in raw &&\n isValidParts(raw.parts)\n ) {\n return { ...m, parts: raw.parts } as UIMessage | ModelMessage\n }\n return m as ModelMessage\n })\n\n return Promise.resolve({\n messages,\n threadId: parsed.threadId,\n runId: parsed.runId,\n parentRunId: parsed.parentRunId,\n tools: parsed.tools as Array<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n forwardedProps: (parsed.forwardedProps ?? {}) as Record<string, unknown>,\n state: parsed.state,\n context: aguiContext,\n aguiContext,\n })\n}\n\n/**\n * Read an HTTP `Request`, parse its JSON body, and validate it as an\n * AG-UI `RunAgentInput` — collapsing the standard `req.json()` +\n * `chatParamsFromRequestBody(...)` pair into a single call.\n *\n * On a malformed body or invalid AG-UI shape, this **throws a\n * `Response`** with status 400 and a migration-pointing message in the\n * body. Frameworks that natively handle thrown `Response` objects\n * (TanStack Start, SolidStart, Remix, React Router 7) will return the\n * 400 to the client automatically, so the handler reduces to:\n *\n * ```ts\n * export async function POST(req: Request) {\n * const params = await chatParamsFromRequest(req)\n * // ...use params\n * }\n * ```\n *\n * In frameworks that do not auto-handle thrown `Response` objects\n * (Next.js Route Handlers, SvelteKit, Hono, raw Node), wrap the call\n * with try/catch and return the caught Response yourself, or use\n * `chatParamsFromRequestBody` directly with your own JSON-parsing.\n *\n * @throws {Response} 400 on malformed JSON or invalid AG-UI shape.\n */\nexport async function chatParamsFromRequest(\n req: Request,\n): Promise<Awaited<ReturnType<typeof chatParamsFromRequestBody>>> {\n let body: unknown\n try {\n body = await req.json()\n } catch (cause) {\n // Preserve the underlying error on the thrown Response for\n // server-side observability without leaking it to the client.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n try {\n return await chatParamsFromRequestBody(body)\n } catch (cause) {\n // Generic public message — avoid echoing Zod paths (which can contain\n // user payload fragments) or internal validator strings to the client.\n // The original AGUIError is attached as `cause` so server logs can\n // surface it without exposing it to remote callers.\n const res = new Response(\n 'Invalid AG-UI request body. See docs/migration/ag-ui-compliance.md.',\n { status: 400 },\n )\n ;(res as { cause?: unknown }).cause = cause\n throw res\n }\n}\n\n/**\n * Merge a server-side tool array with the AG-UI client-declared tools\n * received in the request body.\n *\n * Rules:\n * - Server tools win on name collision. The client's declaration is\n * ignored if the server already has a tool with that name. The client's\n * UI-side handler still fires when the streamed tool-result event comes\n * through (see `chat-client.ts` `onToolCall`), giving the\n * \"after server execution the client also handles\" semantic for free.\n * - Client-only tools (name not in `serverTools`) become no-execute\n * entries: the runtime's existing `ClientToolRequest` path handles\n * them — server emits a tool-call request, client executes via its\n * registered handler, client posts back the result.\n *\n * @param serverTools - The server's tool array (e.g. from\n * `[myToolDef.server(...)]`). Pass directly to `chat({ tools })`.\n * @param clientTools - The `tools` array received from\n * `chatParamsFromRequest(...)` / `chatParamsFromRequestBody(...)`.\n * @returns A merged array suitable for `chat({ tools })`.\n */\nexport function mergeAgentTools<TContext = unknown>(\n serverTools: ReadonlyArray<Tool<SchemaInput, SchemaInput, string, TContext>>,\n clientTools: ReadonlyArray<{\n name: string\n description: string\n parameters: JSONSchema\n }>,\n): Array<Tool<SchemaInput, SchemaInput, string, TContext>> {\n const seen = new Set(serverTools.map((t) => t.name))\n const merged: Array<Tool<SchemaInput, SchemaInput, string, TContext>> = [\n ...serverTools,\n ]\n for (const ct of clientTools) {\n if (seen.has(ct.name)) {\n // Server wins on name collision.\n continue\n }\n seen.add(ct.name)\n merged.push({\n name: ct.name,\n description: ct.description,\n inputSchema: ct.parameters,\n // No `execute` — runtime treats this as a client-side tool and\n // emits ClientToolRequest events.\n } as Tool<SchemaInput, SchemaInput, string, TContext>)\n }\n return merged\n}\n"],"names":[],"mappings":";AAUA,MAAM,uCAAuB,IAAI;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAED,SAAS,aAAa,OAAkD;AACtE,MAAI,CAAC,MAAM,QAAQ,KAAK,EAAG,QAAO;AAClC,aAAW,KAAK,OAAO;AACrB,QAAI,CAAC,KAAK,OAAO,MAAM,SAAU,QAAO;AACxC,UAAM,OAAQ,EAAyB;AACvC,QAAI,OAAO,SAAS,YAAY,CAAC,iBAAiB,IAAI,IAAI,EAAG,QAAO;AAAA,EACtE;AACA,SAAO;AACT;AAcO,SAAS,0BAA0B,MAcvC;AACD,QAAM,cAAc,oBAAoB,UAAU,IAAI;AACtD,MAAI,CAAC,YAAY,SAAS;AACxB,WAAO,QAAQ;AAAA,MACb,IAAI;AAAA,QACF,gLAGwB,YAAY,MAAM,OAAO;AAAA,MAAA;AAAA,IACnD;AAAA,EAEJ;AAEA,QAAM,SAAS,YAAY;AAC3B,QAAM,cAAc,OAAO;AAK3B,QAAM,cACH,KAAuD,YAAY,CAAA;AACtE,QAAM,WAAW,OAAO,SAAS,IAAI,CAAC,GAAG,MAAM;AAC7C,UAAM,MAAM,YAAY,CAAC;AACzB,QACE,OACA,OAAO,QAAQ,YACf,WAAW,OACX,aAAa,IAAI,KAAK,GACtB;AACA,aAAO,EAAE,GAAG,GAAG,OAAO,IAAI,MAAA;AAAA,IAC5B;AACA,WAAO;AAAA,EACT,CAAC;AAED,SAAO,QAAQ,QAAQ;AAAA,IACrB;AAAA,IACA,UAAU,OAAO;AAAA,IACjB,OAAO,OAAO;AAAA,IACd,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IAKd,gBAAiB,OAAO,kBAAkB,CAAA;AAAA,IAC1C,OAAO,OAAO;AAAA,IACd,SAAS;AAAA,IACT;AAAA,EAAA,CACD;AACH;AA2BA,eAAsB,sBACpB,KACgE;AAChE,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,IAAI,KAAA;AAAA,EACnB,SAAS,OAAO;AAGd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACA,MAAI;AACF,WAAO,MAAM,0BAA0B,IAAI;AAAA,EAC7C,SAAS,OAAO;AAKd,UAAM,MAAM,IAAI;AAAA,MACd;AAAA,MACA,EAAE,QAAQ,IAAA;AAAA,IAAI;AAEd,QAA4B,QAAQ;AACtC,UAAM;AAAA,EACR;AACF;AAuBO,SAAS,gBACd,aACA,aAKyD;AACzD,QAAM,OAAO,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,SAAkE;AAAA,IACtE,GAAG;AAAA,EAAA;AAEL,aAAW,MAAM,aAAa;AAC5B,QAAI,KAAK,IAAI,GAAG,IAAI,GAAG;AAErB;AAAA,IACF;AACA,SAAK,IAAI,GAAG,IAAI;AAChB,WAAO,KAAK;AAAA,MACV,MAAM,GAAG;AAAA,MACT,aAAa,GAAG;AAAA,MAChB,aAAa,GAAG;AAAA;AAAA;AAAA,IAAA,CAGmC;AAAA,EACvD;AACA,SAAO;AACT;"}
@@ -0,0 +1,21 @@
1
+ import { ContentPart } from '../types.js';
2
+ /**
3
+ * Structural check for a single `ContentPart`. A text part must carry a string
4
+ * `content`; every other modality must carry a `source` with `type` of
5
+ * `'url' | 'data'` and a string `value`.
6
+ */
7
+ export declare function isContentPart(value: unknown): value is ContentPart;
8
+ /**
9
+ * True iff `value` is a NON-EMPTY array whose every element is a valid
10
+ * `ContentPart`. Empty arrays and mixed arrays return false so they continue
11
+ * to be treated as ordinary (stringified) data — this keeps the auto-detection
12
+ * footgun narrow.
13
+ */
14
+ export declare function isContentPartArray(value: unknown): value is Array<ContentPart>;
15
+ /**
16
+ * Normalize a tool's return value for transport:
17
+ * - string → unchanged
18
+ * - ContentPart array → unchanged (multimodal, passed through to the adapter)
19
+ * - anything else → `JSON.stringify`
20
+ */
21
+ export declare function normalizeToolResult(result: unknown): string | Array<ContentPart>;
@@ -0,0 +1,37 @@
1
+ const CONTENT_PART_TYPES = /* @__PURE__ */ new Set([
2
+ "text",
3
+ "image",
4
+ "audio",
5
+ "video",
6
+ "document"
7
+ ]);
8
+ function isContentPart(value) {
9
+ if (typeof value !== "object" || value === null) return false;
10
+ const part = value;
11
+ if (typeof part.type !== "string" || !CONTENT_PART_TYPES.has(part.type)) {
12
+ return false;
13
+ }
14
+ if (part.type === "text") {
15
+ return typeof part.content === "string";
16
+ }
17
+ const source = part.source;
18
+ if (typeof source !== "object" || source === null) return false;
19
+ const src = source;
20
+ if (typeof src.value !== "string") return false;
21
+ if (src.type === "data") return typeof src.mimeType === "string";
22
+ return src.type === "url";
23
+ }
24
+ function isContentPartArray(value) {
25
+ return Array.isArray(value) && value.length > 0 && value.every(isContentPart);
26
+ }
27
+ function normalizeToolResult(result) {
28
+ if (typeof result === "string") return result;
29
+ if (isContentPartArray(result)) return result;
30
+ return JSON.stringify(result);
31
+ }
32
+ export {
33
+ isContentPart,
34
+ isContentPartArray,
35
+ normalizeToolResult
36
+ };
37
+ //# sourceMappingURL=tool-result.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-result.js","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 modality must carry a `source` with `type` of\n * `'url' | 'data'` and a string `value`.\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 // `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 * 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"],"names":[],"mappings":"AAEA,MAAM,yCAAyB,IAAI;AAAA,EACjC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAOM,SAAS,cAAc,OAAsC;AAClE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,OAAO;AACb,MAAI,OAAO,KAAK,SAAS,YAAY,CAAC,mBAAmB,IAAI,KAAK,IAAI,GAAG;AACvE,WAAO;AAAA,EACT;AACA,MAAI,KAAK,SAAS,QAAQ;AACxB,WAAO,OAAO,KAAK,YAAY;AAAA,EACjC;AACA,QAAM,SAAS,KAAK;AACpB,MAAI,OAAO,WAAW,YAAY,WAAW,KAAM,QAAO;AAC1D,QAAM,MAAM;AACZ,MAAI,OAAO,IAAI,UAAU,SAAU,QAAO;AAI1C,MAAI,IAAI,SAAS,OAAQ,QAAO,OAAO,IAAI,aAAa;AACxD,SAAO,IAAI,SAAS;AACtB;AAQO,SAAS,mBACd,OAC6B;AAC7B,SAAO,MAAM,QAAQ,KAAK,KAAK,MAAM,SAAS,KAAK,MAAM,MAAM,aAAa;AAC9E;AAQO,SAAS,oBACd,QAC6B;AAC7B,MAAI,OAAO,WAAW,SAAU,QAAO;AACvC,MAAI,mBAAmB,MAAM,EAAG,QAAO;AACvC,SAAO,KAAK,UAAU,MAAM;AAC9B;"}
@@ -0,0 +1,31 @@
1
+ import { ProviderUsageDetails, TokenUsage } from '../types.js';
2
+ /**
3
+ * Input parameters for building base TokenUsage.
4
+ * Provider functions should extract these from their SDK's response.
5
+ */
6
+ export interface BaseUsageInput {
7
+ /** Total input/prompt tokens */
8
+ promptTokens: number;
9
+ /** Total output/completion tokens */
10
+ completionTokens: number;
11
+ /** Total tokens (prompt + completion) */
12
+ totalTokens: number;
13
+ }
14
+ /**
15
+ * Builds the base TokenUsage object with core fields.
16
+ * Provider-specific functions should use this and then add their own details.
17
+ *
18
+ * @param input - The base token counts
19
+ * @returns A TokenUsage object with promptTokens, completionTokens, totalTokens
20
+ *
21
+ * @example
22
+ * ```typescript
23
+ * const base = buildBaseUsage({
24
+ * promptTokens: 100,
25
+ * completionTokens: 50,
26
+ * totalTokens: 150
27
+ * });
28
+ * // Returns: { promptTokens: 100, completionTokens: 50, totalTokens: 150 }
29
+ * ```
30
+ */
31
+ export declare function buildBaseUsage<TProviderDetails = ProviderUsageDetails>(input: BaseUsageInput): TokenUsage<TProviderDetails>;
@@ -0,0 +1,11 @@
1
+ function buildBaseUsage(input) {
2
+ return {
3
+ promptTokens: input.promptTokens,
4
+ completionTokens: input.completionTokens,
5
+ totalTokens: input.totalTokens
6
+ };
7
+ }
8
+ export {
9
+ buildBaseUsage
10
+ };
11
+ //# sourceMappingURL=usage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usage.js","sources":["../../../src/utilities/usage.ts"],"sourcesContent":["import type { ProviderUsageDetails, TokenUsage } from '../types'\n\n/**\n * Input parameters for building base TokenUsage.\n * Provider functions should extract these from their SDK's response.\n */\nexport interface BaseUsageInput {\n /** Total input/prompt tokens */\n promptTokens: number\n /** Total output/completion tokens */\n completionTokens: number\n /** Total tokens (prompt + completion) */\n totalTokens: number\n}\n\n/**\n * Builds the base TokenUsage object with core fields.\n * Provider-specific functions should use this and then add their own details.\n *\n * @param input - The base token counts\n * @returns A TokenUsage object with promptTokens, completionTokens, totalTokens\n *\n * @example\n * ```typescript\n * const base = buildBaseUsage({\n * promptTokens: 100,\n * completionTokens: 50,\n * totalTokens: 150\n * });\n * // Returns: { promptTokens: 100, completionTokens: 50, totalTokens: 150 }\n * ```\n */\nexport function buildBaseUsage<TProviderDetails = ProviderUsageDetails>(\n input: BaseUsageInput,\n): TokenUsage<TProviderDetails> {\n return {\n promptTokens: input.promptTokens,\n completionTokens: input.completionTokens,\n totalTokens: input.totalTokens,\n }\n}\n"],"names":[],"mappings":"AAgCO,SAAS,eACd,OAC8B;AAC9B,SAAO;AAAA,IACL,cAAc,MAAM;AAAA,IACpB,kBAAkB,MAAM;AAAA,IACxB,aAAa,MAAM;AAAA,EAAA;AAEvB;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.23.1",
3
+ "version": "0.25.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",
@@ -68,7 +68,7 @@
68
68
  "@ag-ui/core": "^0.0.52",
69
69
  "@standard-schema/spec": "^1.1.0",
70
70
  "partial-json": "^0.1.7",
71
- "@tanstack/ai-event-client": "0.4.2"
71
+ "@tanstack/ai-event-client": "0.5.0"
72
72
  },
73
73
  "peerDependencies": {
74
74
  "@opentelemetry/api": ">=1.9.0"
@@ -4,6 +4,7 @@ import type {
4
4
  Modality,
5
5
  StreamChunk,
6
6
  TextOptions,
7
+ TokenUsage,
7
8
  } from '../../types'
8
9
 
9
10
  /**
@@ -40,6 +41,8 @@ export interface StructuredOutputResult<T = unknown> {
40
41
  data: T
41
42
  /** The raw text response from the model before parsing */
42
43
  rawText: string
44
+ /** Token usage information (if provided by the adapter) */
45
+ usage?: TokenUsage
43
46
  }
44
47
 
45
48
  /**