@tanstack/ai 0.49.1 → 0.50.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/index.js CHANGED
@@ -15,15 +15,15 @@ import { hashSchemaInput, normalizeApprovalSchema } from "./activities/chat/tool
15
15
  import { INTERRUPT_BINDING_METADATA_KEY, InterruptResumeValidationError, interruptItemError, readInterruptBinding, readUnopenedInterruptBinding, validateInterruptResumeBatch, withInterruptBinding, withoutInterruptBinding } from "./interrupt-resume.js";
16
16
  import { INTERRUPT_PAYLOAD_METADATA_KEY, createInterruptBinding, defineInterrupt } from "./interrupt-definition.js";
17
17
  import { INTERRUPT_CONTINUATION_METADATA_KEY, INTERRUPT_CONTINUATION_VERSION, genericInterruptContinuationFromDescriptor, readGenericInterruptContinuation, wrapGenericInterruptContinuation } from "./generic-interrupt-continuation.js";
18
- import { uiMessagesToWire } from "./utilities/ag-ui-wire.js";
19
18
  import { isContentPart, isContentPartArray, normalizeToolResult } from "./utilities/tool-result.js";
20
19
  import { getProviderExecutedMetadata, isProviderExecutedToolCall } from "./utilities/provider-executed.js";
20
+ import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
21
+ import { uiMessagesToWire } from "./utilities/ag-ui-wire.js";
21
22
  import { firstSentence, renderLazyCatalogEntry } from "./activities/chat/tools/lazy-tools.js";
22
23
  import { DISCOVERY_TOOL_NAME } from "./activities/chat/tools/lazy-tool-manager.js";
23
24
  import { DuplicateToolNameError } from "./activities/chat/tools/unique-tool-names.js";
24
25
  import { ToolCallManager } from "./activities/chat/tools/tool-calls.js";
25
26
  import { combineStrategies, maxIterations, untilFinishReason } from "./activities/chat/agent-loop-strategies.js";
26
- import { convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages } from "./activities/chat/messages.js";
27
27
  import { MCPDuplicateToolNameError } from "./activities/chat/mcp/manager.js";
28
28
  import { chat, createChatOptions } from "./activities/chat/index.js";
29
29
  import { createSummarizeOptions, summarize } from "./activities/summarize/index.js";
@@ -250,6 +250,10 @@ export interface ModelMessage<TContent extends string | null | Array<ContentPart
250
250
  content: string;
251
251
  signature?: string;
252
252
  }>;
253
+ /** Error reported by an AG-UI tool message. */
254
+ error?: string;
255
+ /** Optional AG-UI message metadata. TanStack-owned fields live under `tanstack`. */
256
+ metadata?: Record<string, any>;
253
257
  /**
254
258
  * Completed structured output represented by this assistant message.
255
259
  * `content` remains the provider-facing JSON text; this field preserves the
@@ -310,10 +314,14 @@ export interface ToolCallPart<TMetadata = unknown> {
310
314
  }
311
315
  export interface ToolResultPart {
312
316
  type: 'tool-result';
317
+ id?: string;
318
+ name?: string;
313
319
  toolCallId: string;
314
320
  content: string | Array<ContentPart>;
315
321
  state: ToolResultState;
316
322
  error?: string;
323
+ metadata?: Record<string, unknown>;
324
+ createdAt?: Date;
317
325
  }
318
326
  export interface ThinkingPart {
319
327
  type: 'thinking';
@@ -385,9 +393,17 @@ export interface TanStackMessageMetadata {
385
393
  signature?: string;
386
394
  /** Per-tool-call provider metadata keyed by tool call id (e.g. Gemini thoughtSignature). */
387
395
  toolCallMetadata?: Record<string, unknown>;
396
+ toolResult?: {
397
+ id?: string;
398
+ createdAt?: string;
399
+ content?: Array<ContentPart>;
400
+ };
388
401
  structuredOutput?: {
389
402
  status?: 'streaming' | 'complete' | 'error';
403
+ partial?: unknown;
404
+ data?: unknown;
390
405
  raw?: string;
406
+ reasoning?: string;
391
407
  errorMessage?: string;
392
408
  };
393
409
  uiResources?: Array<UIResourcePart>;
@@ -421,6 +437,8 @@ export interface UIMessage<TData = unknown> {
421
437
  role: 'system' | 'user' | 'assistant';
422
438
  parts: Array<MessagePart<TData>>;
423
439
  createdAt?: Date;
440
+ /** Optional AG-UI sender name. Converters preserve it across wire and persist. */
441
+ name?: string;
424
442
  /**
425
443
  * Optional AG-UI metadata bag. TanStack writes the `tanstack` key.
426
444
  * User keys stay at the top.
@@ -1,51 +1,27 @@
1
- import { ContentPart, UIMessage } from '../types.js';
1
+ import { AssistantMessage, ReasoningMessage, SystemMessage, ToolMessage, UserMessage } from '@ag-ui/core';
2
+ import { ModelMessage, UIMessage } from '../types.js';
2
3
  import { MetadataRecord } from './merge-metadata.js';
3
- type AGUITextInputContent = {
4
- type: 'text';
5
- text: string;
6
- };
7
- type AGUIInputContent = AGUITextInputContent | (ContentPart & {
8
- type: 'image' | 'audio' | 'video' | 'document';
9
- });
10
- type AGUIToolCallMirror = {
11
- id: string;
12
- type: 'function';
13
- function: {
14
- name: string;
15
- arguments: string;
16
- };
17
- encryptedValue?: string;
18
- };
19
- type AGUIToolMessage = {
20
- role: 'tool';
21
- id: string;
22
- toolCallId: string;
23
- content: string;
24
- error?: string;
25
- };
26
- type AGUIReasoningMessage = {
27
- role: 'reasoning';
28
- id: string;
29
- content: string;
30
- encryptedValue?: string;
4
+ type WithMetadata<T> = T & {
31
5
  metadata?: MetadataRecord;
32
6
  };
33
- /** Spec AG-UI message. No `parts`, no `createdAt` Date. */
34
- type WireAnchorMessage = {
35
- id: string;
36
- role: UIMessage['role'];
7
+ type WireSystemMessage = WithMetadata<SystemMessage>;
8
+ type WireUserMessage = WithMetadata<UserMessage>;
9
+ type WireAssistantMessage = WithMetadata<AssistantMessage>;
10
+ type WireToolMessage = WithMetadata<ToolMessage & {
37
11
  name?: string;
38
- content?: string | Array<AGUIInputContent>;
39
- toolCalls?: Array<AGUIToolCallMirror>;
40
- metadata?: MetadataRecord;
41
- };
42
- export type WireMessage = WireAnchorMessage | AGUIToolMessage | AGUIReasoningMessage;
12
+ }>;
13
+ type WireReasoningMessage = WithMetadata<ReasoningMessage>;
14
+ export type WireMessage = WireSystemMessage | WireUserMessage | WireAssistantMessage | WireToolMessage | WireReasoningMessage;
43
15
  /**
44
- * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
45
- * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,
46
- * `toolCalls`, `metadata`). Tool results and thinking parts on assistant
47
- * messages are additionally emitted as fan-out `{role:'tool',...}` and
48
- * `{role:'reasoning',...}` entries for strict AG-UI server consumers.
16
+ * Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI
17
+ * `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,
18
+ * `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts
19
+ * on assistant messages are additionally emitted as fan-out
20
+ * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI
21
+ * server consumers. Set `includeSnapshotStructuredOutput` to retain complete
22
+ * structured-output metadata for UI snapshots.
49
23
  */
50
- export declare function uiMessagesToWire(messages: Array<UIMessage>): Array<WireMessage>;
24
+ export declare function uiMessagesToWire(messages: Array<UIMessage | ModelMessage>, options?: {
25
+ includeSnapshotStructuredOutput: boolean;
26
+ }): Array<WireMessage>;
51
27
  export {};
@@ -1,28 +1,74 @@
1
1
  import { tanstackMetadata } from "./merge-metadata.js";
2
+ import { normalizeToolResult } from "./tool-result.js";
3
+ import { coerceCreatedAt, modelMessageToUIMessage } from "../activities/chat/messages.js";
2
4
  //#region src/utilities/ag-ui-wire.ts
5
+ function isRecord(value) {
6
+ return typeof value === "object" && value !== null && !Array.isArray(value);
7
+ }
8
+ function rebuiltToolMetadata(metadata, createdAt, id, content, anchorOwnsUiResources = false) {
9
+ const source = isRecord(metadata) ? metadata : {};
10
+ const tanstack = isRecord(source.tanstack) ? { ...source.tanstack } : {};
11
+ if (anchorOwnsUiResources) delete tanstack.uiResources;
12
+ const date = coerceCreatedAt(createdAt);
13
+ const toolResult = {
14
+ ...id !== void 0 ? { id } : {},
15
+ ...date && { createdAt: date.toISOString() },
16
+ ...Array.isArray(content) && { content }
17
+ };
18
+ const result = {
19
+ ...source,
20
+ tanstack: {
21
+ ...tanstack,
22
+ toolResult
23
+ }
24
+ };
25
+ return Object.keys(result).length ? result : void 0;
26
+ }
3
27
  /**
4
- * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`
5
- * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,
6
- * `toolCalls`, `metadata`). Tool results and thinking parts on assistant
7
- * messages are additionally emitted as fan-out `{role:'tool',...}` and
8
- * `{role:'reasoning',...}` entries for strict AG-UI server consumers.
28
+ * Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI
29
+ * `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,
30
+ * `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts
31
+ * on assistant messages are additionally emitted as fan-out
32
+ * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI
33
+ * server consumers. Set `includeSnapshotStructuredOutput` to retain complete
34
+ * structured-output metadata for UI snapshots.
9
35
  */
10
- function uiMessagesToWire(messages) {
36
+ function uiMessagesToWire(messages, options) {
11
37
  const wire = [];
38
+ const usedWireIds = new Set(messages.flatMap((message) => "id" in message && message.id && message.role !== "tool" ? [message.id] : []));
39
+ const includeSnapshotStructuredOutput = options?.includeSnapshotStructuredOutput ?? false;
40
+ const assistantIds = /* @__PURE__ */ new Set();
41
+ for (const msg of messages) if (msg.role === "assistant" && msg.id !== void 0) assistantIds.add(msg.id);
12
42
  for (const msg of messages) {
13
- const parts = msg.parts ?? [];
43
+ if (!("parts" in msg) && msg.role === "tool" && msg.toolCallId) {
44
+ const id = uniqueToolWireId(toolWireId(msg.id, msg.toolCallId, assistantIds), usedWireIds);
45
+ const metadata = rebuiltToolMetadata(msg.metadata, msg.createdAt, msg.id, msg.content);
46
+ wire.push({
47
+ role: "tool",
48
+ id,
49
+ ...msg.name !== void 0 && { name: msg.name },
50
+ toolCallId: msg.toolCallId,
51
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content),
52
+ ...msg.error !== void 0 && { error: msg.error },
53
+ ...metadata !== void 0 && { metadata }
54
+ });
55
+ continue;
56
+ }
57
+ const uiMessage = "parts" in msg ? msg : modelMessageToUIMessage(msg, msg.id);
58
+ const parts = uiMessage.parts;
59
+ usedWireIds.add(uiMessage.id);
14
60
  if (msg.role === "system") {
15
- wire.push(toAnchor(msg, { content: parts.length > 0 ? collectText(parts) : msg.content ?? "" }, parts));
61
+ wire.push(toAnchor(uiMessage, "system", { content: parts.length > 0 ? collectText(parts) : msg.content ?? "" }, parts, includeSnapshotStructuredOutput));
16
62
  continue;
17
63
  }
18
64
  if (msg.role === "user") {
19
- wire.push(toAnchor(msg, { content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? "" }, parts));
65
+ wire.push(toAnchor(uiMessage, "user", { content: parts.length > 0 ? collectUserContent(parts) : msg.content ?? "" }, parts, includeSnapshotStructuredOutput));
20
66
  continue;
21
67
  }
22
68
  for (const part of parts) if (part.type === "thinking") {
23
69
  const reasoning = {
24
70
  role: "reasoning",
25
- id: deriveReasoningId(msg.id, part),
71
+ id: uniqueWireId(deriveReasoningId(uiMessage.id, part), usedWireIds),
26
72
  content: part.content
27
73
  };
28
74
  if (part.signature) reasoning.encryptedValue = part.signature;
@@ -30,52 +76,96 @@ function uiMessagesToWire(messages) {
30
76
  }
31
77
  const text = collectText(parts);
32
78
  const toolCalls = collectToolCalls(parts);
33
- wire.push(toAnchor(msg, {
79
+ wire.push(toAnchor(uiMessage, "assistant", {
34
80
  ...text !== "" && { content: text },
35
81
  ...toolCalls && { toolCalls }
36
- }, parts));
37
- for (const part of parts) if (part.type === "tool-result") wire.push({
38
- role: "tool",
39
- id: deriveToolMessageId(part.toolCallId),
40
- toolCallId: part.toolCallId,
41
- content: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
42
- ...part.error !== void 0 && { error: part.error }
43
- });
82
+ }, parts, includeSnapshotStructuredOutput));
83
+ const explicitToolResults = new Set(parts.flatMap((part) => part.type === "tool-result" ? [part.toolCallId] : []));
84
+ for (const part of parts) if (part.type === "tool-result") {
85
+ const id = uniqueToolWireId(part.id ?? deriveToolMessageId(part.toolCallId), usedWireIds);
86
+ const metadata = rebuiltToolMetadata(part.metadata, part.createdAt, part.id, part.content, true);
87
+ wire.push({
88
+ role: "tool",
89
+ id,
90
+ toolCallId: part.toolCallId,
91
+ ...part.name !== void 0 && { name: part.name },
92
+ content: typeof part.content === "string" ? part.content : JSON.stringify(part.content),
93
+ ...part.error !== void 0 && { error: part.error },
94
+ ...metadata !== void 0 && { metadata }
95
+ });
96
+ } else if (part.type === "tool-call") {
97
+ const approved = part.approval?.approved;
98
+ if (explicitToolResults.has(part.id) || part.output === void 0 && (part.state !== "approval-responded" || approved === void 0)) continue;
99
+ const result = part.output !== void 0 ? normalizeToolResult(part.output) : JSON.stringify({
100
+ approved,
101
+ ...approved && { pendingExecution: true },
102
+ message: approved ? "User approved this action" : "User denied this action"
103
+ });
104
+ const content = typeof result === "string" ? result : JSON.stringify(result);
105
+ wire.push({
106
+ role: "tool",
107
+ id: uniqueToolWireId(deriveToolMessageId(part.id), usedWireIds),
108
+ toolCallId: part.id,
109
+ content,
110
+ metadata: rebuiltToolMetadata(void 0, void 0, void 0, result)
111
+ });
112
+ }
44
113
  }
45
114
  return wire;
46
115
  }
47
- function toAnchor(msg, extras, parts) {
48
- const metadata = messageMetadata(msg, parts);
49
- const name = msg.name;
50
- return {
116
+ function toAnchor(msg, role, extras, parts, includeSnapshotStructuredOutput) {
117
+ const metadata = messageMetadata(msg, parts, includeSnapshotStructuredOutput);
118
+ const base = {
51
119
  id: msg.id,
52
- role: msg.role,
53
- ...name !== void 0 && { name },
54
- ...extras,
120
+ ...msg.name !== void 0 && { name: msg.name },
55
121
  ...metadata !== void 0 && { metadata }
56
122
  };
123
+ if (role === "system") return {
124
+ ...base,
125
+ role,
126
+ content: String(extras.content ?? "")
127
+ };
128
+ if (role === "user") return {
129
+ ...base,
130
+ role,
131
+ content: extras.content ?? ""
132
+ };
133
+ return {
134
+ ...base,
135
+ role,
136
+ ...typeof extras.content === "string" && { content: extras.content },
137
+ ...extras.toolCalls !== void 0 && { toolCalls: extras.toolCalls }
138
+ };
57
139
  }
58
- function messageMetadata(msg, parts) {
140
+ function messageMetadata(msg, parts, includeSnapshotStructuredOutput) {
59
141
  const base = { ...msg.metadata ?? {} };
60
- const tanstack = { ...tanstackMetadata(msg) ?? {} };
61
- if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString();
62
- const leftover = unfinishedStructuredOutput(parts);
63
- if (leftover) tanstack.structuredOutput = leftover;
142
+ const previousTanstack = tanstackMetadata(msg);
143
+ const tanstack = {};
144
+ if (previousTanstack?.model !== void 0) tanstack.model = previousTanstack.model;
145
+ if (previousTanstack?.signature !== void 0) tanstack.signature = previousTanstack.signature;
146
+ const createdAt = coerceCreatedAt(msg.createdAt);
147
+ if (createdAt !== void 0) tanstack.createdAt = createdAt.toISOString();
148
+ const structuredOutput = serializedStructuredOutput(parts, includeSnapshotStructuredOutput);
149
+ if (structuredOutput) tanstack.structuredOutput = structuredOutput;
64
150
  const toolCallMetadata = {};
65
151
  for (const part of parts) if (part.type === "tool-call" && part.metadata !== void 0) toolCallMetadata[part.id] = part.metadata;
66
152
  if (Object.keys(toolCallMetadata).length > 0) tanstack.toolCallMetadata = toolCallMetadata;
67
153
  const uiResources = parts.filter((p) => p.type === "ui-resource");
68
154
  if (uiResources.length > 0) tanstack.uiResources = uiResources;
69
155
  if (Object.keys(tanstack).length > 0) base.tanstack = tanstack;
156
+ else delete base.tanstack;
70
157
  return Object.keys(base).length > 0 ? base : void 0;
71
158
  }
72
- function unfinishedStructuredOutput(parts) {
73
- for (const p of parts) if (p.type === "structured-output" && p.status !== "complete") return structuredOutputLeftover(p);
159
+ function serializedStructuredOutput(parts, includeSnapshotStructuredOutput) {
160
+ for (const p of parts) if (p.type === "structured-output" && (includeSnapshotStructuredOutput || p.status !== "complete")) return structuredOutputMetadata(p, includeSnapshotStructuredOutput);
74
161
  }
75
- function structuredOutputLeftover(part) {
162
+ function structuredOutputMetadata(part, includeSnapshotStructuredOutput) {
76
163
  return {
77
164
  status: part.status,
78
165
  raw: part.raw,
166
+ ...includeSnapshotStructuredOutput && part.partial !== void 0 ? { partial: part.partial } : {},
167
+ ...includeSnapshotStructuredOutput && part.data !== void 0 ? { data: part.data } : {},
168
+ ...includeSnapshotStructuredOutput && part.reasoning ? { reasoning: part.reasoning } : {},
79
169
  ...part.errorMessage !== void 0 && { errorMessage: part.errorMessage }
80
170
  };
81
171
  }
@@ -123,6 +213,25 @@ function deriveReasoningId(messageId, part) {
123
213
  function deriveToolMessageId(toolCallId) {
124
214
  return `tool-${toolCallId}`;
125
215
  }
216
+ function uniqueToolWireId(id, used) {
217
+ return uniqueWireId(id, used);
218
+ }
219
+ function uniqueWireId(id, used) {
220
+ if (!used.has(id)) {
221
+ used.add(id);
222
+ return id;
223
+ }
224
+ let suffix = 2;
225
+ while (used.has(`${id}-${suffix}`)) suffix++;
226
+ const unique = `${id}-${suffix}`;
227
+ used.add(unique);
228
+ return unique;
229
+ }
230
+ function toolWireId(id, toolCallId, assistantIds) {
231
+ const derived = deriveToolMessageId(toolCallId);
232
+ if (id === void 0 || assistantIds.has(id)) return derived;
233
+ return id;
234
+ }
126
235
  function hashContent(s) {
127
236
  let h = 0;
128
237
  for (let i = 0; i < s.length; i++) h = h * 31 + s.charCodeAt(i) | 0;
@@ -1 +1 @@
1
- {"version":3,"file":"ag-ui-wire.js","names":[],"sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type {\n ContentPart,\n MessagePart,\n StructuredOutputPart,\n TanStackMessageMetadata,\n UIMessage,\n UIResourcePart,\n} from '../types'\nimport type { MetadataRecord } from './merge-metadata'\nimport { tanstackMetadata } from './merge-metadata'\n\ntype AGUITextInputContent = { type: 'text'; text: string }\ntype AGUIInputContent =\n | AGUITextInputContent\n | (ContentPart & { type: 'image' | 'audio' | 'video' | 'document' })\n\ntype AGUIToolCallMirror = {\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n encryptedValue?: string\n}\n\ntype AGUIToolMessage = {\n role: 'tool'\n id: string\n toolCallId: string\n content: string\n error?: string\n}\n\ntype AGUIReasoningMessage = {\n role: 'reasoning'\n id: string\n content: string\n encryptedValue?: string\n metadata?: MetadataRecord\n}\n\n/** Spec AG-UI message. No `parts`, no `createdAt` Date. */\ntype WireAnchorMessage = {\n id: string\n role: UIMessage['role']\n name?: string\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n metadata?: MetadataRecord\n}\n\nexport type WireMessage =\n | WireAnchorMessage\n | AGUIToolMessage\n | AGUIReasoningMessage\n\n/**\n * Serialize TanStack `UIMessage`s into the AG-UI `RunAgentInput.messages`\n * wire shape. Anchors are spec-only (`id`, `role`, `name`, `content`,\n * `toolCalls`, `metadata`). Tool results and thinking parts on assistant\n * messages are additionally emitted as fan-out `{role:'tool',...}` and\n * `{role:'reasoning',...}` entries for strict AG-UI server consumers.\n */\nexport function uiMessagesToWire(\n messages: Array<UIMessage>,\n): Array<WireMessage> {\n const wire: Array<WireMessage> = []\n\n for (const msg of messages) {\n // Defensive: ModelMessage-shaped input has no `parts`; fall back to `content`.\n const parts: ReadonlyArray<MessagePart> =\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- runtime input may be ModelMessage-shaped (no `parts`); cast forces the optional-chain fallback below to remain in scope\n (msg.parts as ReadonlyArray<MessagePart> | undefined) ?? []\n\n if (msg.role === 'system') {\n wire.push(\n toAnchor(\n msg,\n {\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n ),\n )\n continue\n }\n\n if (msg.role === 'user') {\n wire.push(\n toAnchor(\n msg,\n {\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n ),\n )\n continue\n }\n\n // assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs\n for (const part of parts) {\n if (part.type === 'thinking') {\n const reasoning: AGUIReasoningMessage = {\n role: 'reasoning',\n id: deriveReasoningId(msg.id, part),\n content: part.content,\n }\n if (part.signature) {\n reasoning.encryptedValue = part.signature\n }\n wire.push(reasoning)\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push(\n toAnchor(\n msg,\n {\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\n },\n parts,\n ),\n )\n\n for (const part of parts) {\n if (part.type === 'tool-result') {\n wire.push({\n role: 'tool',\n id: deriveToolMessageId(part.toolCallId),\n toolCallId: part.toolCallId,\n content:\n typeof part.content === 'string'\n ? part.content\n : JSON.stringify(part.content),\n ...(part.error !== undefined && { error: part.error }),\n })\n }\n }\n }\n\n return wire\n}\n\nfunction toAnchor(\n msg: UIMessage,\n extras: {\n content?: string | Array<AGUIInputContent>\n toolCalls?: Array<AGUIToolCallMirror>\n },\n parts: ReadonlyArray<MessagePart>,\n): WireAnchorMessage {\n const metadata = messageMetadata(msg, parts)\n const name = (msg as { name?: string }).name\n return {\n id: msg.id,\n role: msg.role,\n ...(name !== undefined && { name }),\n ...extras,\n ...(metadata !== undefined && { metadata }),\n }\n}\n\nfunction messageMetadata(\n msg: UIMessage,\n parts: ReadonlyArray<MessagePart>,\n): MetadataRecord | undefined {\n const base: MetadataRecord = { ...(msg.metadata ?? {}) }\n const tanstack: MetadataRecord = { ...(tanstackMetadata(msg) ?? {}) }\n if (msg.createdAt) tanstack.createdAt = msg.createdAt.toISOString()\n\n const leftover = unfinishedStructuredOutput(parts)\n if (leftover) tanstack.structuredOutput = leftover\n\n const toolCallMetadata: Record<string, unknown> = {}\n for (const part of parts) {\n if (part.type === 'tool-call' && part.metadata !== undefined) {\n toolCallMetadata[part.id] = part.metadata\n }\n }\n if (Object.keys(toolCallMetadata).length > 0) {\n tanstack.toolCallMetadata = toolCallMetadata\n }\n\n const uiResources = parts.filter(\n (p): p is UIResourcePart => p.type === 'ui-resource',\n )\n if (uiResources.length > 0) tanstack.uiResources = uiResources\n\n if (Object.keys(tanstack).length > 0) base.tanstack = tanstack\n return Object.keys(base).length > 0 ? base : undefined\n}\n\nfunction unfinishedStructuredOutput(\n parts: ReadonlyArray<MessagePart>,\n): TanStackMessageMetadata['structuredOutput'] | undefined {\n for (const p of parts) {\n if (p.type === 'structured-output' && p.status !== 'complete') {\n return structuredOutputLeftover(p)\n }\n }\n return undefined\n}\n\nfunction structuredOutputLeftover(\n part: StructuredOutputPart,\n): NonNullable<TanStackMessageMetadata['structuredOutput']> {\n return {\n status: part.status,\n raw: part.raw,\n ...(part.errorMessage !== undefined && { errorMessage: part.errorMessage }),\n }\n}\n\nfunction collectText(parts: ReadonlyArray<MessagePart>): string {\n // The streamed JSON of a completed structured-output part is the source of\n // truth for multi-turn coherence — emitting it back as assistant content\n // lets the LLM see its own prior structured response. Streaming/errored\n // parts are skipped: they'd ship malformed JSON fragments and confuse the\n // model. `completeStructuredOutputPart` tries hard to populate `raw`\n // (caller → existing buffer → `JSON.stringify(data)`), but the stringify\n // fallback can leave it empty when `data` is unserializable (BigInt,\n // circular). The `p.raw !== ''` guard below is what enforces \"no malformed\n // round-trip\" in that case — without it we'd ship `''` and the model would\n // see an empty assistant turn.\n const out: Array<string> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push(p.content)\n } else if (\n p.type === 'structured-output' &&\n p.status === 'complete' &&\n p.raw !== ''\n ) {\n out.push(p.raw)\n }\n }\n return out.join('')\n}\n\nfunction collectUserContent(\n parts: ReadonlyArray<MessagePart>,\n): string | Array<AGUIInputContent> {\n const hasMultimodal = parts.some(\n (p) =>\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document',\n )\n if (!hasMultimodal) {\n return collectText(parts)\n }\n const out: Array<AGUIInputContent> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push({ type: 'text', text: p.content })\n } else if (\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document'\n ) {\n out.push(p)\n }\n }\n return out\n}\n\nfunction thoughtSignatureFromMetadata(metadata: unknown): string | undefined {\n if (\n metadata == null ||\n typeof metadata !== 'object' ||\n Array.isArray(metadata)\n ) {\n return undefined\n }\n if (!('thoughtSignature' in metadata)) return undefined\n const value = metadata.thoughtSignature\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nfunction collectToolCalls(\n parts: ReadonlyArray<MessagePart>,\n): Array<AGUIToolCallMirror> | undefined {\n const calls: Array<AGUIToolCallMirror> = []\n for (const p of parts) {\n if (p.type === 'tool-call') {\n const encryptedValue = thoughtSignatureFromMetadata(p.metadata)\n calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\n ...(encryptedValue !== undefined ? { encryptedValue } : {}),\n })\n }\n }\n return calls.length > 0 ? calls : undefined\n}\n\nfunction deriveReasoningId(messageId: string, part: MessagePart): string {\n return `${messageId}-reasoning-${(part as { id?: string }).id ?? hashContent((part as { content: string }).content)}`\n}\n\nfunction deriveToolMessageId(toolCallId: string): string {\n return `tool-${toolCallId}`\n}\n\nfunction hashContent(s: string): string {\n // Cheap deterministic id suffix; collisions are tolerable since\n // reasoning ids only matter for AG-UI server consumers, not for our\n // own server's dedup logic (which keys on toolCallId, not reasoning id).\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0\n return Math.abs(h).toString(36)\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ag-ui-wire.js","names":[],"sources":["../../../src/utilities/ag-ui-wire.ts"],"sourcesContent":["import type {\n AssistantMessage,\n InputContent,\n ReasoningMessage,\n SystemMessage,\n ToolCall,\n ToolMessage,\n UserMessage,\n} from '@ag-ui/core'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StructuredOutputPart,\n TanStackMessageMetadata,\n UIMessage,\n UIResourcePart,\n} from '../types'\nimport type { MetadataRecord } from './merge-metadata'\nimport { tanstackMetadata } from './merge-metadata'\nimport { normalizeToolResult } from './tool-result'\nimport {\n coerceCreatedAt,\n modelMessageToUIMessage,\n} from '../activities/chat/messages'\n\ntype WithMetadata<T> = T & { metadata?: MetadataRecord }\ntype WireSystemMessage = WithMetadata<SystemMessage>\ntype WireUserMessage = WithMetadata<UserMessage>\ntype WireAssistantMessage = WithMetadata<AssistantMessage>\ntype WireToolMessage = WithMetadata<\n ToolMessage & {\n name?: string\n }\n>\ntype WireReasoningMessage = WithMetadata<ReasoningMessage>\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction rebuiltToolMetadata(\n metadata: unknown,\n createdAt: unknown,\n id: string | undefined,\n content: string | null | Array<ContentPart>,\n anchorOwnsUiResources = false,\n): MetadataRecord | undefined {\n const source: MetadataRecord = isRecord(metadata) ? metadata : {}\n const tanstack = isRecord(source.tanstack) ? { ...source.tanstack } : {}\n if (anchorOwnsUiResources) delete tanstack.uiResources\n const date = coerceCreatedAt(createdAt)\n const toolResult: NonNullable<TanStackMessageMetadata['toolResult']> = {\n ...(id !== undefined ? { id } : {}),\n ...(date && { createdAt: date.toISOString() }),\n ...(Array.isArray(content) && { content }),\n }\n const result = {\n ...source,\n tanstack: { ...tanstack, toolResult },\n }\n return Object.keys(result).length ? result : undefined\n}\n\nexport type WireMessage =\n | WireSystemMessage\n | WireUserMessage\n | WireAssistantMessage\n | WireToolMessage\n | WireReasoningMessage\n\n/**\n * Serialize TanStack `UIMessage`s and `ModelMessage`s into the AG-UI\n * `RunAgentInput.messages` wire shape. Anchors are spec-only (`id`, `role`,\n * `name`, `content`, `toolCalls`, `metadata`). Tool results and thinking parts\n * on assistant messages are additionally emitted as fan-out\n * `{role:'tool',...}` and `{role:'reasoning',...}` entries for strict AG-UI\n * server consumers. Set `includeSnapshotStructuredOutput` to retain complete\n * structured-output metadata for UI snapshots.\n */\nexport function uiMessagesToWire(\n messages: Array<UIMessage | ModelMessage>,\n options?: { includeSnapshotStructuredOutput: boolean },\n): Array<WireMessage> {\n const wire: Array<WireMessage> = []\n const usedWireIds = new Set<string>(\n messages.flatMap((message) =>\n 'id' in message && message.id && message.role !== 'tool'\n ? [message.id]\n : [],\n ),\n )\n const includeSnapshotStructuredOutput =\n options?.includeSnapshotStructuredOutput ?? false\n\n const assistantIds = new Set<string>()\n for (const msg of messages) {\n if (msg.role === 'assistant' && msg.id !== undefined) {\n assistantIds.add(msg.id)\n }\n }\n\n for (const msg of messages) {\n if (!('parts' in msg) && msg.role === 'tool' && msg.toolCallId) {\n const id = uniqueToolWireId(\n toolWireId(msg.id, msg.toolCallId, assistantIds),\n usedWireIds,\n )\n const metadata = rebuiltToolMetadata(\n msg.metadata,\n msg.createdAt,\n msg.id,\n msg.content,\n )\n wire.push({\n role: 'tool',\n id,\n ...(msg.name !== undefined && { name: msg.name }),\n toolCallId: msg.toolCallId,\n content:\n typeof msg.content === 'string'\n ? msg.content\n : JSON.stringify(msg.content),\n ...(msg.error !== undefined && { error: msg.error }),\n ...(metadata !== undefined && { metadata }),\n })\n continue\n }\n\n const uiMessage: UIMessage =\n 'parts' in msg ? msg : modelMessageToUIMessage(msg, msg.id)\n const parts: ReadonlyArray<MessagePart> = uiMessage.parts\n usedWireIds.add(uiMessage.id)\n\n if (msg.role === 'system') {\n wire.push(\n toAnchor(\n uiMessage,\n 'system',\n {\n content:\n parts.length > 0\n ? collectText(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n includeSnapshotStructuredOutput,\n ),\n )\n continue\n }\n\n if (msg.role === 'user') {\n wire.push(\n toAnchor(\n uiMessage,\n 'user',\n {\n content:\n parts.length > 0\n ? collectUserContent(parts)\n : ((msg as { content?: string }).content ?? ''),\n },\n parts,\n includeSnapshotStructuredOutput,\n ),\n )\n continue\n }\n\n // assistant: emit reasoning fan-outs first, then anchor, then tool fan-outs\n for (const part of parts) {\n if (part.type === 'thinking') {\n const reasoning: WireReasoningMessage = {\n role: 'reasoning',\n id: uniqueWireId(deriveReasoningId(uiMessage.id, part), usedWireIds),\n content: part.content,\n }\n if (part.signature) {\n reasoning.encryptedValue = part.signature\n }\n wire.push(reasoning)\n }\n }\n\n const text = collectText(parts)\n const toolCalls = collectToolCalls(parts)\n wire.push(\n toAnchor(\n uiMessage,\n 'assistant',\n {\n ...(text !== '' && { content: text }),\n ...(toolCalls && { toolCalls }),\n },\n parts,\n includeSnapshotStructuredOutput,\n ),\n )\n\n const explicitToolResults = new Set(\n parts.flatMap((part) =>\n part.type === 'tool-result' ? [part.toolCallId] : [],\n ),\n )\n for (const part of parts) {\n if (part.type === 'tool-result') {\n const id = uniqueToolWireId(\n part.id ?? deriveToolMessageId(part.toolCallId),\n usedWireIds,\n )\n const metadata = rebuiltToolMetadata(\n part.metadata,\n part.createdAt,\n part.id,\n part.content,\n true,\n )\n wire.push({\n role: 'tool',\n id,\n toolCallId: part.toolCallId,\n ...(part.name !== undefined && { name: part.name }),\n content:\n typeof part.content === 'string'\n ? part.content\n : JSON.stringify(part.content),\n ...(part.error !== undefined && { error: part.error }),\n ...(metadata !== undefined && { metadata }),\n })\n } else if (part.type === 'tool-call') {\n const approved = part.approval?.approved\n if (\n explicitToolResults.has(part.id) ||\n (part.output === undefined &&\n (part.state !== 'approval-responded' || approved === undefined))\n ) {\n continue\n }\n const result =\n part.output !== undefined\n ? normalizeToolResult(part.output)\n : JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n })\n const content =\n typeof result === 'string' ? result : JSON.stringify(result)\n wire.push({\n role: 'tool',\n id: uniqueToolWireId(deriveToolMessageId(part.id), usedWireIds),\n toolCallId: part.id,\n content,\n metadata: rebuiltToolMetadata(\n undefined,\n undefined,\n undefined,\n result,\n ),\n })\n }\n }\n }\n\n return wire\n}\n\nfunction toAnchor(\n msg: UIMessage,\n role: 'system',\n extras: { content: string },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireSystemMessage\nfunction toAnchor(\n msg: UIMessage,\n role: 'user',\n extras: { content: string | Array<InputContent> },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireUserMessage\nfunction toAnchor(\n msg: UIMessage,\n role: 'assistant',\n extras: {\n content?: string\n toolCalls?: Array<ToolCall>\n },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireAssistantMessage\nfunction toAnchor(\n msg: UIMessage,\n role: UIMessage['role'],\n extras: {\n content?: string | Array<InputContent>\n toolCalls?: Array<ToolCall>\n },\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): WireSystemMessage | WireUserMessage | WireAssistantMessage {\n const metadata = messageMetadata(msg, parts, includeSnapshotStructuredOutput)\n const base = {\n id: msg.id,\n ...(msg.name !== undefined && { name: msg.name }),\n ...(metadata !== undefined && { metadata }),\n }\n if (role === 'system') {\n return { ...base, role, content: String(extras.content ?? '') }\n }\n if (role === 'user') {\n return { ...base, role, content: extras.content ?? '' }\n }\n return {\n ...base,\n role,\n ...(typeof extras.content === 'string' && { content: extras.content }),\n ...(extras.toolCalls !== undefined && { toolCalls: extras.toolCalls }),\n }\n}\n\nfunction messageMetadata(\n msg: UIMessage,\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): MetadataRecord | undefined {\n const base: MetadataRecord = { ...(msg.metadata ?? {}) }\n const previousTanstack = tanstackMetadata(msg)\n const tanstack: TanStackMessageMetadata = {}\n if (previousTanstack?.model !== undefined)\n tanstack.model = previousTanstack.model\n if (previousTanstack?.signature !== undefined)\n tanstack.signature = previousTanstack.signature\n const createdAt = coerceCreatedAt(msg.createdAt)\n if (createdAt !== undefined) tanstack.createdAt = createdAt.toISOString()\n\n const structuredOutput = serializedStructuredOutput(\n parts,\n includeSnapshotStructuredOutput,\n )\n if (structuredOutput) tanstack.structuredOutput = structuredOutput\n\n const toolCallMetadata: Record<string, unknown> = {}\n for (const part of parts) {\n if (part.type === 'tool-call' && part.metadata !== undefined) {\n toolCallMetadata[part.id] = part.metadata\n }\n }\n if (Object.keys(toolCallMetadata).length > 0) {\n tanstack.toolCallMetadata = toolCallMetadata\n }\n\n const uiResources = parts.filter(\n (p): p is UIResourcePart => p.type === 'ui-resource',\n )\n if (uiResources.length > 0) tanstack.uiResources = uiResources\n\n if (Object.keys(tanstack).length > 0) base.tanstack = tanstack\n else delete base.tanstack\n return Object.keys(base).length > 0 ? base : undefined\n}\n\nfunction serializedStructuredOutput(\n parts: ReadonlyArray<MessagePart>,\n includeSnapshotStructuredOutput: boolean,\n): TanStackMessageMetadata['structuredOutput'] | undefined {\n for (const p of parts) {\n if (\n p.type === 'structured-output' &&\n (includeSnapshotStructuredOutput || p.status !== 'complete')\n ) {\n return structuredOutputMetadata(p, includeSnapshotStructuredOutput)\n }\n }\n return undefined\n}\n\nfunction structuredOutputMetadata(\n part: StructuredOutputPart,\n includeSnapshotStructuredOutput: boolean,\n): NonNullable<TanStackMessageMetadata['structuredOutput']> {\n return {\n status: part.status,\n raw: part.raw,\n ...(includeSnapshotStructuredOutput && part.partial !== undefined\n ? { partial: part.partial }\n : {}),\n ...(includeSnapshotStructuredOutput && part.data !== undefined\n ? { data: part.data }\n : {}),\n ...(includeSnapshotStructuredOutput && part.reasoning\n ? { reasoning: part.reasoning }\n : {}),\n ...(part.errorMessage !== undefined && { errorMessage: part.errorMessage }),\n }\n}\n\nfunction collectText(parts: ReadonlyArray<MessagePart>): string {\n // The streamed JSON of a completed structured-output part is the source of\n // truth for multi-turn coherence — emitting it back as assistant content\n // lets the LLM see its own prior structured response. Streaming/errored\n // parts are skipped: they'd ship malformed JSON fragments and confuse the\n // model. `completeStructuredOutputPart` tries hard to populate `raw`\n // (caller → existing buffer → `JSON.stringify(data)`), but the stringify\n // fallback can leave it empty when `data` is unserializable (BigInt,\n // circular). The `p.raw !== ''` guard below is what enforces \"no malformed\n // round-trip\" in that case — without it we'd ship `''` and the model would\n // see an empty assistant turn.\n const out: Array<string> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push(p.content)\n } else if (\n p.type === 'structured-output' &&\n p.status === 'complete' &&\n p.raw !== ''\n ) {\n out.push(p.raw)\n }\n }\n return out.join('')\n}\n\nfunction collectUserContent(\n parts: ReadonlyArray<MessagePart>,\n): string | Array<InputContent> {\n const hasMultimodal = parts.some(\n (p) =>\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document',\n )\n if (!hasMultimodal) {\n return collectText(parts)\n }\n const out: Array<InputContent> = []\n for (const p of parts) {\n if (p.type === 'text') {\n out.push({ type: 'text', text: p.content })\n } else if (\n p.type === 'image' ||\n p.type === 'audio' ||\n p.type === 'video' ||\n p.type === 'document'\n ) {\n out.push(p)\n }\n }\n return out\n}\n\nfunction thoughtSignatureFromMetadata(metadata: unknown): string | undefined {\n if (\n metadata == null ||\n typeof metadata !== 'object' ||\n Array.isArray(metadata)\n ) {\n return undefined\n }\n if (!('thoughtSignature' in metadata)) return undefined\n const value = metadata.thoughtSignature\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nfunction collectToolCalls(\n parts: ReadonlyArray<MessagePart>,\n): Array<ToolCall> | undefined {\n const calls: Array<ToolCall> = []\n for (const p of parts) {\n if (p.type === 'tool-call') {\n const encryptedValue = thoughtSignatureFromMetadata(p.metadata)\n calls.push({\n id: p.id,\n type: 'function',\n function: { name: p.name, arguments: p.arguments },\n ...(encryptedValue !== undefined ? { encryptedValue } : {}),\n })\n }\n }\n return calls.length > 0 ? calls : undefined\n}\n\nfunction deriveReasoningId(messageId: string, part: MessagePart): string {\n return `${messageId}-reasoning-${(part as { id?: string }).id ?? hashContent((part as { content: string }).content)}`\n}\n\nfunction deriveToolMessageId(toolCallId: string): string {\n return `tool-${toolCallId}`\n}\n\nfunction uniqueToolWireId(id: string, used: Set<string>): string {\n return uniqueWireId(id, used)\n}\n\nfunction uniqueWireId(id: string, used: Set<string>): string {\n if (!used.has(id)) {\n used.add(id)\n return id\n }\n let suffix = 2\n while (used.has(`${id}-${suffix}`)) suffix++\n const unique = `${id}-${suffix}`\n used.add(unique)\n return unique\n}\n\nfunction toolWireId(\n id: string | undefined,\n toolCallId: string,\n assistantIds: ReadonlySet<string>,\n): string {\n const derived = deriveToolMessageId(toolCallId)\n if (id === undefined || assistantIds.has(id)) return derived\n return id\n}\n\nfunction hashContent(s: string): string {\n // Cheap deterministic id suffix; collisions are tolerable since\n // reasoning ids only matter for AG-UI server consumers, not for our\n // own server's dedup logic (which keys on toolCallId, not reasoning id).\n let h = 0\n for (let i = 0; i < s.length; i++) h = (h * 31 + s.charCodeAt(i)) | 0\n return Math.abs(h).toString(36)\n}\n"],"mappings":";;;;AAqCA,SAAS,SAAS,OAAkD;CAClE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,oBACP,UACA,WACA,IACA,SACA,wBAAwB,OACI;CAC5B,MAAM,SAAyB,SAAS,QAAQ,IAAI,WAAW,CAAC;CAChE,MAAM,WAAW,SAAS,OAAO,QAAQ,IAAI,EAAE,GAAG,OAAO,SAAS,IAAI,CAAC;CACvE,IAAI,uBAAuB,OAAO,SAAS;CAC3C,MAAM,OAAO,gBAAgB,SAAS;CACtC,MAAM,aAAiE;EACrE,GAAI,OAAO,KAAA,IAAY,EAAE,GAAG,IAAI,CAAC;EACjC,GAAI,QAAQ,EAAE,WAAW,KAAK,YAAY,EAAE;EAC5C,GAAI,MAAM,QAAQ,OAAO,KAAK,EAAE,QAAQ;CAC1C;CACA,MAAM,SAAS;EACb,GAAG;EACH,UAAU;GAAE,GAAG;GAAU;EAAW;CACtC;CACA,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,SAAS,KAAA;AAC/C;;;;;;;;;;AAkBA,SAAgB,iBACd,UACA,SACoB;CACpB,MAAM,OAA2B,CAAC;CAClC,MAAM,cAAc,IAAI,IACtB,SAAS,SAAS,YAChB,QAAQ,WAAW,QAAQ,MAAM,QAAQ,SAAS,SAC9C,CAAC,QAAQ,EAAE,IACX,CAAC,CACP,CACF;CACA,MAAM,kCACJ,SAAS,mCAAmC;CAE9C,MAAM,+BAAe,IAAI,IAAY;CACrC,KAAK,MAAM,OAAO,UAChB,IAAI,IAAI,SAAS,eAAe,IAAI,OAAO,KAAA,GACzC,aAAa,IAAI,IAAI,EAAE;CAI3B,KAAK,MAAM,OAAO,UAAU;EAC1B,IAAI,EAAE,WAAW,QAAQ,IAAI,SAAS,UAAU,IAAI,YAAY;GAC9D,MAAM,KAAK,iBACT,WAAW,IAAI,IAAI,IAAI,YAAY,YAAY,GAC/C,WACF;GACA,MAAM,WAAW,oBACf,IAAI,UACJ,IAAI,WACJ,IAAI,IACJ,IAAI,OACN;GACA,KAAK,KAAK;IACR,MAAM;IACN;IACA,GAAI,IAAI,SAAS,KAAA,KAAa,EAAE,MAAM,IAAI,KAAK;IAC/C,YAAY,IAAI;IAChB,SACE,OAAO,IAAI,YAAY,WACnB,IAAI,UACJ,KAAK,UAAU,IAAI,OAAO;IAChC,GAAI,IAAI,UAAU,KAAA,KAAa,EAAE,OAAO,IAAI,MAAM;IAClD,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;GAC3C,CAAC;GACD;EACF;EAEA,MAAM,YACJ,WAAW,MAAM,MAAM,wBAAwB,KAAK,IAAI,EAAE;EAC5D,MAAM,QAAoC,UAAU;EACpD,YAAY,IAAI,UAAU,EAAE;EAE5B,IAAI,IAAI,SAAS,UAAU;GACzB,KAAK,KACH,SACE,WACA,UACA,EACE,SACE,MAAM,SAAS,IACX,YAAY,KAAK,IACf,IAA6B,WAAW,GAClD,GACA,OACA,+BACF,CACF;GACA;EACF;EAEA,IAAI,IAAI,SAAS,QAAQ;GACvB,KAAK,KACH,SACE,WACA,QACA,EACE,SACE,MAAM,SAAS,IACX,mBAAmB,KAAK,IACtB,IAA6B,WAAW,GAClD,GACA,OACA,+BACF,CACF;GACA;EACF;EAGA,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,YAAY;GAC5B,MAAM,YAAkC;IACtC,MAAM;IACN,IAAI,aAAa,kBAAkB,UAAU,IAAI,IAAI,GAAG,WAAW;IACnE,SAAS,KAAK;GAChB;GACA,IAAI,KAAK,WACP,UAAU,iBAAiB,KAAK;GAElC,KAAK,KAAK,SAAS;EACrB;EAGF,MAAM,OAAO,YAAY,KAAK;EAC9B,MAAM,YAAY,iBAAiB,KAAK;EACxC,KAAK,KACH,SACE,WACA,aACA;GACE,GAAI,SAAS,MAAM,EAAE,SAAS,KAAK;GACnC,GAAI,aAAa,EAAE,UAAU;EAC/B,GACA,OACA,+BACF,CACF;EAEA,MAAM,sBAAsB,IAAI,IAC9B,MAAM,SAAS,SACb,KAAK,SAAS,gBAAgB,CAAC,KAAK,UAAU,IAAI,CAAC,CACrD,CACF;EACA,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,eAAe;GAC/B,MAAM,KAAK,iBACT,KAAK,MAAM,oBAAoB,KAAK,UAAU,GAC9C,WACF;GACA,MAAM,WAAW,oBACf,KAAK,UACL,KAAK,WACL,KAAK,IACL,KAAK,SACL,IACF;GACA,KAAK,KAAK;IACR,MAAM;IACN;IACA,YAAY,KAAK;IACjB,GAAI,KAAK,SAAS,KAAA,KAAa,EAAE,MAAM,KAAK,KAAK;IACjD,SACE,OAAO,KAAK,YAAY,WACpB,KAAK,UACL,KAAK,UAAU,KAAK,OAAO;IACjC,GAAI,KAAK,UAAU,KAAA,KAAa,EAAE,OAAO,KAAK,MAAM;IACpD,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;GAC3C,CAAC;EACH,OAAO,IAAI,KAAK,SAAS,aAAa;GACpC,MAAM,WAAW,KAAK,UAAU;GAChC,IACE,oBAAoB,IAAI,KAAK,EAAE,KAC9B,KAAK,WAAW,KAAA,MACd,KAAK,UAAU,wBAAwB,aAAa,KAAA,IAEvD;GAEF,MAAM,SACJ,KAAK,WAAW,KAAA,IACZ,oBAAoB,KAAK,MAAM,IAC/B,KAAK,UAAU;IACb;IACA,GAAI,YAAY,EAAE,kBAAkB,KAAK;IACzC,SAAS,WACL,8BACA;GACN,CAAC;GACP,MAAM,UACJ,OAAO,WAAW,WAAW,SAAS,KAAK,UAAU,MAAM;GAC7D,KAAK,KAAK;IACR,MAAM;IACN,IAAI,iBAAiB,oBAAoB,KAAK,EAAE,GAAG,WAAW;IAC9D,YAAY,KAAK;IACjB;IACA,UAAU,oBACR,KAAA,GACA,KAAA,GACA,KAAA,GACA,MACF;GACF,CAAC;EACH;CAEJ;CAEA,OAAO;AACT;AA0BA,SAAS,SACP,KACA,MACA,QAIA,OACA,iCAC4D;CAC5D,MAAM,WAAW,gBAAgB,KAAK,OAAO,+BAA+B;CAC5E,MAAM,OAAO;EACX,IAAI,IAAI;EACR,GAAI,IAAI,SAAS,KAAA,KAAa,EAAE,MAAM,IAAI,KAAK;EAC/C,GAAI,aAAa,KAAA,KAAa,EAAE,SAAS;CAC3C;CACA,IAAI,SAAS,UACX,OAAO;EAAE,GAAG;EAAM;EAAM,SAAS,OAAO,OAAO,WAAW,EAAE;CAAE;CAEhE,IAAI,SAAS,QACX,OAAO;EAAE,GAAG;EAAM;EAAM,SAAS,OAAO,WAAW;CAAG;CAExD,OAAO;EACL,GAAG;EACH;EACA,GAAI,OAAO,OAAO,YAAY,YAAY,EAAE,SAAS,OAAO,QAAQ;EACpE,GAAI,OAAO,cAAc,KAAA,KAAa,EAAE,WAAW,OAAO,UAAU;CACtE;AACF;AAEA,SAAS,gBACP,KACA,OACA,iCAC4B;CAC5B,MAAM,OAAuB,EAAE,GAAI,IAAI,YAAY,CAAC,EAAG;CACvD,MAAM,mBAAmB,iBAAiB,GAAG;CAC7C,MAAM,WAAoC,CAAC;CAC3C,IAAI,kBAAkB,UAAU,KAAA,GAC9B,SAAS,QAAQ,iBAAiB;CACpC,IAAI,kBAAkB,cAAc,KAAA,GAClC,SAAS,YAAY,iBAAiB;CACxC,MAAM,YAAY,gBAAgB,IAAI,SAAS;CAC/C,IAAI,cAAc,KAAA,GAAW,SAAS,YAAY,UAAU,YAAY;CAExE,MAAM,mBAAmB,2BACvB,OACA,+BACF;CACA,IAAI,kBAAkB,SAAS,mBAAmB;CAElD,MAAM,mBAA4C,CAAC;CACnD,KAAK,MAAM,QAAQ,OACjB,IAAI,KAAK,SAAS,eAAe,KAAK,aAAa,KAAA,GACjD,iBAAiB,KAAK,MAAM,KAAK;CAGrC,IAAI,OAAO,KAAK,gBAAgB,CAAC,CAAC,SAAS,GACzC,SAAS,mBAAmB;CAG9B,MAAM,cAAc,MAAM,QACvB,MAA2B,EAAE,SAAS,aACzC;CACA,IAAI,YAAY,SAAS,GAAG,SAAS,cAAc;CAEnD,IAAI,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,GAAG,KAAK,WAAW;MACjD,OAAO,KAAK;CACjB,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,IAAI,OAAO,KAAA;AAC/C;AAEA,SAAS,2BACP,OACA,iCACyD;CACzD,KAAK,MAAM,KAAK,OACd,IACE,EAAE,SAAS,wBACV,mCAAmC,EAAE,WAAW,aAEjD,OAAO,yBAAyB,GAAG,+BAA+B;AAIxE;AAEA,SAAS,yBACP,MACA,iCAC0D;CAC1D,OAAO;EACL,QAAQ,KAAK;EACb,KAAK,KAAK;EACV,GAAI,mCAAmC,KAAK,YAAY,KAAA,IACpD,EAAE,SAAS,KAAK,QAAQ,IACxB,CAAC;EACL,GAAI,mCAAmC,KAAK,SAAS,KAAA,IACjD,EAAE,MAAM,KAAK,KAAK,IAClB,CAAC;EACL,GAAI,mCAAmC,KAAK,YACxC,EAAE,WAAW,KAAK,UAAU,IAC5B,CAAC;EACL,GAAI,KAAK,iBAAiB,KAAA,KAAa,EAAE,cAAc,KAAK,aAAa;CAC3E;AACF;AAEA,SAAS,YAAY,OAA2C;CAW9D,MAAM,MAAqB,CAAC;CAC5B,KAAK,MAAM,KAAK,OACd,IAAI,EAAE,SAAS,QACb,IAAI,KAAK,EAAE,OAAO;MACb,IACL,EAAE,SAAS,uBACX,EAAE,WAAW,cACb,EAAE,QAAQ,IAEV,IAAI,KAAK,EAAE,GAAG;CAGlB,OAAO,IAAI,KAAK,EAAE;AACpB;AAEA,SAAS,mBACP,OAC8B;CAQ9B,IAAI,CAPkB,MAAM,MACzB,MACC,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,UAEV,GACH,OAAO,YAAY,KAAK;CAE1B,MAAM,MAA2B,CAAC;CAClC,KAAK,MAAM,KAAK,OACd,IAAI,EAAE,SAAS,QACb,IAAI,KAAK;EAAE,MAAM;EAAQ,MAAM,EAAE;CAAQ,CAAC;MACrC,IACL,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,WACX,EAAE,SAAS,YAEX,IAAI,KAAK,CAAC;CAGd,OAAO;AACT;AAEA,SAAS,6BAA6B,UAAuC;CAC3E,IACE,YAAY,QACZ,OAAO,aAAa,YACpB,MAAM,QAAQ,QAAQ,GAEtB;CAEF,IAAI,EAAE,sBAAsB,WAAW,OAAO,KAAA;CAC9C,MAAM,QAAQ,SAAS;CACvB,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,KAAA;AAC7D;AAEA,SAAS,iBACP,OAC6B;CAC7B,MAAM,QAAyB,CAAC;CAChC,KAAK,MAAM,KAAK,OACd,IAAI,EAAE,SAAS,aAAa;EAC1B,MAAM,iBAAiB,6BAA6B,EAAE,QAAQ;EAC9D,MAAM,KAAK;GACT,IAAI,EAAE;GACN,MAAM;GACN,UAAU;IAAE,MAAM,EAAE;IAAM,WAAW,EAAE;GAAU;GACjD,GAAI,mBAAmB,KAAA,IAAY,EAAE,eAAe,IAAI,CAAC;EAC3D,CAAC;CACH;CAEF,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;AACpC;AAEA,SAAS,kBAAkB,WAAmB,MAA2B;CACvE,OAAO,GAAG,UAAU,aAAc,KAAyB,MAAM,YAAa,KAA6B,OAAO;AACpH;AAEA,SAAS,oBAAoB,YAA4B;CACvD,OAAO,QAAQ;AACjB;AAEA,SAAS,iBAAiB,IAAY,MAA2B;CAC/D,OAAO,aAAa,IAAI,IAAI;AAC9B;AAEA,SAAS,aAAa,IAAY,MAA2B;CAC3D,IAAI,CAAC,KAAK,IAAI,EAAE,GAAG;EACjB,KAAK,IAAI,EAAE;EACX,OAAO;CACT;CACA,IAAI,SAAS;CACb,OAAO,KAAK,IAAI,GAAG,GAAG,GAAG,QAAQ,GAAG;CACpC,MAAM,SAAS,GAAG,GAAG,GAAG;CACxB,KAAK,IAAI,MAAM;CACf,OAAO;AACT;AAEA,SAAS,WACP,IACA,YACA,cACQ;CACR,MAAM,UAAU,oBAAoB,UAAU;CAC9C,IAAI,OAAO,KAAA,KAAa,aAAa,IAAI,EAAE,GAAG,OAAO;CACrD,OAAO;AACT;AAEA,SAAS,YAAY,GAAmB;CAItC,IAAI,IAAI;CACR,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAK,IAAI,KAAK,EAAE,WAAW,CAAC,IAAK;CACpE,OAAO,KAAK,IAAI,CAAC,CAAC,CAAC,SAAS,EAAE;AAChC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/ai",
3
- "version": "0.49.1",
3
+ "version": "0.50.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",
@@ -88,7 +88,7 @@
88
88
  "@ag-ui/core": "0.1.1-canary.beta.0",
89
89
  "@standard-schema/spec": "^1.1.0",
90
90
  "partial-json": "^0.1.7",
91
- "@tanstack/ai-event-client": "^0.10.0",
91
+ "@tanstack/ai-event-client": "^0.11.0",
92
92
  "@tanstack/ai-utils": "^0.4.0"
93
93
  },
94
94
  "peerDependencies": {
@@ -103,7 +103,6 @@ import type {
103
103
  Interrupt,
104
104
  JSONSchema,
105
105
  LazyToolsConfig,
106
- MessagesSnapshotEvent,
107
106
  ModelMessage,
108
107
  ProviderTool,
109
108
  RunFinishedEvent,
@@ -2756,9 +2755,9 @@ class TextEngine<
2756
2755
  return {
2757
2756
  type: EventType.MESSAGES_SNAPSHOT,
2758
2757
  timestamp: Date.now(),
2759
- messages: uiMessagesToWire(
2760
- modelMessagesToUIMessages(withIds),
2761
- ) as MessagesSnapshotEvent['messages'],
2758
+ messages: uiMessagesToWire(modelMessagesToUIMessages(withIds), {
2759
+ includeSnapshotStructuredOutput: true,
2760
+ }),
2762
2761
  }
2763
2762
  }
2764
2763