@tanstack/ai 0.47.2 → 0.48.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 (105) hide show
  1. package/README.md +2 -1
  2. package/dist/esm/activities/chat/adapter.d.ts +5 -4
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/index.d.ts +4 -7
  5. package/dist/esm/activities/chat/index.js +201 -243
  6. package/dist/esm/activities/chat/index.js.map +1 -1
  7. package/dist/esm/activities/chat/messages.js +126 -31
  8. package/dist/esm/activities/chat/messages.js.map +1 -1
  9. package/dist/esm/activities/chat/stream/processor.d.ts +38 -17
  10. package/dist/esm/activities/chat/stream/processor.js +197 -110
  11. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  12. package/dist/esm/activities/chat/tools/tool-calls.d.ts +3 -2
  13. package/dist/esm/activities/chat/tools/tool-calls.js +15 -10
  14. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  15. package/dist/esm/activities/generateVideo/index.js +6 -6
  16. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  17. package/dist/esm/activities/stream-generation-result.js +7 -8
  18. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  19. package/dist/esm/activities/summarize/chat-stream-summarize.d.ts +2 -1
  20. package/dist/esm/activities/summarize/chat-stream-summarize.js +59 -54
  21. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  22. package/dist/esm/adapter-internals.d.ts +2 -0
  23. package/dist/esm/adapter-internals.js +3 -1
  24. package/dist/esm/client.d.ts +8 -1
  25. package/dist/esm/client.js +7 -2
  26. package/dist/esm/client.js.map +1 -1
  27. package/dist/esm/index.d.ts +6 -0
  28. package/dist/esm/index.js +6 -2
  29. package/dist/esm/middlewares/otel.js +12 -5
  30. package/dist/esm/middlewares/otel.js.map +1 -1
  31. package/dist/esm/stream-to-response.js +13 -6
  32. package/dist/esm/stream-to-response.js.map +1 -1
  33. package/dist/esm/stream-to-websocket.js +4 -2
  34. package/dist/esm/stream-to-websocket.js.map +1 -1
  35. package/dist/esm/strip-to-spec-middleware.d.ts +10 -13
  36. package/dist/esm/strip-to-spec-middleware.js +24 -22
  37. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  38. package/dist/esm/types.d.ts +82 -305
  39. package/dist/esm/utilities/adapter-yield-chunk.d.ts +31 -0
  40. package/dist/esm/utilities/ag-ui-usage.d.ts +24 -0
  41. package/dist/esm/utilities/ag-ui-usage.js +66 -0
  42. package/dist/esm/utilities/ag-ui-usage.js.map +1 -0
  43. package/dist/esm/utilities/ag-ui-wire.d.ts +14 -7
  44. package/dist/esm/utilities/ag-ui-wire.js +71 -30
  45. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  46. package/dist/esm/utilities/chat-params.d.ts +3 -3
  47. package/dist/esm/utilities/chat-params.js +10 -30
  48. package/dist/esm/utilities/chat-params.js.map +1 -1
  49. package/dist/esm/utilities/chunk-ids.d.ts +5 -0
  50. package/dist/esm/utilities/chunk-ids.js +25 -0
  51. package/dist/esm/utilities/chunk-ids.js.map +1 -0
  52. package/dist/esm/utilities/errors.d.ts +9 -0
  53. package/dist/esm/utilities/errors.js +13 -1
  54. package/dist/esm/utilities/errors.js.map +1 -1
  55. package/dist/esm/utilities/merge-metadata.d.ts +14 -0
  56. package/dist/esm/utilities/merge-metadata.js +43 -0
  57. package/dist/esm/utilities/merge-metadata.js.map +1 -0
  58. package/dist/esm/utilities/normalize-stream-chunk.d.ts +3 -0
  59. package/dist/esm/utilities/normalize-stream-chunk.js +100 -0
  60. package/dist/esm/utilities/normalize-stream-chunk.js.map +1 -0
  61. package/dist/esm/utilities/reasoning-encrypted-value.d.ts +8 -0
  62. package/dist/esm/utilities/reasoning-encrypted-value.js +16 -0
  63. package/dist/esm/utilities/reasoning-encrypted-value.js.map +1 -0
  64. package/dist/esm/utilities/restore-inbound-chunk.d.ts +15 -0
  65. package/dist/esm/utilities/restore-inbound-chunk.js +43 -0
  66. package/dist/esm/utilities/restore-inbound-chunk.js.map +1 -0
  67. package/dist/esm/utilities/spec-event-keys.d.ts +2 -0
  68. package/dist/esm/utilities/spec-event-keys.js +56 -0
  69. package/dist/esm/utilities/spec-event-keys.js.map +1 -0
  70. package/dist/esm/utilities/structured-output-events.d.ts +3 -3
  71. package/dist/esm/utilities/structured-output-events.js.map +1 -1
  72. package/package.json +3 -3
  73. package/src/activities/chat/adapter.ts +4 -4
  74. package/src/activities/chat/index.ts +398 -400
  75. package/src/activities/chat/messages.ts +183 -36
  76. package/src/activities/chat/stream/processor.ts +313 -239
  77. package/src/activities/chat/tools/tool-calls.ts +23 -22
  78. package/src/activities/generateVideo/index.ts +7 -6
  79. package/src/activities/stream-generation-result.ts +8 -12
  80. package/src/activities/summarize/chat-stream-summarize.ts +98 -70
  81. package/src/adapter-internals.ts +2 -0
  82. package/src/client.ts +13 -0
  83. package/src/index.ts +6 -0
  84. package/src/middlewares/otel.ts +16 -8
  85. package/src/stream-to-response.ts +16 -4
  86. package/src/stream-to-websocket.ts +3 -1
  87. package/src/strip-to-spec-middleware.ts +48 -24
  88. package/src/types.ts +109 -393
  89. package/src/utilities/adapter-yield-chunk.ts +30 -0
  90. package/src/utilities/ag-ui-usage.test.ts +194 -0
  91. package/src/utilities/ag-ui-usage.ts +148 -0
  92. package/src/utilities/ag-ui-wire.ts +149 -31
  93. package/src/utilities/chat-params.ts +22 -39
  94. package/src/utilities/chunk-ids.ts +24 -0
  95. package/src/utilities/errors.ts +23 -0
  96. package/src/utilities/merge-metadata.test.ts +117 -0
  97. package/src/utilities/merge-metadata.ts +59 -0
  98. package/src/utilities/normalize-stream-chunk.test.ts +423 -0
  99. package/src/utilities/normalize-stream-chunk.ts +186 -0
  100. package/src/utilities/reasoning-encrypted-value.ts +18 -0
  101. package/src/utilities/restore-inbound-chunk.test.ts +133 -0
  102. package/src/utilities/restore-inbound-chunk.ts +72 -0
  103. package/src/utilities/spec-event-keys.test.ts +34 -0
  104. package/src/utilities/spec-event-keys.ts +74 -0
  105. package/src/utilities/structured-output-events.ts +3 -3
@@ -1,4 +1,6 @@
1
+ import { tanstackMetadata } from "../../utilities/merge-metadata.js";
1
2
  import { normalizeToolResult } from "../../utilities/tool-result.js";
3
+ import { isProviderExecutedToolCall } from "../../utilities/provider-executed.js";
2
4
  //#region src/activities/chat/messages.ts
3
5
  /**
4
6
  * Check if a MessagePart is a content part (text, image, audio, video, document)
@@ -14,6 +16,28 @@ function safeJsonStringify(value) {
14
16
  return "";
15
17
  }
16
18
  }
19
+ function nonEmptyString(value) {
20
+ return typeof value === "string" && value !== "" ? value : void 0;
21
+ }
22
+ function encryptedValueFrom(value) {
23
+ if ("encryptedValue" in value) {
24
+ const fromSpec = nonEmptyString(value.encryptedValue);
25
+ if (fromSpec !== void 0) return fromSpec;
26
+ }
27
+ return nonEmptyString(tanstackMetadata(value)?.signature);
28
+ }
29
+ function toolCallFromWire(toolCall, bag) {
30
+ const fromBag = bag != null && typeof bag === "object" && !Array.isArray(bag) ? bag : void 0;
31
+ const encrypted = encryptedValueFrom(toolCall);
32
+ if (fromBag === void 0 && encrypted === void 0) return toolCall;
33
+ return {
34
+ ...toolCall,
35
+ metadata: {
36
+ ...fromBag ?? {},
37
+ ...encrypted !== void 0 ? { thoughtSignature: encrypted } : {}
38
+ }
39
+ };
40
+ }
17
41
  function parseToolResultContent(content) {
18
42
  try {
19
43
  return JSON.parse(content);
@@ -37,7 +61,7 @@ function collapseContentParts(parts) {
37
61
  * Used when only the text portion is needed (e.g., tool result content).
38
62
  */
39
63
  function getTextContent(content) {
40
- if (content === null) return "";
64
+ if (content === null || content === void 0) return "";
41
65
  if (typeof content === "string") return content;
42
66
  return content.filter((part) => part.type === "text").map((part) => part.content).join("");
43
67
  }
@@ -50,6 +74,7 @@ function convertMessagesToModelMessages(messages) {
50
74
  for (const part of msg.parts) if (part.type === "tool-result") anchoredToolCallIds.add(part.toolCallId);
51
75
  }
52
76
  const modelMessages = [];
77
+ let pendingThinking = [];
53
78
  for (const msg of messages) {
54
79
  if ("parts" in msg) {
55
80
  modelMessages.push(...uiMessageToModelMessages(msg));
@@ -57,7 +82,18 @@ function convertMessagesToModelMessages(messages) {
57
82
  }
58
83
  const role = msg.role;
59
84
  if (role === "tool" && msg.toolCallId && anchoredToolCallIds.has(msg.toolCallId)) continue;
60
- if (role === "reasoning" || role === "activity") continue;
85
+ if (role === "reasoning") {
86
+ const content = msg.content;
87
+ if (content) {
88
+ const signature = encryptedValueFrom(msg);
89
+ pendingThinking.push({
90
+ content,
91
+ ...signature !== void 0 ? { signature } : {}
92
+ });
93
+ }
94
+ continue;
95
+ }
96
+ if (role === "activity") continue;
61
97
  if (role === "developer") {
62
98
  modelMessages.push({
63
99
  role: "system",
@@ -65,6 +101,32 @@ function convertMessagesToModelMessages(messages) {
65
101
  });
66
102
  continue;
67
103
  }
104
+ if (role === "user" && Array.isArray(msg.content)) {
105
+ const content = msg.content;
106
+ if (!content.some((part) => part.type === "text" && "text" in part && !("content" in part))) {
107
+ modelMessages.push(msg);
108
+ continue;
109
+ }
110
+ const contentParts = aguiUserContentToParts(content).filter(isContentPart);
111
+ modelMessages.push({
112
+ role: "user",
113
+ content: collapseContentParts(contentParts),
114
+ ...msg.id !== void 0 && { id: msg.id }
115
+ });
116
+ continue;
117
+ }
118
+ if (role === "assistant") {
119
+ const source = msg;
120
+ const toolCallMetadata = tanstackMetadata(msg)?.toolCallMetadata;
121
+ const toolCalls = source.toolCalls?.map((toolCall) => toolCallFromWire(toolCall, toolCallMetadata?.[toolCall.id]));
122
+ modelMessages.push({
123
+ ...source,
124
+ ...toolCalls !== void 0 ? { toolCalls } : {},
125
+ ...pendingThinking.length > 0 ? { thinking: [...source.thinking ?? [], ...pendingThinking] } : {}
126
+ });
127
+ pendingThinking = [];
128
+ continue;
129
+ }
68
130
  modelMessages.push(msg);
69
131
  }
70
132
  return modelMessages;
@@ -134,13 +196,14 @@ function buildAssistantMessages(uiMessage) {
134
196
  const content = collapseContentParts(current.contentParts);
135
197
  const hasContent = content !== null;
136
198
  const hasToolCalls = current.toolCalls.length > 0;
137
- if (hasContent || hasToolCalls) {
199
+ const hasThinking = pendingThinking.length > 0;
200
+ if (hasContent || hasToolCalls || hasThinking) {
138
201
  messageList.push({
139
202
  id: uiMessage.id,
140
203
  role: "assistant",
141
204
  content,
142
205
  ...hasToolCalls && { toolCalls: current.toolCalls },
143
- ...pendingThinking.length > 0 && { thinking: pendingThinking },
206
+ ...hasThinking && { thinking: pendingThinking },
144
207
  ...current.structuredOutput && { structuredOutput: current.structuredOutput },
145
208
  ...uiMessage.createdAt !== void 0 && { createdAt: uiMessage.createdAt }
146
209
  });
@@ -181,10 +244,13 @@ function buildAssistantMessages(uiMessage) {
181
244
  }
182
245
  break;
183
246
  case "thinking":
184
- if (part.content) pendingThinking.push({
185
- content: part.content,
186
- ...part.signature && { signature: part.signature }
187
- });
247
+ if (part.content) {
248
+ if (current.toolCalls.some(isProviderExecutedToolCall)) flushSegment();
249
+ pendingThinking.push({
250
+ content: part.content,
251
+ ...part.signature && { signature: part.signature }
252
+ });
253
+ }
188
254
  break;
189
255
  case "structured-output": if (part.status === "complete") {
190
256
  const serialized = part.raw !== "" ? part.raw : part.data !== void 0 ? safeJsonStringify(part.data) : "";
@@ -312,51 +378,80 @@ function modelMessageToUIMessage(modelMessage, id) {
312
378
  * unchanged apart from ensuring an id.
313
379
  */
314
380
  function aguiSnapshotMessageToUIMessage(message) {
315
- if ("parts" in message) return {
381
+ if ("parts" in message) return applySnapshotMetadata(message, {
316
382
  ...message,
317
383
  id: message.id || generateMessageId()
318
- };
384
+ });
319
385
  const id = message.id || generateMessageId();
320
386
  switch (message.role) {
321
- case "user": return {
387
+ case "user": return applySnapshotMetadata(message, {
322
388
  id,
323
389
  role: "user",
324
390
  parts: aguiUserContentToParts(message.content)
325
- };
326
- case "assistant": return modelMessageToUIMessage({
327
- role: "assistant",
328
- content: message.content ?? null,
329
- ...message.toolCalls && { toolCalls: message.toolCalls }
330
- }, id);
331
- case "tool": return modelMessageToUIMessage({
391
+ });
392
+ case "assistant": {
393
+ const toolCallMetadata = tanstackMetadata(message)?.toolCallMetadata;
394
+ const toolCalls = message.toolCalls?.map((toolCall) => {
395
+ const metadata = toolCallMetadata != null && typeof toolCallMetadata === "object" ? toolCallMetadata[toolCall.id] : void 0;
396
+ return metadata !== void 0 ? {
397
+ ...toolCall,
398
+ metadata
399
+ } : toolCall;
400
+ });
401
+ return applySnapshotMetadata(message, modelMessageToUIMessage({
402
+ role: "assistant",
403
+ content: message.content ?? null,
404
+ ...toolCalls && { toolCalls }
405
+ }, id));
406
+ }
407
+ case "tool": return applySnapshotMetadata(message, modelMessageToUIMessage({
332
408
  role: "tool",
333
409
  content: message.content,
334
410
  toolCallId: message.toolCallId
335
- }, id);
411
+ }, id));
336
412
  case "system":
337
- case "developer": return {
413
+ case "developer": return applySnapshotMetadata(message, {
338
414
  id,
339
415
  role: "system",
340
416
  parts: message.content ? [{
341
417
  type: "text",
342
418
  content: message.content
343
419
  }] : []
344
- };
345
- case "reasoning": return {
346
- id,
347
- role: "assistant",
348
- parts: message.content ? [{
349
- type: "thinking",
350
- content: message.content
351
- }] : []
352
- };
353
- default: return {
420
+ });
421
+ case "reasoning": {
422
+ const signature = encryptedValueFrom(message);
423
+ return applySnapshotMetadata(message, {
424
+ id,
425
+ role: "assistant",
426
+ parts: message.content ? [{
427
+ type: "thinking",
428
+ content: message.content,
429
+ ...signature !== void 0 ? { signature } : {}
430
+ }] : []
431
+ });
432
+ }
433
+ default: return applySnapshotMetadata(message, {
354
434
  id,
355
435
  role: "assistant",
356
436
  parts: []
357
- };
437
+ });
358
438
  }
359
439
  }
440
+ /** Copy snapshot metadata when it is a record. Rebuild createdAt from tanstack.createdAt. */
441
+ function applySnapshotMetadata(source, ui) {
442
+ if (!("metadata" in source)) return ui;
443
+ const raw = source.metadata;
444
+ if (raw == null || typeof raw !== "object" || Array.isArray(raw)) return ui;
445
+ const metadata = raw;
446
+ const createdAtRaw = tanstackMetadata(metadata)?.createdAt;
447
+ const createdAt = typeof createdAtRaw === "string" ? new Date(createdAtRaw) : void 0;
448
+ const createdAtValid = createdAt !== void 0 && !Number.isNaN(createdAt.getTime());
449
+ return {
450
+ ...ui,
451
+ metadata,
452
+ ...createdAtValid ? { createdAt } : {}
453
+ };
454
+ }
360
455
  /**
361
456
  * Convert AG-UI user message content into `UIMessage` parts.
362
457
  *
@@ -1 +1 @@
1
- {"version":3,"file":"messages.js","names":[],"sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import { normalizeToolResult } from '../../utilities/tool-result'\nimport type { Message as AGUIMessage } from '@ag-ui/core'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StructuredOutputPart,\n TextPart,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\nexport function safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? ''\n } catch {\n return ''\n }\n}\n\nfunction parseToolResultContent(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\n }\n}\n\n/**\n * Collapse an array of ContentParts into the most compact ModelMessage content:\n * - Empty array → null\n * - All text parts → joined string (or null if empty)\n * - Mixed content → ContentPart array as-is\n */\nfunction collapseContentParts(\n parts: Array<ContentPart>,\n): string | null | Array<ContentPart> {\n if (parts.length === 0) return null\n\n const allText = parts.every((p) => p.type === 'text')\n if (allText) {\n const joined = parts.map((p) => p.content).join('')\n return joined || null\n }\n\n return parts\n}\n\n/**\n * Extract text content from ModelMessage content (string, null, or ContentPart array).\n * Used when only the text portion is needed (e.g., tool result content).\n */\nfunction getTextContent(content: string | null | Array<ContentPart>): string {\n if (content === null) return ''\n if (typeof content === 'string') return content\n return content\n .filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.content)\n .join('')\n}\n\n/**\n * Convert UIMessages or ModelMessages to ModelMessages\n */\nexport function convertMessagesToModelMessages(\n messages: Array<UIMessage | ModelMessage>,\n): Array<ModelMessage> {\n // Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.\n // Fan-out tool messages whose toolCallId matches an anchored ToolResultPart\n // are AG-UI duplicates and must be dropped to avoid double-feeding the LLM.\n const anchoredToolCallIds = new Set<string>()\n for (const msg of messages) {\n if ('parts' in msg) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n anchoredToolCallIds.add(part.toolCallId)\n }\n }\n }\n }\n\n const modelMessages: Array<ModelMessage> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n // UIMessage anchor — existing fan-out path\n modelMessages.push(...uiMessageToModelMessages(msg))\n continue\n }\n\n const role = (msg as { role: string }).role\n\n // AG-UI tool fan-out duplicate — drop if anchor already covers it\n if (\n role === 'tool' &&\n msg.toolCallId &&\n anchoredToolCallIds.has(msg.toolCallId)\n ) {\n continue\n }\n\n // AG-UI reasoning and activity — no ModelMessage equivalent today\n if (role === 'reasoning' || role === 'activity') {\n continue\n }\n\n // AG-UI developer — collapse to system\n if (role === 'developer') {\n modelMessages.push({\n role: 'system' as ModelMessage['role'],\n content: (msg as { content: string }).content,\n })\n continue\n }\n\n // Already a ModelMessage (user, assistant, system, tool with no anchor) — pass through\n modelMessages.push(msg)\n }\n return modelMessages\n}\n\n/**\n * Convert a UIMessage to ModelMessage(s)\n *\n * Walks the parts array IN ORDER to preserve the interleaving of text,\n * tool calls, and tool results. This is critical for multi-round tool\n * flows where the model generates text, calls a tool, gets the result,\n * then generates more text and calls another tool.\n *\n * The output preserves the sequential structure:\n * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2\n * becomes:\n * assistant: {content: \"text1\", toolCalls: [toolCall1]}\n * tool: toolResult1\n * assistant: {content: \"text2\", toolCalls: [toolCall2]}\n * tool: toolResult2\n *\n * @param uiMessage - The UIMessage to convert\n * @returns An array of ModelMessages preserving part ordering\n */\nexport function uiMessageToModelMessages(\n uiMessage: UIMessage,\n): Array<ModelMessage> {\n // Skip system messages - they're handled via systemPrompts, not ModelMessages\n if (uiMessage.role === 'system') {\n return []\n }\n\n // For non-assistant messages (user), use the simpler path since they\n // don't have tool calls or tool results to interleave\n if (uiMessage.role !== 'assistant') {\n return [buildUserOrToolMessage(uiMessage)]\n }\n\n // For assistant messages, walk parts in order to preserve interleaving\n return buildAssistantMessages(uiMessage)\n}\n\n/**\n * Build a single ModelMessage for user messages (simple path).\n * Preserves ordering of text and multimodal content parts.\n */\nfunction buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {\n const contentParts: Array<ContentPart> = []\n for (const part of uiMessage.parts) {\n if (isContentPart(part)) {\n contentParts.push(part)\n }\n }\n\n return {\n id: uiMessage.id,\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n structuredOutput?: StructuredOutputPart\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'complete' ||\n part.state === 'approval-requested' ||\n part.state === 'approval-responded' ||\n part.state === 'error' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n // A single UI message can fan out into several model messages. Keep the\n // shared UI id on each one so persistence can retain the original identity.\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n\n if (hasContent || hasToolCalls) {\n messageList.push({\n id: uiMessage.id,\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\n ...(current.structuredOutput && {\n structuredOutput: current.structuredOutput,\n }),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n id: uiMessage.id,\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n current.structuredOutput = part\n }\n }\n break\n\n case 'ui-resource':\n // MCP Apps widget — rendered client-side only. It must never enter\n // model input, so it is intentionally dropped from the model message.\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n id: uiMessage.id,\n role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n id: uiMessage.id,\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n id: uiMessage.id,\n role: 'assistant',\n content: null,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'assistant' && modelMessage.structuredOutput) {\n parts.push(modelMessage.structuredOutput)\n } else if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n // Model-message arguments are complete, so surface the parsed input.\n // A malformed arguments string just leaves `input` undefined.\n let input: unknown\n try {\n input = JSON.parse(toolCall.function.arguments)\n } catch {\n input = undefined\n }\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(input !== undefined && { input }),\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n ...(modelMessage.createdAt !== undefined && {\n createdAt: modelMessage.createdAt,\n }),\n }\n}\n\n/**\n * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.\n *\n * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have\n * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code\n * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`\n * handler) crashes with \"Cannot read properties of undefined (reading 'find')\".\n *\n * Each role is mapped to the canonical `UIMessage` shape, reusing\n * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.\n * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /\n * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated\n * id only when the snapshot omits one). Messages that already carry `parts`\n * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through\n * unchanged apart from ensuring an id.\n */\nexport function aguiSnapshotMessageToUIMessage(\n message: AGUIMessage | UIMessage,\n): UIMessage {\n if ('parts' in message) {\n return { ...message, id: message.id || generateMessageId() }\n }\n\n const id = message.id || generateMessageId()\n\n switch (message.role) {\n case 'user':\n return {\n id,\n role: 'user',\n parts: aguiUserContentToParts(message.content),\n }\n case 'assistant':\n return modelMessageToUIMessage(\n {\n role: 'assistant',\n content: message.content ?? null,\n ...(message.toolCalls && { toolCalls: message.toolCalls }),\n },\n id,\n )\n case 'tool':\n return modelMessageToUIMessage(\n {\n role: 'tool',\n content: message.content,\n toolCallId: message.toolCallId,\n },\n id,\n )\n case 'system':\n case 'developer':\n // `ModelMessage` has no system/developer role; build the part directly.\n return {\n id,\n role: 'system',\n parts: message.content\n ? [{ type: 'text', content: message.content }]\n : [],\n }\n case 'reasoning':\n return {\n id,\n role: 'assistant',\n parts: message.content\n ? [{ type: 'thinking', content: message.content }]\n : [],\n }\n case 'activity':\n default:\n // `activity` (and any future role) has no text/parts equivalent today.\n return { id, role: 'assistant', parts: [] }\n }\n}\n\n/**\n * Convert AG-UI user message content into `UIMessage` parts.\n *\n * AG-UI user content is either a plain string or a multimodal array whose text\n * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).\n * Text entries are rewritten to the TanStack shape; image/audio/video/document\n * entries already match `ContentPart` and pass through. `binary` entries have no\n * TanStack equivalent and are dropped.\n */\nfunction aguiUserContentToParts(\n content: Extract<AGUIMessage, { role: 'user' }>['content'],\n): Array<MessagePart> {\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : []\n }\n\n const parts: Array<MessagePart> = []\n for (const part of content) {\n if (part.type === 'text') {\n parts.push({ type: 'text', content: part.text })\n } else if (part.type !== 'binary') {\n parts.push(part)\n }\n }\n return parts\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n msg.toolCallId !== undefined &&\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n const content = getTextContent(msg.content)\n const toolCallPart = currentAssistantMessage.parts.find(\n (part): part is ToolCallPart =>\n part.type === 'tool-call' && part.id === msg.toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = parseToolResultContent(content)\n toolCallPart.state = 'complete'\n }\n\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content,\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg, msg.id)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message. Preserve a persisted stable id so a hydrated message\n // keeps the same identity as its live stream (enables in-place resume).\n const uiMessage = modelMessageToUIMessage(msg, msg.id)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: message.createdAt ?? new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"messages.js","names":[],"sources":["../../../../src/activities/chat/messages.ts"],"sourcesContent":["import { isProviderExecutedToolCall } from '../../utilities/provider-executed'\nimport { normalizeToolResult } from '../../utilities/tool-result'\nimport { tanstackMetadata } from '../../utilities/merge-metadata'\nimport type { Message as AGUIMessage } from '@ag-ui/core'\nimport type {\n ContentPart,\n MessagePart,\n ModelMessage,\n StructuredOutputPart,\n TextPart,\n ToolCall,\n ToolCallPart,\n UIMessage,\n} from '../../types'\n// ===========================\n// Message Converters\n// ===========================\n\n/**\n * Check if a MessagePart is a content part (text, image, audio, video, document)\n * that maps directly to a ModelMessage ContentPart.\n */\nfunction isContentPart(part: MessagePart): part is ContentPart {\n return (\n part.type === 'text' ||\n part.type === 'image' ||\n part.type === 'audio' ||\n part.type === 'video' ||\n part.type === 'document'\n )\n}\n\nexport function safeJsonStringify(value: unknown): string {\n try {\n return JSON.stringify(value) ?? ''\n } catch {\n return ''\n }\n}\n\nfunction nonEmptyString(value: unknown): string | undefined {\n return typeof value === 'string' && value !== '' ? value : undefined\n}\n\nfunction encryptedValueFrom(value: object): string | undefined {\n if ('encryptedValue' in value) {\n const fromSpec = nonEmptyString(value.encryptedValue)\n if (fromSpec !== undefined) return fromSpec\n }\n return nonEmptyString(tanstackMetadata(value)?.signature)\n}\n\nfunction toolCallFromWire(toolCall: ToolCall, bag: unknown): ToolCall {\n const fromBag =\n bag != null && typeof bag === 'object' && !Array.isArray(bag)\n ? bag\n : undefined\n const encrypted = encryptedValueFrom(toolCall)\n if (fromBag === undefined && encrypted === undefined) return toolCall\n return {\n ...toolCall,\n metadata: {\n ...(fromBag ?? {}),\n ...(encrypted !== undefined ? { thoughtSignature: encrypted } : {}),\n },\n }\n}\n\nfunction parseToolResultContent(content: string): unknown {\n try {\n return JSON.parse(content)\n } catch {\n return content\n }\n}\n\n/**\n * Collapse an array of ContentParts into the most compact ModelMessage content:\n * - Empty array → null\n * - All text parts → joined string (or null if empty)\n * - Mixed content → ContentPart array as-is\n */\nfunction collapseContentParts(\n parts: Array<ContentPart>,\n): string | null | Array<ContentPart> {\n if (parts.length === 0) return null\n\n const allText = parts.every((p) => p.type === 'text')\n if (allText) {\n const joined = parts.map((p) => p.content).join('')\n return joined || null\n }\n\n return parts\n}\n\n/**\n * Extract text content from ModelMessage content (string, null, or ContentPart array).\n * Used when only the text portion is needed (e.g., tool result content).\n */\nfunction getTextContent(\n content: string | null | undefined | Array<ContentPart>,\n): string {\n // Tool-call-only assistant turns carry no text and reach here as `null` or\n // `undefined`; both must collapse to an empty string rather than crash on\n // `.filter` (issue #532 — the interrupt-boundary MessagesSnapshot).\n if (content === null || content === undefined) return ''\n if (typeof content === 'string') return content\n return content\n .filter((part): part is TextPart => part.type === 'text')\n .map((part) => part.content)\n .join('')\n}\n\n/**\n * Convert UIMessages or ModelMessages to ModelMessages\n */\nexport function convertMessagesToModelMessages(\n messages: Array<UIMessage | ModelMessage>,\n): Array<ModelMessage> {\n // Pre-pass: collect toolCallIds already represented in anchor UIMessage parts.\n // Fan-out tool messages whose toolCallId matches an anchored ToolResultPart\n // are AG-UI duplicates and must be dropped to avoid double-feeding the LLM.\n const anchoredToolCallIds = new Set<string>()\n for (const msg of messages) {\n if ('parts' in msg) {\n for (const part of msg.parts) {\n if (part.type === 'tool-result') {\n anchoredToolCallIds.add(part.toolCallId)\n }\n }\n }\n }\n\n const modelMessages: Array<ModelMessage> = []\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n for (const msg of messages) {\n if ('parts' in msg) {\n modelMessages.push(...uiMessageToModelMessages(msg))\n continue\n }\n\n const role = (msg as { role: string }).role\n\n if (\n role === 'tool' &&\n msg.toolCallId &&\n anchoredToolCallIds.has(msg.toolCallId)\n ) {\n continue\n }\n\n if (role === 'reasoning') {\n const content = (msg as { content?: string }).content\n if (content) {\n const signature = encryptedValueFrom(msg)\n pendingThinking.push({\n content,\n ...(signature !== undefined ? { signature } : {}),\n })\n }\n continue\n }\n\n if (role === 'activity') {\n continue\n }\n\n if (role === 'developer') {\n modelMessages.push({\n role: 'system' as ModelMessage['role'],\n content: (msg as { content: string }).content,\n })\n continue\n }\n\n if (\n role === 'user' &&\n Array.isArray((msg as { content?: unknown }).content)\n ) {\n const content = (msg as { content: Array<{ type: string }> }).content\n // TanStack ModelMessage text parts use `{ content }`. AG-UI wire text\n // parts use `{ text }`. Only rewrite the AG-UI shape.\n if (\n !content.some(\n (part) =>\n part.type === 'text' && 'text' in part && !('content' in part),\n )\n ) {\n modelMessages.push(msg as ModelMessage)\n continue\n }\n const parts = aguiUserContentToParts(\n content as Extract<AGUIMessage, { role: 'user' }>['content'],\n )\n const contentParts = parts.filter(isContentPart)\n modelMessages.push({\n role: 'user',\n content: collapseContentParts(contentParts),\n ...((msg as { id?: string }).id !== undefined && {\n id: (msg as { id: string }).id,\n }),\n })\n continue\n }\n\n if (role === 'assistant') {\n const source = msg as ModelMessage\n const toolCallMetadata = tanstackMetadata(msg)?.toolCallMetadata\n const toolCalls = source.toolCalls?.map((toolCall) =>\n toolCallFromWire(toolCall, toolCallMetadata?.[toolCall.id]),\n )\n modelMessages.push({\n ...source,\n ...(toolCalls !== undefined ? { toolCalls } : {}),\n ...(pendingThinking.length > 0\n ? {\n thinking: [...(source.thinking ?? []), ...pendingThinking],\n }\n : {}),\n })\n pendingThinking = []\n continue\n }\n\n modelMessages.push(msg as ModelMessage)\n }\n return modelMessages\n}\n\n/**\n * Convert a UIMessage to ModelMessage(s)\n *\n * Walks the parts array IN ORDER to preserve the interleaving of text,\n * tool calls, and tool results. This is critical for multi-round tool\n * flows where the model generates text, calls a tool, gets the result,\n * then generates more text and calls another tool.\n *\n * The output preserves the sequential structure:\n * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2\n * becomes:\n * assistant: {content: \"text1\", toolCalls: [toolCall1]}\n * tool: toolResult1\n * assistant: {content: \"text2\", toolCalls: [toolCall2]}\n * tool: toolResult2\n *\n * @param uiMessage - The UIMessage to convert\n * @returns An array of ModelMessages preserving part ordering\n */\nexport function uiMessageToModelMessages(\n uiMessage: UIMessage,\n): Array<ModelMessage> {\n // Skip system messages - they're handled via systemPrompts, not ModelMessages\n if (uiMessage.role === 'system') {\n return []\n }\n\n // For non-assistant messages (user), use the simpler path since they\n // don't have tool calls or tool results to interleave\n if (uiMessage.role !== 'assistant') {\n return [buildUserOrToolMessage(uiMessage)]\n }\n\n // For assistant messages, walk parts in order to preserve interleaving\n return buildAssistantMessages(uiMessage)\n}\n\n/**\n * Build a single ModelMessage for user messages (simple path).\n * Preserves ordering of text and multimodal content parts.\n */\nfunction buildUserOrToolMessage(uiMessage: UIMessage): ModelMessage {\n const contentParts: Array<ContentPart> = []\n for (const part of uiMessage.parts) {\n if (isContentPart(part)) {\n contentParts.push(part)\n }\n }\n\n return {\n id: uiMessage.id,\n role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\n structuredOutput?: StructuredOutputPart\n toolCalls: Array<{\n id: string\n type: 'function'\n function: { name: string; arguments: string }\n /** Provider-specific metadata that round-trips with the tool call.\n * Untyped at this framework layer; adapters narrow it via their\n * `TToolCallMetadata` generic. */\n metadata?: unknown\n }>\n}\n\nfunction createSegment(): AssistantSegment {\n return { contentParts: [], toolCalls: [] }\n}\n\nfunction isToolCallIncluded(part: ToolCallPart): boolean {\n return (\n part.state === 'input-complete' ||\n part.state === 'complete' ||\n part.state === 'approval-requested' ||\n part.state === 'approval-responded' ||\n part.state === 'error' ||\n part.output !== undefined\n )\n}\n\n/**\n * Build ModelMessages for an assistant UIMessage, preserving the\n * sequential interleaving of text, tool calls, and tool results.\n *\n * Walks parts in order. Text and tool-call parts accumulate into the\n * current \"segment\". When a tool-result part is encountered, the\n * current segment is flushed as an assistant message, then the tool\n * result is emitted as a tool message.\n */\nfunction buildAssistantMessages(uiMessage: UIMessage): Array<ModelMessage> {\n // A single UI message can fan out into several model messages. Keep the\n // shared UI id on each one so persistence can retain the original identity.\n const messageList: Array<ModelMessage> = []\n let current = createSegment()\n let pendingThinking: Array<{ content: string; signature?: string }> = []\n\n // Track emitted tool result IDs to avoid duplicates.\n // A tool call can have BOTH an explicit tool-result part AND an output\n // field on the tool-call part. We only want one per tool call ID.\n const emittedToolResultIds = new Set<string>()\n\n function flushSegment(): void {\n const content = collapseContentParts(current.contentParts)\n const hasContent = content !== null\n const hasToolCalls = current.toolCalls.length > 0\n const hasThinking = pendingThinking.length > 0\n\n if (hasContent || hasToolCalls || hasThinking) {\n messageList.push({\n id: uiMessage.id,\n role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(hasThinking && { thinking: pendingThinking }),\n ...(current.structuredOutput && {\n structuredOutput: current.structuredOutput,\n }),\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n pendingThinking = []\n }\n current = createSegment()\n }\n\n for (const part of uiMessage.parts) {\n switch (part.type) {\n case 'text':\n case 'image':\n case 'audio':\n case 'video':\n case 'document':\n current.contentParts.push(part)\n break\n\n case 'tool-call':\n if (isToolCallIncluded(part)) {\n current.toolCalls.push({\n id: part.id,\n type: 'function' as const,\n function: {\n name: part.name,\n arguments: part.arguments,\n },\n ...(part.metadata !== undefined && { metadata: part.metadata }),\n })\n }\n break\n\n case 'tool-result':\n // Flush the current assistant segment before emitting the tool result\n flushSegment()\n\n // Emit the tool result\n if (\n (part.state === 'complete' || part.state === 'error') &&\n !emittedToolResultIds.has(part.toolCallId)\n ) {\n messageList.push({\n id: uiMessage.id,\n role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n emittedToolResultIds.add(part.toolCallId)\n }\n break\n\n case 'thinking':\n if (part.content) {\n // Provider-executed tools have no tool-result part, so thinking\n // after them has to start the next segment or it replays first.\n if (current.toolCalls.some(isProviderExecutedToolCall)) {\n flushSegment()\n }\n pendingThinking.push({\n content: part.content,\n ...(part.signature && { signature: part.signature }),\n })\n }\n break\n\n case 'structured-output':\n // Only emit completed structured responses into history. Streaming or\n // errored buffers would push malformed JSON into the next LLM turn's\n // assistant content. `raw` is the source of truth; `data` is the\n // defensive fallback for terminal-only completes that didn't ship raw.\n if (part.status === 'complete') {\n const serialized =\n part.raw !== ''\n ? part.raw\n : part.data !== undefined\n ? safeJsonStringify(part.data)\n : ''\n if (serialized !== '') {\n current.contentParts.push({ type: 'text', content: serialized })\n current.structuredOutput = part\n }\n }\n break\n\n case 'ui-resource':\n // MCP Apps widget — rendered client-side only. It must never enter\n // model input, so it is intentionally dropped from the model message.\n break\n\n default:\n break\n }\n }\n\n // Flush any remaining accumulated content\n flushSegment()\n\n // Emit tool results from client tool-call parts with output or approval,\n // but only if not already covered by an explicit tool-result part above.\n // These are appended at the end since they don't have explicit tool-result\n // parts in the parts array to trigger inline emission.\n for (const part of uiMessage.parts) {\n if (part.type !== 'tool-call') continue\n\n // Output takes priority — if the tool has already produced a result,\n // emit the concrete output regardless of approval metadata.\n if (part.output !== undefined && !emittedToolResultIds.has(part.id)) {\n messageList.push({\n id: uiMessage.id,\n role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n emittedToolResultIds.add(part.id)\n }\n\n // Approval response without output — emit approval status for iteration tracking\n if (\n part.output === undefined &&\n part.state === 'approval-responded' &&\n part.approval?.approved !== undefined &&\n !emittedToolResultIds.has(part.id)\n ) {\n const approved = part.approval.approved\n messageList.push({\n id: uiMessage.id,\n role: 'tool',\n content: JSON.stringify({\n approved,\n ...(approved && { pendingExecution: true }),\n message: approved\n ? 'User approved this action'\n : 'User denied this action',\n }),\n toolCallId: part.id,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n emittedToolResultIds.add(part.id)\n }\n }\n\n // If no messages were produced (e.g., empty parts), emit a minimal assistant message\n if (messageList.length === 0) {\n messageList.push({\n id: uiMessage.id,\n role: 'assistant',\n content: null,\n ...(uiMessage.createdAt !== undefined && {\n createdAt: uiMessage.createdAt,\n }),\n })\n }\n\n return messageList\n}\n\n/**\n * Convert a ModelMessage to UIMessage\n *\n * This conversion creates a parts-based structure:\n * - content field → TextPart\n * - toolCalls array → ToolCallPart[]\n * - role=\"tool\" messages should be converted separately and merged\n *\n * @param modelMessage - The ModelMessage to convert\n * @param id - Optional ID for the UIMessage (generated if not provided)\n * @returns A UIMessage with parts\n */\nexport function modelMessageToUIMessage(\n modelMessage: ModelMessage,\n id?: string,\n): UIMessage {\n const parts: Array<MessagePart> = []\n\n if (modelMessage.role === 'assistant' && modelMessage.thinking?.length) {\n for (const thinking of modelMessage.thinking) {\n if (!thinking.content) continue\n parts.push({\n type: 'thinking',\n content: thinking.content,\n ...(thinking.signature && { signature: thinking.signature }),\n })\n }\n }\n\n // Handle tool results (when role is \"tool\") - only produce tool-result part,\n // not a text part (the content IS the tool result, not display text)\n if (modelMessage.role === 'assistant' && modelMessage.structuredOutput) {\n parts.push(modelMessage.structuredOutput)\n } else if (modelMessage.role === 'tool' && modelMessage.toolCallId) {\n parts.push({\n type: 'tool-result',\n toolCallId: modelMessage.toolCallId,\n content: getTextContent(modelMessage.content),\n state: 'complete',\n })\n } else if (Array.isArray(modelMessage.content)) {\n // Multimodal content - preserve all content parts as MessageParts\n for (const part of modelMessage.content) {\n parts.push(part)\n }\n } else {\n // String or null content\n const textContent = getTextContent(modelMessage.content)\n if (textContent) {\n parts.push({\n type: 'text',\n content: textContent,\n })\n }\n }\n\n // Handle tool calls\n if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {\n for (const toolCall of modelMessage.toolCalls) {\n // Model-message arguments are complete, so surface the parsed input.\n // A malformed arguments string just leaves `input` undefined.\n let input: unknown\n try {\n input = JSON.parse(toolCall.function.arguments)\n } catch {\n input = undefined\n }\n parts.push({\n type: 'tool-call',\n id: toolCall.id,\n name: toolCall.function.name,\n arguments: toolCall.function.arguments,\n state: 'input-complete', // Model messages have complete arguments\n ...(input !== undefined && { input }),\n ...(toolCall.metadata !== undefined && { metadata: toolCall.metadata }),\n })\n }\n }\n\n return {\n id: id || generateMessageId(),\n role: modelMessage.role === 'tool' ? 'assistant' : modelMessage.role,\n parts,\n ...(modelMessage.createdAt !== undefined && {\n createdAt: modelMessage.createdAt,\n }),\n }\n}\n\n/**\n * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.\n *\n * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have\n * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code\n * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`\n * handler) crashes with \"Cannot read properties of undefined (reading 'find')\".\n *\n * Each role is mapped to the canonical `UIMessage` shape, reusing\n * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.\n * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /\n * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated\n * id only when the snapshot omits one). Messages that already carry `parts`\n * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through\n * unchanged apart from ensuring an id.\n */\nexport function aguiSnapshotMessageToUIMessage(\n message: AGUIMessage | UIMessage,\n): UIMessage {\n if ('parts' in message) {\n return applySnapshotMetadata(message, {\n ...message,\n id: message.id || generateMessageId(),\n })\n }\n\n const id = message.id || generateMessageId()\n\n switch (message.role) {\n case 'user':\n return applySnapshotMetadata(message, {\n id,\n role: 'user',\n parts: aguiUserContentToParts(message.content),\n })\n case 'assistant': {\n const toolCallMetadata = tanstackMetadata(message)?.toolCallMetadata\n const toolCalls = message.toolCalls?.map((toolCall) => {\n const metadata =\n toolCallMetadata != null && typeof toolCallMetadata === 'object'\n ? (toolCallMetadata as Record<string, unknown>)[toolCall.id]\n : undefined\n return metadata !== undefined ? { ...toolCall, metadata } : toolCall\n })\n return applySnapshotMetadata(\n message,\n modelMessageToUIMessage(\n {\n role: 'assistant',\n content: message.content ?? null,\n ...(toolCalls && { toolCalls }),\n },\n id,\n ),\n )\n }\n case 'tool':\n return applySnapshotMetadata(\n message,\n modelMessageToUIMessage(\n {\n role: 'tool',\n content: message.content,\n toolCallId: message.toolCallId,\n },\n id,\n ),\n )\n case 'system':\n case 'developer':\n // `ModelMessage` has no system/developer role; build the part directly.\n return applySnapshotMetadata(message, {\n id,\n role: 'system',\n parts: message.content\n ? [{ type: 'text', content: message.content }]\n : [],\n })\n case 'reasoning': {\n const signature = encryptedValueFrom(message)\n return applySnapshotMetadata(message, {\n id,\n role: 'assistant',\n parts: message.content\n ? [\n {\n type: 'thinking' as const,\n content: message.content,\n ...(signature !== undefined ? { signature } : {}),\n },\n ]\n : [],\n })\n }\n case 'activity':\n default:\n // `activity` (and any future role) has no text/parts equivalent today.\n return applySnapshotMetadata(message, {\n id,\n role: 'assistant',\n parts: [],\n })\n }\n}\n\n/** Copy snapshot metadata when it is a record. Rebuild createdAt from tanstack.createdAt. */\nfunction applySnapshotMetadata(source: object, ui: UIMessage): UIMessage {\n if (!('metadata' in source)) return ui\n const raw = source.metadata\n if (raw == null || typeof raw !== 'object' || Array.isArray(raw)) return ui\n const metadata = raw as NonNullable<UIMessage['metadata']>\n const createdAtRaw = tanstackMetadata(metadata)?.createdAt\n const createdAt =\n typeof createdAtRaw === 'string' ? new Date(createdAtRaw) : undefined\n const createdAtValid =\n createdAt !== undefined && !Number.isNaN(createdAt.getTime())\n return {\n ...ui,\n metadata,\n ...(createdAtValid ? { createdAt } : {}),\n }\n}\n\n/**\n * Convert AG-UI user message content into `UIMessage` parts.\n *\n * AG-UI user content is either a plain string or a multimodal array whose text\n * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).\n * Text entries are rewritten to the TanStack shape; image/audio/video/document\n * entries already match `ContentPart` and pass through. `binary` entries have no\n * TanStack equivalent and are dropped.\n */\nfunction aguiUserContentToParts(\n content: Extract<AGUIMessage, { role: 'user' }>['content'],\n): Array<MessagePart> {\n if (typeof content === 'string') {\n return content ? [{ type: 'text', content }] : []\n }\n\n const parts: Array<MessagePart> = []\n for (const part of content) {\n if (part.type === 'text') {\n parts.push({ type: 'text', content: part.text })\n } else if (part.type !== 'binary') {\n parts.push(part)\n }\n }\n return parts\n}\n\n/**\n * Convert an array of ModelMessages to UIMessages\n *\n * This handles merging tool result messages with their corresponding assistant messages\n *\n * @param modelMessages - Array of ModelMessages to convert\n * @returns Array of UIMessages\n */\nexport function modelMessagesToUIMessages(\n modelMessages: Array<ModelMessage>,\n): Array<UIMessage> {\n const uiMessages: Array<UIMessage> = []\n let currentAssistantMessage: UIMessage | null = null\n\n for (const msg of modelMessages) {\n if (msg.role === 'tool') {\n // Tool result - merge into the last assistant message if possible\n if (\n msg.toolCallId !== undefined &&\n currentAssistantMessage &&\n currentAssistantMessage.role === 'assistant'\n ) {\n const content = getTextContent(msg.content)\n const toolCallPart = currentAssistantMessage.parts.find(\n (part): part is ToolCallPart =>\n part.type === 'tool-call' && part.id === msg.toolCallId,\n )\n\n if (toolCallPart) {\n toolCallPart.output = parseToolResultContent(content)\n toolCallPart.state = 'complete'\n }\n\n currentAssistantMessage.parts.push({\n type: 'tool-result',\n toolCallId: msg.toolCallId,\n content,\n state: 'complete',\n })\n } else {\n // No assistant message to merge into, create a standalone one\n const toolResultUIMessage = modelMessageToUIMessage(msg, msg.id)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message. Preserve a persisted stable id so a hydrated message\n // keeps the same identity as its live stream (enables in-place resume).\n const uiMessage = modelMessageToUIMessage(msg, msg.id)\n uiMessages.push(uiMessage)\n\n // Track assistant messages for potential tool result merging\n if (msg.role === 'assistant') {\n currentAssistantMessage = uiMessage\n } else {\n currentAssistantMessage = null\n }\n }\n }\n\n return uiMessages\n}\n\n/**\n * Normalize a message (UIMessage or ModelMessage) to a UIMessage\n * Ensures the message has an ID and createdAt timestamp\n *\n * @param message - Either a UIMessage or ModelMessage\n * @param generateId - Function to generate a message ID if needed\n * @returns A UIMessage with guaranteed id and createdAt\n */\nexport function normalizeToUIMessage(\n message: UIMessage | ModelMessage,\n generateId: () => string,\n): UIMessage {\n if ('parts' in message) {\n // Already a UIMessage\n return {\n ...message,\n id: message.id || generateId(),\n createdAt: message.createdAt || new Date(),\n }\n } else {\n // ModelMessage - convert to UIMessage\n return {\n ...modelMessageToUIMessage(message, generateId()),\n createdAt: message.createdAt ?? new Date(),\n }\n }\n}\n\n/**\n * Generate a unique message ID\n */\nexport function generateMessageId(): string {\n return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`\n}\n"],"mappings":";;;;;;;;AAsBA,SAAS,cAAc,MAAwC;CAC7D,OACE,KAAK,SAAS,UACd,KAAK,SAAS,WACd,KAAK,SAAS,WACd,KAAK,SAAS,WACd,KAAK,SAAS;AAElB;AAEA,SAAgB,kBAAkB,OAAwB;CACxD,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK;CAClC,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,eAAe,OAAoC;CAC1D,OAAO,OAAO,UAAU,YAAY,UAAU,KAAK,QAAQ,KAAA;AAC7D;AAEA,SAAS,mBAAmB,OAAmC;CAC7D,IAAI,oBAAoB,OAAO;EAC7B,MAAM,WAAW,eAAe,MAAM,cAAc;EACpD,IAAI,aAAa,KAAA,GAAW,OAAO;CACrC;CACA,OAAO,eAAe,iBAAiB,KAAK,CAAC,EAAE,SAAS;AAC1D;AAEA,SAAS,iBAAiB,UAAoB,KAAwB;CACpE,MAAM,UACJ,OAAO,QAAQ,OAAO,QAAQ,YAAY,CAAC,MAAM,QAAQ,GAAG,IACxD,MACA,KAAA;CACN,MAAM,YAAY,mBAAmB,QAAQ;CAC7C,IAAI,YAAY,KAAA,KAAa,cAAc,KAAA,GAAW,OAAO;CAC7D,OAAO;EACL,GAAG;EACH,UAAU;GACR,GAAI,WAAW,CAAC;GAChB,GAAI,cAAc,KAAA,IAAY,EAAE,kBAAkB,UAAU,IAAI,CAAC;EACnE;CACF;AACF;AAEA,SAAS,uBAAuB,SAA0B;CACxD,IAAI;EACF,OAAO,KAAK,MAAM,OAAO;CAC3B,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAS,qBACP,OACoC;CACpC,IAAI,MAAM,WAAW,GAAG,OAAO;CAG/B,IADgB,MAAM,OAAO,MAAM,EAAE,SAAS,MAC1C,GAEF,OADe,MAAM,KAAK,MAAM,EAAE,OAAO,CAAC,CAAC,KAAK,EACzC,KAAU;CAGnB,OAAO;AACT;;;;;AAMA,SAAS,eACP,SACQ;CAIR,IAAI,YAAY,QAAQ,YAAY,KAAA,GAAW,OAAO;CACtD,IAAI,OAAO,YAAY,UAAU,OAAO;CACxC,OAAO,QACJ,QAAQ,SAA2B,KAAK,SAAS,MAAM,CAAC,CACxD,KAAK,SAAS,KAAK,OAAO,CAAC,CAC3B,KAAK,EAAE;AACZ;;;;AAKA,SAAgB,+BACd,UACqB;CAIrB,MAAM,sCAAsB,IAAI,IAAY;CAC5C,KAAK,MAAM,OAAO,UAChB,IAAI,WAAW,KACR;OAAA,MAAM,QAAQ,IAAI,OACrB,IAAI,KAAK,SAAS,eAChB,oBAAoB,IAAI,KAAK,UAAU;CAAA;CAM/C,MAAM,gBAAqC,CAAC;CAC5C,IAAI,kBAAkE,CAAC;CACvE,KAAK,MAAM,OAAO,UAAU;EAC1B,IAAI,WAAW,KAAK;GAClB,cAAc,KAAK,GAAG,yBAAyB,GAAG,CAAC;GACnD;EACF;EAEA,MAAM,OAAQ,IAAyB;EAEvC,IACE,SAAS,UACT,IAAI,cACJ,oBAAoB,IAAI,IAAI,UAAU,GAEtC;EAGF,IAAI,SAAS,aAAa;GACxB,MAAM,UAAW,IAA6B;GAC9C,IAAI,SAAS;IACX,MAAM,YAAY,mBAAmB,GAAG;IACxC,gBAAgB,KAAK;KACnB;KACA,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;IACjD,CAAC;GACH;GACA;EACF;EAEA,IAAI,SAAS,YACX;EAGF,IAAI,SAAS,aAAa;GACxB,cAAc,KAAK;IACjB,MAAM;IACN,SAAU,IAA4B;GACxC,CAAC;GACD;EACF;EAEA,IACE,SAAS,UACT,MAAM,QAAS,IAA8B,OAAO,GACpD;GACA,MAAM,UAAW,IAA6C;GAG9D,IACE,CAAC,QAAQ,MACN,SACC,KAAK,SAAS,UAAU,UAAU,QAAQ,EAAE,aAAa,KAC7D,GACA;IACA,cAAc,KAAK,GAAmB;IACtC;GACF;GAIA,MAAM,eAHQ,uBACZ,OAEmB,CAAA,CAAM,OAAO,aAAa;GAC/C,cAAc,KAAK;IACjB,MAAM;IACN,SAAS,qBAAqB,YAAY;IAC1C,GAAK,IAAwB,OAAO,KAAA,KAAa,EAC/C,IAAK,IAAuB,GAC9B;GACF,CAAC;GACD;EACF;EAEA,IAAI,SAAS,aAAa;GACxB,MAAM,SAAS;GACf,MAAM,mBAAmB,iBAAiB,GAAG,CAAC,EAAE;GAChD,MAAM,YAAY,OAAO,WAAW,KAAK,aACvC,iBAAiB,UAAU,mBAAmB,SAAS,GAAG,CAC5D;GACA,cAAc,KAAK;IACjB,GAAG;IACH,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;IAC/C,GAAI,gBAAgB,SAAS,IACzB,EACE,UAAU,CAAC,GAAI,OAAO,YAAY,CAAC,GAAI,GAAG,eAAe,EAC3D,IACA,CAAC;GACP,CAAC;GACD,kBAAkB,CAAC;GACnB;EACF;EAEA,cAAc,KAAK,GAAmB;CACxC;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,yBACd,WACqB;CAErB,IAAI,UAAU,SAAS,UACrB,OAAO,CAAC;CAKV,IAAI,UAAU,SAAS,aACrB,OAAO,CAAC,uBAAuB,SAAS,CAAC;CAI3C,OAAO,uBAAuB,SAAS;AACzC;;;;;AAMA,SAAS,uBAAuB,WAAoC;CAClE,MAAM,eAAmC,CAAC;CAC1C,KAAK,MAAM,QAAQ,UAAU,OAC3B,IAAI,cAAc,IAAI,GACpB,aAAa,KAAK,IAAI;CAI1B,OAAO;EACL,IAAI,UAAU;EACd,MAAM,UAAU;EAChB,SAAS,qBAAqB,YAAY;EAC1C,GAAI,UAAU,cAAc,KAAA,KAAa,EACvC,WAAW,UAAU,UACvB;CACF;AACF;AAiBA,SAAS,gBAAkC;CACzC,OAAO;EAAE,cAAc,CAAC;EAAG,WAAW,CAAC;CAAE;AAC3C;AAEA,SAAS,mBAAmB,MAA6B;CACvD,OACE,KAAK,UAAU,oBACf,KAAK,UAAU,cACf,KAAK,UAAU,wBACf,KAAK,UAAU,wBACf,KAAK,UAAU,WACf,KAAK,WAAW,KAAA;AAEpB;;;;;;;;;;AAWA,SAAS,uBAAuB,WAA2C;CAGzE,MAAM,cAAmC,CAAC;CAC1C,IAAI,UAAU,cAAc;CAC5B,IAAI,kBAAkE,CAAC;CAKvE,MAAM,uCAAuB,IAAI,IAAY;CAE7C,SAAS,eAAqB;EAC5B,MAAM,UAAU,qBAAqB,QAAQ,YAAY;EACzD,MAAM,aAAa,YAAY;EAC/B,MAAM,eAAe,QAAQ,UAAU,SAAS;EAChD,MAAM,cAAc,gBAAgB,SAAS;EAE7C,IAAI,cAAc,gBAAgB,aAAa;GAC7C,YAAY,KAAK;IACf,IAAI,UAAU;IACd,MAAM;IACN;IACA,GAAI,gBAAgB,EAAE,WAAW,QAAQ,UAAU;IACnD,GAAI,eAAe,EAAE,UAAU,gBAAgB;IAC/C,GAAI,QAAQ,oBAAoB,EAC9B,kBAAkB,QAAQ,iBAC5B;IACA,GAAI,UAAU,cAAc,KAAA,KAAa,EACvC,WAAW,UAAU,UACvB;GACF,CAAC;GACD,kBAAkB,CAAC;EACrB;EACA,UAAU,cAAc;CAC1B;CAEA,KAAK,MAAM,QAAQ,UAAU,OAC3B,QAAQ,KAAK,MAAb;EACE,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;EACL,KAAK;GACH,QAAQ,aAAa,KAAK,IAAI;GAC9B;EAEF,KAAK;GACH,IAAI,mBAAmB,IAAI,GACzB,QAAQ,UAAU,KAAK;IACrB,IAAI,KAAK;IACT,MAAM;IACN,UAAU;KACR,MAAM,KAAK;KACX,WAAW,KAAK;IAClB;IACA,GAAI,KAAK,aAAa,KAAA,KAAa,EAAE,UAAU,KAAK,SAAS;GAC/D,CAAC;GAEH;EAEF,KAAK;GAEH,aAAa;GAGb,KACG,KAAK,UAAU,cAAc,KAAK,UAAU,YAC7C,CAAC,qBAAqB,IAAI,KAAK,UAAU,GACzC;IACA,YAAY,KAAK;KACf,IAAI,UAAU;KACd,MAAM;KACN,SAAS,KAAK;KACd,YAAY,KAAK;KACjB,GAAI,UAAU,cAAc,KAAA,KAAa,EACvC,WAAW,UAAU,UACvB;IACF,CAAC;IACD,qBAAqB,IAAI,KAAK,UAAU;GAC1C;GACA;EAEF,KAAK;GACH,IAAI,KAAK,SAAS;IAGhB,IAAI,QAAQ,UAAU,KAAK,0BAA0B,GACnD,aAAa;IAEf,gBAAgB,KAAK;KACnB,SAAS,KAAK;KACd,GAAI,KAAK,aAAa,EAAE,WAAW,KAAK,UAAU;IACpD,CAAC;GACH;GACA;EAEF,KAAK,qBAKH,IAAI,KAAK,WAAW,YAAY;GAC9B,MAAM,aACJ,KAAK,QAAQ,KACT,KAAK,MACL,KAAK,SAAS,KAAA,IACZ,kBAAkB,KAAK,IAAI,IAC3B;GACR,IAAI,eAAe,IAAI;IACrB,QAAQ,aAAa,KAAK;KAAE,MAAM;KAAQ,SAAS;IAAW,CAAC;IAC/D,QAAQ,mBAAmB;GAC7B;EACF;CAUJ;CAIF,aAAa;CAMb,KAAK,MAAM,QAAQ,UAAU,OAAO;EAClC,IAAI,KAAK,SAAS,aAAa;EAI/B,IAAI,KAAK,WAAW,KAAA,KAAa,CAAC,qBAAqB,IAAI,KAAK,EAAE,GAAG;GACnE,YAAY,KAAK;IACf,IAAI,UAAU;IACd,MAAM;IACN,SAAS,oBAAoB,KAAK,MAAM;IACxC,YAAY,KAAK;IACjB,GAAI,UAAU,cAAc,KAAA,KAAa,EACvC,WAAW,UAAU,UACvB;GACF,CAAC;GACD,qBAAqB,IAAI,KAAK,EAAE;EAClC;EAGA,IACE,KAAK,WAAW,KAAA,KAChB,KAAK,UAAU,wBACf,KAAK,UAAU,aAAa,KAAA,KAC5B,CAAC,qBAAqB,IAAI,KAAK,EAAE,GACjC;GACA,MAAM,WAAW,KAAK,SAAS;GAC/B,YAAY,KAAK;IACf,IAAI,UAAU;IACd,MAAM;IACN,SAAS,KAAK,UAAU;KACtB;KACA,GAAI,YAAY,EAAE,kBAAkB,KAAK;KACzC,SAAS,WACL,8BACA;IACN,CAAC;IACD,YAAY,KAAK;IACjB,GAAI,UAAU,cAAc,KAAA,KAAa,EACvC,WAAW,UAAU,UACvB;GACF,CAAC;GACD,qBAAqB,IAAI,KAAK,EAAE;EAClC;CACF;CAGA,IAAI,YAAY,WAAW,GACzB,YAAY,KAAK;EACf,IAAI,UAAU;EACd,MAAM;EACN,SAAS;EACT,GAAI,UAAU,cAAc,KAAA,KAAa,EACvC,WAAW,UAAU,UACvB;CACF,CAAC;CAGH,OAAO;AACT;;;;;;;;;;;;;AAcA,SAAgB,wBACd,cACA,IACW;CACX,MAAM,QAA4B,CAAC;CAEnC,IAAI,aAAa,SAAS,eAAe,aAAa,UAAU,QAC9D,KAAK,MAAM,YAAY,aAAa,UAAU;EAC5C,IAAI,CAAC,SAAS,SAAS;EACvB,MAAM,KAAK;GACT,MAAM;GACN,SAAS,SAAS;GAClB,GAAI,SAAS,aAAa,EAAE,WAAW,SAAS,UAAU;EAC5D,CAAC;CACH;CAKF,IAAI,aAAa,SAAS,eAAe,aAAa,kBACpD,MAAM,KAAK,aAAa,gBAAgB;MACnC,IAAI,aAAa,SAAS,UAAU,aAAa,YACtD,MAAM,KAAK;EACT,MAAM;EACN,YAAY,aAAa;EACzB,SAAS,eAAe,aAAa,OAAO;EAC5C,OAAO;CACT,CAAC;MACI,IAAI,MAAM,QAAQ,aAAa,OAAO,GAE3C,KAAK,MAAM,QAAQ,aAAa,SAC9B,MAAM,KAAK,IAAI;MAEZ;EAEL,MAAM,cAAc,eAAe,aAAa,OAAO;EACvD,IAAI,aACF,MAAM,KAAK;GACT,MAAM;GACN,SAAS;EACX,CAAC;CAEL;CAGA,IAAI,aAAa,aAAa,aAAa,UAAU,SAAS,GAC5D,KAAK,MAAM,YAAY,aAAa,WAAW;EAG7C,IAAI;EACJ,IAAI;GACF,QAAQ,KAAK,MAAM,SAAS,SAAS,SAAS;EAChD,QAAQ;GACN,QAAQ,KAAA;EACV;EACA,MAAM,KAAK;GACT,MAAM;GACN,IAAI,SAAS;GACb,MAAM,SAAS,SAAS;GACxB,WAAW,SAAS,SAAS;GAC7B,OAAO;GACP,GAAI,UAAU,KAAA,KAAa,EAAE,MAAM;GACnC,GAAI,SAAS,aAAa,KAAA,KAAa,EAAE,UAAU,SAAS,SAAS;EACvE,CAAC;CACH;CAGF,OAAO;EACL,IAAI,MAAM,kBAAkB;EAC5B,MAAM,aAAa,SAAS,SAAS,cAAc,aAAa;EAChE;EACA,GAAI,aAAa,cAAc,KAAA,KAAa,EAC1C,WAAW,aAAa,UAC1B;CACF;AACF;;;;;;;;;;;;;;;;;AAkBA,SAAgB,+BACd,SACW;CACX,IAAI,WAAW,SACb,OAAO,sBAAsB,SAAS;EACpC,GAAG;EACH,IAAI,QAAQ,MAAM,kBAAkB;CACtC,CAAC;CAGH,MAAM,KAAK,QAAQ,MAAM,kBAAkB;CAE3C,QAAQ,QAAQ,MAAhB;EACE,KAAK,QACH,OAAO,sBAAsB,SAAS;GACpC;GACA,MAAM;GACN,OAAO,uBAAuB,QAAQ,OAAO;EAC/C,CAAC;EACH,KAAK,aAAa;GAChB,MAAM,mBAAmB,iBAAiB,OAAO,CAAC,EAAE;GACpD,MAAM,YAAY,QAAQ,WAAW,KAAK,aAAa;IACrD,MAAM,WACJ,oBAAoB,QAAQ,OAAO,qBAAqB,WACnD,iBAA6C,SAAS,MACvD,KAAA;IACN,OAAO,aAAa,KAAA,IAAY;KAAE,GAAG;KAAU;IAAS,IAAI;GAC9D,CAAC;GACD,OAAO,sBACL,SACA,wBACE;IACE,MAAM;IACN,SAAS,QAAQ,WAAW;IAC5B,GAAI,aAAa,EAAE,UAAU;GAC/B,GACA,EACF,CACF;EACF;EACA,KAAK,QACH,OAAO,sBACL,SACA,wBACE;GACE,MAAM;GACN,SAAS,QAAQ;GACjB,YAAY,QAAQ;EACtB,GACA,EACF,CACF;EACF,KAAK;EACL,KAAK,aAEH,OAAO,sBAAsB,SAAS;GACpC;GACA,MAAM;GACN,OAAO,QAAQ,UACX,CAAC;IAAE,MAAM;IAAQ,SAAS,QAAQ;GAAQ,CAAC,IAC3C,CAAC;EACP,CAAC;EACH,KAAK,aAAa;GAChB,MAAM,YAAY,mBAAmB,OAAO;GAC5C,OAAO,sBAAsB,SAAS;IACpC;IACA,MAAM;IACN,OAAO,QAAQ,UACX,CACE;KACE,MAAM;KACN,SAAS,QAAQ;KACjB,GAAI,cAAc,KAAA,IAAY,EAAE,UAAU,IAAI,CAAC;IACjD,CACF,IACA,CAAC;GACP,CAAC;EACH;EAEA,SAEE,OAAO,sBAAsB,SAAS;GACpC;GACA,MAAM;GACN,OAAO,CAAC;EACV,CAAC;CACL;AACF;;AAGA,SAAS,sBAAsB,QAAgB,IAA0B;CACvE,IAAI,EAAE,cAAc,SAAS,OAAO;CACpC,MAAM,MAAM,OAAO;CACnB,IAAI,OAAO,QAAQ,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG,OAAO;CACzE,MAAM,WAAW;CACjB,MAAM,eAAe,iBAAiB,QAAQ,CAAC,EAAE;CACjD,MAAM,YACJ,OAAO,iBAAiB,WAAW,IAAI,KAAK,YAAY,IAAI,KAAA;CAC9D,MAAM,iBACJ,cAAc,KAAA,KAAa,CAAC,OAAO,MAAM,UAAU,QAAQ,CAAC;CAC9D,OAAO;EACL,GAAG;EACH;EACA,GAAI,iBAAiB,EAAE,UAAU,IAAI,CAAC;CACxC;AACF;;;;;;;;;;AAWA,SAAS,uBACP,SACoB;CACpB,IAAI,OAAO,YAAY,UACrB,OAAO,UAAU,CAAC;EAAE,MAAM;EAAQ;CAAQ,CAAC,IAAI,CAAC;CAGlD,MAAM,QAA4B,CAAC;CACnC,KAAK,MAAM,QAAQ,SACjB,IAAI,KAAK,SAAS,QAChB,MAAM,KAAK;EAAE,MAAM;EAAQ,SAAS,KAAK;CAAK,CAAC;MAC1C,IAAI,KAAK,SAAS,UACvB,MAAM,KAAK,IAAI;CAGnB,OAAO;AACT;;;;;;;;;AAUA,SAAgB,0BACd,eACkB;CAClB,MAAM,aAA+B,CAAC;CACtC,IAAI,0BAA4C;CAEhD,KAAK,MAAM,OAAO,eAChB,IAAI,IAAI,SAAS,QAAQ;EAEvB,IACE,IAAI,eAAe,KAAA,KACnB,2BACA,wBAAwB,SAAS,aACjC;GACA,MAAM,UAAU,eAAe,IAAI,OAAO;GAC1C,MAAM,eAAe,wBAAwB,MAAM,MAChD,SACC,KAAK,SAAS,eAAe,KAAK,OAAO,IAAI,UACjD;GAEA,IAAI,cAAc;IAChB,aAAa,SAAS,uBAAuB,OAAO;IACpD,aAAa,QAAQ;GACvB;GAEA,wBAAwB,MAAM,KAAK;IACjC,MAAM;IACN,YAAY,IAAI;IAChB;IACA,OAAO;GACT,CAAC;EACH,OAAO;GAEL,MAAM,sBAAsB,wBAAwB,KAAK,IAAI,EAAE;GAC/D,WAAW,KAAK,mBAAmB;EACrC;CACF,OAAO;EAGL,MAAM,YAAY,wBAAwB,KAAK,IAAI,EAAE;EACrD,WAAW,KAAK,SAAS;EAGzB,IAAI,IAAI,SAAS,aACf,0BAA0B;OAE1B,0BAA0B;CAE9B;CAGF,OAAO;AACT;;;;;;;;;AAUA,SAAgB,qBACd,SACA,YACW;CACX,IAAI,WAAW,SAEb,OAAO;EACL,GAAG;EACH,IAAI,QAAQ,MAAM,WAAW;EAC7B,WAAW,QAAQ,6BAAa,IAAI,KAAK;CAC3C;MAGA,OAAO;EACL,GAAG,wBAAwB,SAAS,WAAW,CAAC;EAChD,WAAW,QAAQ,6BAAa,IAAI,KAAK;CAC3C;AAEJ;;;;AAKA,SAAgB,oBAA4B;CAC1C,OAAO,OAAO,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,UAAU,CAAC;AACpE"}
@@ -98,6 +98,7 @@ export declare class StreamProcessor {
98
98
  *
99
99
  * @param content - The message content (string or array of content parts)
100
100
  * @param id - Optional custom message ID (generated if not provided)
101
+ * @param metadata - Optional AG-UI metadata bag
101
102
  * @returns The created UIMessage
102
103
  *
103
104
  * @example
@@ -115,7 +116,7 @@ export declare class StreamProcessor {
115
116
  * processor.addUserMessage('Hello!', 'custom-id-123')
116
117
  * ```
117
118
  */
118
- addUserMessage(content: string | Array<ContentPart>, id?: string): UIMessage;
119
+ addUserMessage(content: string | Array<ContentPart>, id?: string, metadata?: UIMessage['metadata']): UIMessage;
119
120
  /**
120
121
  * Prepare for a new assistant message stream.
121
122
  * Does NOT create the message immediately -- the message is created lazily
@@ -208,6 +209,13 @@ export declare class StreamProcessor {
208
209
  * hydrate state from the existing message rather than creating a duplicate.
209
210
  */
210
211
  private ensureAssistantMessage;
212
+ /**
213
+ * Merge event metadata onto a UIMessage. `tanstack` is deep-merged so a
214
+ * later delta does not wipe `tanstack.model`. High-frequency leftover
215
+ * keys (`content`, `args`) never stamp onto the message.
216
+ * Rebuilds `createdAt` when `tanstack.createdAt` is an ISO string.
217
+ */
218
+ private mergeMessageMetadata;
211
219
  /**
212
220
  * Handle TEXT_MESSAGE_START event
213
221
  */
@@ -243,6 +251,14 @@ export declare class StreamProcessor {
243
251
  * matching `tool-call` (and prefer pre-snapshot complete/output when the
244
252
  * snapshot is poorer) so server tools keep the same UI shape as client tools.
245
253
  */
254
+ /**
255
+ * Wire order is reasoning fan-outs, then the assistant anchor.
256
+ * Snapshot conversion turns each reasoning row into its own assistant
257
+ * message. Fold leading thinking-only messages into the next real
258
+ * assistant. Do not fold into a tool-result-only message (`role: 'tool'`
259
+ * on the wire). `reconcileSnapshotToolCalls` anchors those results.
260
+ */
261
+ private mergeReasoningFanOut;
246
262
  private reconcileSnapshotToolCalls;
247
263
  /**
248
264
  * Post-pass (c): fold `tool-result` content into sibling `tool-call` parts
@@ -290,17 +306,15 @@ export declare class StreamProcessor {
290
306
  */
291
307
  private handleToolCallArgsEvent;
292
308
  /**
293
- * Handle TOOL_CALL_END event — authoritative signal that a tool call's input is finalized.
309
+ * Handle TOOL_CALL_END event — arguments are finalized (input-complete).
310
+ * Tool output arrives on TOOL_CALL_RESULT, not on this event.
294
311
  *
295
- * This event has a DUAL ROLE:
296
- * - Without `result`: Signals arguments are done (from adapter). Transitions to input-complete.
297
- * - With `result`: Signals tool was executed and result is available (from TextEngine).
298
- * Creates both output on the tool-call part AND a tool-result part.
312
+ * If TOOL_CALL_END carries parsed `input`, use it as the canonical arguments:
313
+ * back-fill the accumulated string when no TOOL_CALL_ARGS deltas were seen
314
+ * (adapters that deliver the whole input on END — e.g. Anthropic
315
+ * server_tool_use / web_search — issue #839) and override the rendered part's
316
+ * `input` with the canonical value.
299
317
  *
300
- * If `input` is provided, it overrides the accumulated string parse as the
301
- * canonical parsed arguments.
302
- *
303
- * @see docs/chat-architecture.md#tool-results-and-the-tool_call_end-dual-role — Full explanation
304
318
  * @see docs/chat-architecture.md#single-shot-tool-call-response — End-to-end flow
305
319
  */
306
320
  private handleToolCallEndEvent;
@@ -340,18 +354,18 @@ export declare class StreamProcessor {
340
354
  /**
341
355
  * Handle STEP_STARTED event (for thinking/reasoning content).
342
356
  *
343
- * Records the stepId so that subsequent STEP_FINISHED deltas accumulate
357
+ * Records the stepId so later REASONING_MESSAGE_CONTENT deltas accumulate
344
358
  * into their own ThinkingPart. Does not create a message — the message
345
- * is lazily created when the first STEP_FINISHED content arrives.
359
+ * is lazily created when the first REASONING_MESSAGE_CONTENT arrives.
346
360
  */
347
361
  private handleStepStartedEvent;
348
362
  /**
349
- * Handle STEP_FINISHED event (for thinking/reasoning content).
350
- *
351
- * Accumulates delta into the current thinking step's content and updates
352
- * the corresponding ThinkingPart in the UIMessage.
363
+ * Handle STEP_FINISHED event.
353
364
  *
354
- * @see docs/chat-architecture.md#thinkingreasoning-content — Thinking flow
365
+ * Thinking *content* comes from REASONING_MESSAGE_CONTENT, not STEP_FINISHED.
366
+ * But some adapters (e.g. BytePlus thinking-summary) carry the provider
367
+ * signature blob ONLY on the STEP_FINISHED event, so still extract that here
368
+ * and attach it to the thinking step the reasoning events already built.
355
369
  */
356
370
  private handleStepFinishedEvent;
357
371
  /**
@@ -361,6 +375,13 @@ export declare class StreamProcessor {
361
375
  * corresponding ThinkingPart in the UIMessage.
362
376
  */
363
377
  private handleReasoningMessageContentEvent;
378
+ /**
379
+ * Attach a provider signature blob from REASONING_ENCRYPTED_VALUE.
380
+ * `subtype: 'message'` updates ThinkingPart.signature.
381
+ * `subtype: 'tool-call'` stores Gemini thoughtSignature on the tool-call part.
382
+ */
383
+ private handleReasoningEncryptedValueEvent;
384
+ private attachToolCallSignature;
364
385
  /**
365
386
  * Handle CUSTOM event.
366
387
  *