@tanstack/ai 0.42.0 → 0.43.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 (272) hide show
  1. package/README.md +15 -1
  2. package/dist/esm/activities/chat/adapter.js +23 -16
  3. package/dist/esm/activities/chat/adapter.js.map +1 -1
  4. package/dist/esm/activities/chat/agent-loop-strategies.d.ts +5 -36
  5. package/dist/esm/activities/chat/agent-loop-strategies.js +75 -21
  6. package/dist/esm/activities/chat/agent-loop-strategies.js.map +1 -1
  7. package/dist/esm/activities/chat/cancel.d.ts +40 -0
  8. package/dist/esm/activities/chat/cancel.js +54 -0
  9. package/dist/esm/activities/chat/cancel.js.map +1 -0
  10. package/dist/esm/activities/chat/index.d.ts +28 -21
  11. package/dist/esm/activities/chat/index.js +2100 -1813
  12. package/dist/esm/activities/chat/index.js.map +1 -1
  13. package/dist/esm/activities/chat/mcp/manager.d.ts +2 -2
  14. package/dist/esm/activities/chat/mcp/manager.js +90 -77
  15. package/dist/esm/activities/chat/mcp/manager.js.map +1 -1
  16. package/dist/esm/activities/chat/mcp/types.d.ts +2 -2
  17. package/dist/esm/activities/chat/messages.js +397 -346
  18. package/dist/esm/activities/chat/messages.js.map +1 -1
  19. package/dist/esm/activities/chat/middleware/builder.js +17 -15
  20. package/dist/esm/activities/chat/middleware/builder.js.map +1 -1
  21. package/dist/esm/activities/chat/middleware/capabilities.js +78 -43
  22. package/dist/esm/activities/chat/middleware/capabilities.js.map +1 -1
  23. package/dist/esm/activities/chat/middleware/compose.d.ts +94 -1
  24. package/dist/esm/activities/chat/middleware/compose.js +623 -531
  25. package/dist/esm/activities/chat/middleware/compose.js.map +1 -1
  26. package/dist/esm/activities/chat/middleware/define.js +12 -5
  27. package/dist/esm/activities/chat/middleware/define.js.map +1 -1
  28. package/dist/esm/activities/chat/middleware/index.d.ts +5 -1
  29. package/dist/esm/activities/chat/middleware/locks.d.ts +50 -0
  30. package/dist/esm/activities/chat/middleware/locks.js +71 -0
  31. package/dist/esm/activities/chat/middleware/locks.js.map +1 -0
  32. package/dist/esm/activities/chat/middleware/pending-turn.d.ts +15 -0
  33. package/dist/esm/activities/chat/middleware/pending-turn.js +35 -0
  34. package/dist/esm/activities/chat/middleware/pending-turn.js.map +1 -0
  35. package/dist/esm/activities/chat/middleware/run-disconnect.d.ts +23 -0
  36. package/dist/esm/activities/chat/middleware/run-disconnect.js +42 -0
  37. package/dist/esm/activities/chat/middleware/run-disconnect.js.map +1 -0
  38. package/dist/esm/activities/chat/middleware/run-store.d.ts +283 -0
  39. package/dist/esm/activities/chat/middleware/run-store.js +176 -0
  40. package/dist/esm/activities/chat/middleware/run-store.js.map +1 -0
  41. package/dist/esm/activities/chat/middleware/sandbox-runtime.js +14 -8
  42. package/dist/esm/activities/chat/middleware/sandbox-runtime.js.map +1 -1
  43. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js +79 -70
  44. package/dist/esm/activities/chat/middleware/tool-cache-middleware.js.map +1 -1
  45. package/dist/esm/activities/chat/middleware/types.d.ts +59 -2
  46. package/dist/esm/activities/chat/middleware/validate.js +23 -28
  47. package/dist/esm/activities/chat/middleware/validate.js.map +1 -1
  48. package/dist/esm/activities/chat/stream/json-parser.js +39 -25
  49. package/dist/esm/activities/chat/stream/json-parser.js.map +1 -1
  50. package/dist/esm/activities/chat/stream/message-updaters.js +275 -234
  51. package/dist/esm/activities/chat/stream/message-updaters.js.map +1 -1
  52. package/dist/esm/activities/chat/stream/processor.d.ts +24 -4
  53. package/dist/esm/activities/chat/stream/processor.js +1341 -1542
  54. package/dist/esm/activities/chat/stream/processor.js.map +1 -1
  55. package/dist/esm/activities/chat/stream/strategies.js +69 -53
  56. package/dist/esm/activities/chat/stream/strategies.js.map +1 -1
  57. package/dist/esm/activities/chat/tools/approval-schema.d.ts +19 -0
  58. package/dist/esm/activities/chat/tools/approval-schema.js +117 -0
  59. package/dist/esm/activities/chat/tools/approval-schema.js.map +1 -0
  60. package/dist/esm/activities/chat/tools/lazy-tool-manager.js +164 -191
  61. package/dist/esm/activities/chat/tools/lazy-tool-manager.js.map +1 -1
  62. package/dist/esm/activities/chat/tools/lazy-tools.js +24 -12
  63. package/dist/esm/activities/chat/tools/lazy-tools.js.map +1 -1
  64. package/dist/esm/activities/chat/tools/schema-converter.js +293 -146
  65. package/dist/esm/activities/chat/tools/schema-converter.js.map +1 -1
  66. package/dist/esm/activities/chat/tools/tool-calls.d.ts +18 -2
  67. package/dist/esm/activities/chat/tools/tool-calls.js +522 -531
  68. package/dist/esm/activities/chat/tools/tool-calls.js.map +1 -1
  69. package/dist/esm/activities/chat/tools/tool-definition.d.ts +75 -16
  70. package/dist/esm/activities/chat/tools/tool-definition.js +95 -23
  71. package/dist/esm/activities/chat/tools/tool-definition.js.map +1 -1
  72. package/dist/esm/activities/error-payload.js +85 -47
  73. package/dist/esm/activities/error-payload.js.map +1 -1
  74. package/dist/esm/activities/generateAudio/adapter.js +22 -15
  75. package/dist/esm/activities/generateAudio/adapter.js.map +1 -1
  76. package/dist/esm/activities/generateAudio/index.d.ts +4 -0
  77. package/dist/esm/activities/generateAudio/index.js +141 -105
  78. package/dist/esm/activities/generateAudio/index.js.map +1 -1
  79. package/dist/esm/activities/generateImage/adapter.js +22 -15
  80. package/dist/esm/activities/generateImage/adapter.js.map +1 -1
  81. package/dist/esm/activities/generateImage/index.d.ts +4 -0
  82. package/dist/esm/activities/generateImage/index.js +155 -111
  83. package/dist/esm/activities/generateImage/index.js.map +1 -1
  84. package/dist/esm/activities/generateSpeech/adapter.js +22 -15
  85. package/dist/esm/activities/generateSpeech/adapter.js.map +1 -1
  86. package/dist/esm/activities/generateSpeech/index.d.ts +4 -0
  87. package/dist/esm/activities/generateSpeech/index.js +159 -110
  88. package/dist/esm/activities/generateSpeech/index.js.map +1 -1
  89. package/dist/esm/activities/generateTranscription/adapter.js +22 -15
  90. package/dist/esm/activities/generateTranscription/adapter.js.map +1 -1
  91. package/dist/esm/activities/generateTranscription/index.d.ts +4 -0
  92. package/dist/esm/activities/generateTranscription/index.js +159 -100
  93. package/dist/esm/activities/generateTranscription/index.js.map +1 -1
  94. package/dist/esm/activities/generateVideo/adapter.js +36 -29
  95. package/dist/esm/activities/generateVideo/adapter.js.map +1 -1
  96. package/dist/esm/activities/generateVideo/index.d.ts +143 -19
  97. package/dist/esm/activities/generateVideo/index.js +456 -279
  98. package/dist/esm/activities/generateVideo/index.js.map +1 -1
  99. package/dist/esm/activities/generateVideo/snap.js +60 -48
  100. package/dist/esm/activities/generateVideo/snap.js.map +1 -1
  101. package/dist/esm/activities/index.js +8 -34
  102. package/dist/esm/activities/middleware/index.d.ts +1 -1
  103. package/dist/esm/activities/middleware/run.d.ts +10 -0
  104. package/dist/esm/activities/middleware/run.js +53 -29
  105. package/dist/esm/activities/middleware/run.js.map +1 -1
  106. package/dist/esm/activities/middleware/types.d.ts +44 -6
  107. package/dist/esm/activities/stream-generation-result.d.ts +4 -1
  108. package/dist/esm/activities/stream-generation-result.js +79 -44
  109. package/dist/esm/activities/stream-generation-result.js.map +1 -1
  110. package/dist/esm/activities/summarize/adapter.js +22 -15
  111. package/dist/esm/activities/summarize/adapter.js.map +1 -1
  112. package/dist/esm/activities/summarize/chat-stream-summarize.js +252 -202
  113. package/dist/esm/activities/summarize/chat-stream-summarize.js.map +1 -1
  114. package/dist/esm/activities/summarize/index.d.ts +27 -0
  115. package/dist/esm/activities/summarize/index.js +268 -102
  116. package/dist/esm/activities/summarize/index.js.map +1 -1
  117. package/dist/esm/adapter-internals.d.ts +2 -1
  118. package/dist/esm/adapter-internals.js +4 -11
  119. package/dist/esm/client.d.ts +25 -3
  120. package/dist/esm/client.js +131 -64
  121. package/dist/esm/client.js.map +1 -1
  122. package/dist/esm/custom-events.d.ts +76 -0
  123. package/dist/esm/custom-events.js +37 -0
  124. package/dist/esm/custom-events.js.map +1 -0
  125. package/dist/esm/delivery-detach.d.ts +50 -0
  126. package/dist/esm/delivery-detach.js +71 -0
  127. package/dist/esm/delivery-detach.js.map +1 -0
  128. package/dist/esm/delivery-disconnect.d.ts +62 -0
  129. package/dist/esm/delivery-disconnect.js +81 -0
  130. package/dist/esm/delivery-disconnect.js.map +1 -0
  131. package/dist/esm/extend-adapter.js +19 -17
  132. package/dist/esm/extend-adapter.js.map +1 -1
  133. package/dist/esm/index.d.ts +24 -6
  134. package/dist/esm/index.js +30 -98
  135. package/dist/esm/interrupt-resume.d.ts +71 -0
  136. package/dist/esm/interrupt-resume.js +438 -0
  137. package/dist/esm/interrupt-resume.js.map +1 -0
  138. package/dist/esm/interrupt-serialization.d.ts +12 -0
  139. package/dist/esm/interrupt-serialization.js +178 -0
  140. package/dist/esm/interrupt-serialization.js.map +1 -0
  141. package/dist/esm/interrupts.d.ts +84 -0
  142. package/dist/esm/interrupts.js +31 -0
  143. package/dist/esm/interrupts.js.map +1 -0
  144. package/dist/esm/locks.d.ts +10 -0
  145. package/dist/esm/locks.js +2 -0
  146. package/dist/esm/logger/console-logger.js +101 -78
  147. package/dist/esm/logger/console-logger.js.map +1 -1
  148. package/dist/esm/logger/internal-logger.js +104 -89
  149. package/dist/esm/logger/internal-logger.js.map +1 -1
  150. package/dist/esm/logger/resolve.js +54 -49
  151. package/dist/esm/logger/resolve.js.map +1 -1
  152. package/dist/esm/logger/types.d.ts +1 -1
  153. package/dist/esm/middlewares/content-guard.js +142 -148
  154. package/dist/esm/middlewares/content-guard.js.map +1 -1
  155. package/dist/esm/middlewares/index.js +2 -6
  156. package/dist/esm/middlewares/otel.js +598 -732
  157. package/dist/esm/middlewares/otel.js.map +1 -1
  158. package/dist/esm/middlewares/usage-attributes.js +47 -40
  159. package/dist/esm/middlewares/usage-attributes.js.map +1 -1
  160. package/dist/esm/realtime/event-emitter.js +24 -25
  161. package/dist/esm/realtime/event-emitter.js.map +1 -1
  162. package/dist/esm/realtime/index.d.ts +5 -9
  163. package/dist/esm/realtime/index.js +29 -6
  164. package/dist/esm/realtime/index.js.map +1 -1
  165. package/dist/esm/scope.d.ts +47 -0
  166. package/dist/esm/stream-durability.d.ts +171 -0
  167. package/dist/esm/stream-durability.js +295 -0
  168. package/dist/esm/stream-durability.js.map +1 -0
  169. package/dist/esm/stream-to-response.d.ts +178 -13
  170. package/dist/esm/stream-to-response.js +663 -115
  171. package/dist/esm/stream-to-response.js.map +1 -1
  172. package/dist/esm/strip-to-spec-middleware.js +30 -16
  173. package/dist/esm/strip-to-spec-middleware.js.map +1 -1
  174. package/dist/esm/system-prompts.js +27 -21
  175. package/dist/esm/system-prompts.js.map +1 -1
  176. package/dist/esm/tool-registry.js +72 -45
  177. package/dist/esm/tool-registry.js.map +1 -1
  178. package/dist/esm/tools/provider-tool.js +14 -5
  179. package/dist/esm/tools/provider-tool.js.map +1 -1
  180. package/dist/esm/types.d.ts +321 -42
  181. package/dist/esm/types.js +2 -0
  182. package/dist/esm/utilities/ag-ui-wire.js +79 -93
  183. package/dist/esm/utilities/ag-ui-wire.js.map +1 -1
  184. package/dist/esm/utilities/chat-params.d.ts +26 -4
  185. package/dist/esm/utilities/chat-params.js +218 -92
  186. package/dist/esm/utilities/chat-params.js.map +1 -1
  187. package/dist/esm/utilities/errors.js +28 -18
  188. package/dist/esm/utilities/errors.js.map +1 -1
  189. package/dist/esm/utilities/media-prompt.js +46 -41
  190. package/dist/esm/utilities/media-prompt.js.map +1 -1
  191. package/dist/esm/utilities/numbers.js +13 -10
  192. package/dist/esm/utilities/numbers.js.map +1 -1
  193. package/dist/esm/utilities/provider-executed.js +20 -11
  194. package/dist/esm/utilities/provider-executed.js.map +1 -1
  195. package/dist/esm/utilities/sampling-keys.js +31 -19
  196. package/dist/esm/utilities/sampling-keys.js.map +1 -1
  197. package/dist/esm/utilities/tool-result.js +42 -30
  198. package/dist/esm/utilities/tool-result.js.map +1 -1
  199. package/dist/esm/utilities/usage.js +27 -9
  200. package/dist/esm/utilities/usage.js.map +1 -1
  201. package/dist/esm/utils.js +26 -18
  202. package/dist/esm/utils.js.map +1 -1
  203. package/package.json +10 -6
  204. package/skills/ai-core/SKILL.md +69 -18
  205. package/skills/ai-core/adapter-configuration/SKILL.md +44 -21
  206. package/skills/ai-core/adapter-configuration/references/anthropic-adapter.md +1 -3
  207. package/skills/ai-core/adapter-configuration/references/byteplus-adapter.md +148 -0
  208. package/skills/ai-core/adapter-configuration/references/gemini-adapter.md +2 -6
  209. package/skills/ai-core/adapter-configuration/references/groq-adapter.md +2 -6
  210. package/skills/ai-core/adapter-configuration/references/openai-adapter.md +1 -3
  211. package/skills/ai-core/ag-ui-protocol/SKILL.md +1 -1
  212. package/skills/ai-core/chat-experience/SKILL.md +98 -11
  213. package/skills/ai-core/client-persistence/SKILL.md +277 -0
  214. package/skills/ai-core/custom-backend-integration/SKILL.md +1 -1
  215. package/skills/ai-core/debug-logging/SKILL.md +1 -1
  216. package/skills/ai-core/locks/SKILL.md +143 -0
  217. package/skills/ai-core/media-generation/SKILL.md +144 -12
  218. package/skills/ai-core/middleware/SKILL.md +258 -33
  219. package/skills/ai-core/structured-outputs/SKILL.md +1 -1
  220. package/skills/ai-core/tool-calling/SKILL.md +54 -61
  221. package/src/activities/chat/agent-loop-strategies.ts +5 -39
  222. package/src/activities/chat/cancel.ts +81 -0
  223. package/src/activities/chat/index.ts +1091 -200
  224. package/src/activities/chat/mcp/manager.ts +4 -4
  225. package/src/activities/chat/mcp/types.ts +2 -2
  226. package/src/activities/chat/messages.ts +5 -3
  227. package/src/activities/chat/middleware/builder.ts +1 -1
  228. package/src/activities/chat/middleware/compose.ts +186 -9
  229. package/src/activities/chat/middleware/index.ts +26 -0
  230. package/src/activities/chat/middleware/locks.ts +102 -0
  231. package/src/activities/chat/middleware/pending-turn.ts +47 -0
  232. package/src/activities/chat/middleware/run-disconnect.ts +62 -0
  233. package/src/activities/chat/middleware/run-store.ts +412 -0
  234. package/src/activities/chat/middleware/types.ts +62 -1
  235. package/src/activities/chat/stream/processor.ts +189 -5
  236. package/src/activities/chat/tools/approval-schema.ts +205 -0
  237. package/src/activities/chat/tools/tool-calls.ts +106 -13
  238. package/src/activities/chat/tools/tool-definition.ts +210 -39
  239. package/src/activities/generateAudio/index.ts +20 -3
  240. package/src/activities/generateImage/index.ts +20 -3
  241. package/src/activities/generateSpeech/index.ts +25 -3
  242. package/src/activities/generateTranscription/index.ts +26 -3
  243. package/src/activities/generateVideo/index.ts +345 -82
  244. package/src/activities/middleware/index.ts +2 -0
  245. package/src/activities/middleware/run.ts +31 -0
  246. package/src/activities/middleware/types.ts +49 -5
  247. package/src/activities/stream-generation-result.ts +30 -2
  248. package/src/activities/summarize/chat-stream-summarize.ts +5 -0
  249. package/src/activities/summarize/index.ts +200 -10
  250. package/src/adapter-internals.ts +10 -1
  251. package/src/client.ts +244 -0
  252. package/src/custom-events.ts +107 -0
  253. package/src/delivery-detach.ts +72 -0
  254. package/src/delivery-disconnect.ts +84 -0
  255. package/src/index.ts +138 -1
  256. package/src/interrupt-resume.ts +824 -0
  257. package/src/interrupt-serialization.ts +183 -0
  258. package/src/interrupts.ts +146 -0
  259. package/src/locks.ts +17 -0
  260. package/src/logger/types.ts +1 -1
  261. package/src/middlewares/otel.ts +1 -0
  262. package/src/realtime/index.ts +5 -9
  263. package/src/scope.ts +47 -0
  264. package/src/stream-durability.ts +598 -0
  265. package/src/stream-to-response.ts +1051 -95
  266. package/src/strip-to-spec-middleware.ts +3 -3
  267. package/src/types.ts +405 -45
  268. package/src/utilities/chat-params.ts +245 -55
  269. package/dist/esm/activities/index.js.map +0 -1
  270. package/dist/esm/adapter-internals.js.map +0 -1
  271. package/dist/esm/index.js.map +0 -1
  272. package/dist/esm/middlewares/index.js.map +0 -1
@@ -1,381 +1,432 @@
1
1
  import { normalizeToolResult } from "../../utilities/tool-result.js";
2
+ //#region src/activities/chat/messages.ts
3
+ /**
4
+ * Check if a MessagePart is a content part (text, image, audio, video, document)
5
+ * that maps directly to a ModelMessage ContentPart.
6
+ */
2
7
  function isContentPart(part) {
3
- return part.type === "text" || part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document";
8
+ return part.type === "text" || part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document";
4
9
  }
5
10
  function safeJsonStringify(value) {
6
- try {
7
- return JSON.stringify(value);
8
- } catch {
9
- return "";
10
- }
11
+ try {
12
+ return JSON.stringify(value);
13
+ } catch {
14
+ return "";
15
+ }
11
16
  }
12
17
  function parseToolResultContent(content) {
13
- try {
14
- return JSON.parse(content);
15
- } catch {
16
- return content;
17
- }
18
+ try {
19
+ return JSON.parse(content);
20
+ } catch {
21
+ return content;
22
+ }
18
23
  }
24
+ /**
25
+ * Collapse an array of ContentParts into the most compact ModelMessage content:
26
+ * - Empty array → null
27
+ * - All text parts → joined string (or null if empty)
28
+ * - Mixed content → ContentPart array as-is
29
+ */
19
30
  function collapseContentParts(parts) {
20
- if (parts.length === 0) return null;
21
- const allText = parts.every((p) => p.type === "text");
22
- if (allText) {
23
- const joined = parts.map((p) => p.content).join("");
24
- return joined || null;
25
- }
26
- return parts;
31
+ if (parts.length === 0) return null;
32
+ if (parts.every((p) => p.type === "text")) return parts.map((p) => p.content).join("") || null;
33
+ return parts;
27
34
  }
35
+ /**
36
+ * Extract text content from ModelMessage content (string, null, or ContentPart array).
37
+ * Used when only the text portion is needed (e.g., tool result content).
38
+ */
28
39
  function getTextContent(content) {
29
- if (content === null) return "";
30
- if (typeof content === "string") return content;
31
- return content.filter((part) => part.type === "text").map((part) => part.content).join("");
40
+ if (content === null) return "";
41
+ if (typeof content === "string") return content;
42
+ return content.filter((part) => part.type === "text").map((part) => part.content).join("");
32
43
  }
44
+ /**
45
+ * Convert UIMessages or ModelMessages to ModelMessages
46
+ */
33
47
  function convertMessagesToModelMessages(messages) {
34
- const anchoredToolCallIds = /* @__PURE__ */ new Set();
35
- for (const msg of messages) {
36
- if ("parts" in msg) {
37
- for (const part of msg.parts) {
38
- if (part.type === "tool-result") {
39
- anchoredToolCallIds.add(part.toolCallId);
40
- }
41
- }
42
- }
43
- }
44
- const modelMessages = [];
45
- for (const msg of messages) {
46
- if ("parts" in msg) {
47
- modelMessages.push(...uiMessageToModelMessages(msg));
48
- continue;
49
- }
50
- const role = msg.role;
51
- if (role === "tool" && msg.toolCallId && anchoredToolCallIds.has(msg.toolCallId)) {
52
- continue;
53
- }
54
- if (role === "reasoning" || role === "activity") {
55
- continue;
56
- }
57
- if (role === "developer") {
58
- modelMessages.push({
59
- role: "system",
60
- content: msg.content
61
- });
62
- continue;
63
- }
64
- modelMessages.push(msg);
65
- }
66
- return modelMessages;
48
+ const anchoredToolCallIds = /* @__PURE__ */ new Set();
49
+ for (const msg of messages) if ("parts" in msg) {
50
+ for (const part of msg.parts) if (part.type === "tool-result") anchoredToolCallIds.add(part.toolCallId);
51
+ }
52
+ const modelMessages = [];
53
+ for (const msg of messages) {
54
+ if ("parts" in msg) {
55
+ modelMessages.push(...uiMessageToModelMessages(msg));
56
+ continue;
57
+ }
58
+ const role = msg.role;
59
+ if (role === "tool" && msg.toolCallId && anchoredToolCallIds.has(msg.toolCallId)) continue;
60
+ if (role === "reasoning" || role === "activity") continue;
61
+ if (role === "developer") {
62
+ modelMessages.push({
63
+ role: "system",
64
+ content: msg.content
65
+ });
66
+ continue;
67
+ }
68
+ modelMessages.push(msg);
69
+ }
70
+ return modelMessages;
67
71
  }
72
+ /**
73
+ * Convert a UIMessage to ModelMessage(s)
74
+ *
75
+ * Walks the parts array IN ORDER to preserve the interleaving of text,
76
+ * tool calls, and tool results. This is critical for multi-round tool
77
+ * flows where the model generates text, calls a tool, gets the result,
78
+ * then generates more text and calls another tool.
79
+ *
80
+ * The output preserves the sequential structure:
81
+ * text1 → toolCall1 → toolResult1 → text2 → toolCall2 → toolResult2
82
+ * becomes:
83
+ * assistant: {content: "text1", toolCalls: [toolCall1]}
84
+ * tool: toolResult1
85
+ * assistant: {content: "text2", toolCalls: [toolCall2]}
86
+ * tool: toolResult2
87
+ *
88
+ * @param uiMessage - The UIMessage to convert
89
+ * @returns An array of ModelMessages preserving part ordering
90
+ */
68
91
  function uiMessageToModelMessages(uiMessage) {
69
- if (uiMessage.role === "system") {
70
- return [];
71
- }
72
- if (uiMessage.role !== "assistant") {
73
- return [buildUserOrToolMessage(uiMessage)];
74
- }
75
- return buildAssistantMessages(uiMessage);
92
+ if (uiMessage.role === "system") return [];
93
+ if (uiMessage.role !== "assistant") return [buildUserOrToolMessage(uiMessage)];
94
+ return buildAssistantMessages(uiMessage);
76
95
  }
96
+ /**
97
+ * Build a single ModelMessage for user messages (simple path).
98
+ * Preserves ordering of text and multimodal content parts.
99
+ */
77
100
  function buildUserOrToolMessage(uiMessage) {
78
- const contentParts = [];
79
- for (const part of uiMessage.parts) {
80
- if (isContentPart(part)) {
81
- contentParts.push(part);
82
- }
83
- }
84
- return {
85
- role: uiMessage.role,
86
- content: collapseContentParts(contentParts)
87
- };
101
+ const contentParts = [];
102
+ for (const part of uiMessage.parts) if (isContentPart(part)) contentParts.push(part);
103
+ return {
104
+ role: uiMessage.role,
105
+ content: collapseContentParts(contentParts)
106
+ };
88
107
  }
89
108
  function createSegment() {
90
- return { contentParts: [], toolCalls: [] };
109
+ return {
110
+ contentParts: [],
111
+ toolCalls: []
112
+ };
91
113
  }
92
114
  function isToolCallIncluded(part) {
93
- return part.state === "input-complete" || part.state === "complete" || part.state === "approval-responded" || part.state === "error" || part.output !== void 0;
115
+ return part.state === "input-complete" || part.state === "complete" || part.state === "approval-requested" || part.state === "approval-responded" || part.state === "error" || part.output !== void 0;
94
116
  }
117
+ /**
118
+ * Build ModelMessages for an assistant UIMessage, preserving the
119
+ * sequential interleaving of text, tool calls, and tool results.
120
+ *
121
+ * Walks parts in order. Text and tool-call parts accumulate into the
122
+ * current "segment". When a tool-result part is encountered, the
123
+ * current segment is flushed as an assistant message, then the tool
124
+ * result is emitted as a tool message.
125
+ */
95
126
  function buildAssistantMessages(uiMessage) {
96
- const messageList = [];
97
- let current = createSegment();
98
- let pendingThinking = [];
99
- const emittedToolResultIds = /* @__PURE__ */ new Set();
100
- function flushSegment() {
101
- const content = collapseContentParts(current.contentParts);
102
- const hasContent = content !== null;
103
- const hasToolCalls = current.toolCalls.length > 0;
104
- if (hasContent || hasToolCalls) {
105
- messageList.push({
106
- role: "assistant",
107
- content,
108
- ...hasToolCalls && { toolCalls: current.toolCalls },
109
- ...pendingThinking.length > 0 && { thinking: pendingThinking }
110
- });
111
- pendingThinking = [];
112
- }
113
- current = createSegment();
114
- }
115
- for (const part of uiMessage.parts) {
116
- switch (part.type) {
117
- case "text":
118
- case "image":
119
- case "audio":
120
- case "video":
121
- case "document":
122
- current.contentParts.push(part);
123
- break;
124
- case "tool-call":
125
- if (isToolCallIncluded(part)) {
126
- current.toolCalls.push({
127
- id: part.id,
128
- type: "function",
129
- function: {
130
- name: part.name,
131
- arguments: part.arguments
132
- },
133
- ...part.metadata !== void 0 && { metadata: part.metadata }
134
- });
135
- }
136
- break;
137
- case "tool-result":
138
- flushSegment();
139
- if ((part.state === "complete" || part.state === "error") && !emittedToolResultIds.has(part.toolCallId)) {
140
- messageList.push({
141
- role: "tool",
142
- content: part.content,
143
- toolCallId: part.toolCallId
144
- });
145
- emittedToolResultIds.add(part.toolCallId);
146
- }
147
- break;
148
- case "thinking":
149
- if (part.content) {
150
- pendingThinking.push({
151
- content: part.content,
152
- ...part.signature && { signature: part.signature }
153
- });
154
- }
155
- break;
156
- case "structured-output":
157
- if (part.status === "complete") {
158
- const serialized = part.raw !== "" ? part.raw : part.data !== void 0 ? safeJsonStringify(part.data) : "";
159
- if (serialized !== "") {
160
- current.contentParts.push({ type: "text", content: serialized });
161
- }
162
- }
163
- break;
164
- }
165
- }
166
- flushSegment();
167
- for (const part of uiMessage.parts) {
168
- if (part.type !== "tool-call") continue;
169
- if (part.output !== void 0 && !emittedToolResultIds.has(part.id)) {
170
- messageList.push({
171
- role: "tool",
172
- content: normalizeToolResult(part.output),
173
- toolCallId: part.id
174
- });
175
- emittedToolResultIds.add(part.id);
176
- }
177
- if (part.output === void 0 && part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
178
- const approved = part.approval.approved;
179
- messageList.push({
180
- role: "tool",
181
- content: JSON.stringify({
182
- approved,
183
- ...approved && { pendingExecution: true },
184
- message: approved ? "User approved this action" : "User denied this action"
185
- }),
186
- toolCallId: part.id
187
- });
188
- emittedToolResultIds.add(part.id);
189
- }
190
- }
191
- if (messageList.length === 0) {
192
- messageList.push({
193
- role: "assistant",
194
- content: null
195
- });
196
- }
197
- return messageList;
127
+ const messageList = [];
128
+ let current = createSegment();
129
+ let pendingThinking = [];
130
+ const emittedToolResultIds = /* @__PURE__ */ new Set();
131
+ function flushSegment() {
132
+ const content = collapseContentParts(current.contentParts);
133
+ const hasContent = content !== null;
134
+ const hasToolCalls = current.toolCalls.length > 0;
135
+ if (hasContent || hasToolCalls) {
136
+ messageList.push({
137
+ role: "assistant",
138
+ content,
139
+ ...hasToolCalls && { toolCalls: current.toolCalls },
140
+ ...pendingThinking.length > 0 && { thinking: pendingThinking }
141
+ });
142
+ pendingThinking = [];
143
+ }
144
+ current = createSegment();
145
+ }
146
+ for (const part of uiMessage.parts) switch (part.type) {
147
+ case "text":
148
+ case "image":
149
+ case "audio":
150
+ case "video":
151
+ case "document":
152
+ current.contentParts.push(part);
153
+ break;
154
+ case "tool-call":
155
+ if (isToolCallIncluded(part)) current.toolCalls.push({
156
+ id: part.id,
157
+ type: "function",
158
+ function: {
159
+ name: part.name,
160
+ arguments: part.arguments
161
+ },
162
+ ...part.metadata !== void 0 && { metadata: part.metadata }
163
+ });
164
+ break;
165
+ case "tool-result":
166
+ flushSegment();
167
+ if ((part.state === "complete" || part.state === "error") && !emittedToolResultIds.has(part.toolCallId)) {
168
+ messageList.push({
169
+ role: "tool",
170
+ content: part.content,
171
+ toolCallId: part.toolCallId
172
+ });
173
+ emittedToolResultIds.add(part.toolCallId);
174
+ }
175
+ break;
176
+ case "thinking":
177
+ if (part.content) pendingThinking.push({
178
+ content: part.content,
179
+ ...part.signature && { signature: part.signature }
180
+ });
181
+ break;
182
+ case "structured-output":
183
+ if (part.status === "complete") {
184
+ const serialized = part.raw !== "" ? part.raw : part.data !== void 0 ? safeJsonStringify(part.data) : "";
185
+ if (serialized !== "") current.contentParts.push({
186
+ type: "text",
187
+ content: serialized
188
+ });
189
+ }
190
+ break;
191
+ case "ui-resource": break;
192
+ default: break;
193
+ }
194
+ flushSegment();
195
+ for (const part of uiMessage.parts) {
196
+ if (part.type !== "tool-call") continue;
197
+ if (part.output !== void 0 && !emittedToolResultIds.has(part.id)) {
198
+ messageList.push({
199
+ role: "tool",
200
+ content: normalizeToolResult(part.output),
201
+ toolCallId: part.id
202
+ });
203
+ emittedToolResultIds.add(part.id);
204
+ }
205
+ if (part.output === void 0 && part.state === "approval-responded" && part.approval?.approved !== void 0 && !emittedToolResultIds.has(part.id)) {
206
+ const approved = part.approval.approved;
207
+ messageList.push({
208
+ role: "tool",
209
+ content: JSON.stringify({
210
+ approved,
211
+ ...approved && { pendingExecution: true },
212
+ message: approved ? "User approved this action" : "User denied this action"
213
+ }),
214
+ toolCallId: part.id
215
+ });
216
+ emittedToolResultIds.add(part.id);
217
+ }
218
+ }
219
+ if (messageList.length === 0) messageList.push({
220
+ role: "assistant",
221
+ content: null
222
+ });
223
+ return messageList;
198
224
  }
225
+ /**
226
+ * Convert a ModelMessage to UIMessage
227
+ *
228
+ * This conversion creates a parts-based structure:
229
+ * - content field → TextPart
230
+ * - toolCalls array → ToolCallPart[]
231
+ * - role="tool" messages should be converted separately and merged
232
+ *
233
+ * @param modelMessage - The ModelMessage to convert
234
+ * @param id - Optional ID for the UIMessage (generated if not provided)
235
+ * @returns A UIMessage with parts
236
+ */
199
237
  function modelMessageToUIMessage(modelMessage, id) {
200
- const parts = [];
201
- if (modelMessage.role === "assistant" && modelMessage.thinking?.length) {
202
- for (const thinking of modelMessage.thinking) {
203
- if (!thinking.content) continue;
204
- parts.push({
205
- type: "thinking",
206
- content: thinking.content,
207
- ...thinking.signature && { signature: thinking.signature }
208
- });
209
- }
210
- }
211
- if (modelMessage.role === "tool" && modelMessage.toolCallId) {
212
- parts.push({
213
- type: "tool-result",
214
- toolCallId: modelMessage.toolCallId,
215
- content: getTextContent(modelMessage.content),
216
- state: "complete"
217
- });
218
- } else if (Array.isArray(modelMessage.content)) {
219
- for (const part of modelMessage.content) {
220
- parts.push(part);
221
- }
222
- } else {
223
- const textContent = getTextContent(modelMessage.content);
224
- if (textContent) {
225
- parts.push({
226
- type: "text",
227
- content: textContent
228
- });
229
- }
230
- }
231
- if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) {
232
- for (const toolCall of modelMessage.toolCalls) {
233
- let input;
234
- try {
235
- input = JSON.parse(toolCall.function.arguments);
236
- } catch {
237
- input = void 0;
238
- }
239
- parts.push({
240
- type: "tool-call",
241
- id: toolCall.id,
242
- name: toolCall.function.name,
243
- arguments: toolCall.function.arguments,
244
- state: "input-complete",
245
- // Model messages have complete arguments
246
- ...input !== void 0 && { input },
247
- ...toolCall.metadata !== void 0 && { metadata: toolCall.metadata }
248
- });
249
- }
250
- }
251
- return {
252
- id: id || generateMessageId(),
253
- role: modelMessage.role === "tool" ? "assistant" : modelMessage.role,
254
- parts
255
- };
238
+ const parts = [];
239
+ if (modelMessage.role === "assistant" && modelMessage.thinking?.length) for (const thinking of modelMessage.thinking) {
240
+ if (!thinking.content) continue;
241
+ parts.push({
242
+ type: "thinking",
243
+ content: thinking.content,
244
+ ...thinking.signature && { signature: thinking.signature }
245
+ });
246
+ }
247
+ if (modelMessage.role === "tool" && modelMessage.toolCallId) parts.push({
248
+ type: "tool-result",
249
+ toolCallId: modelMessage.toolCallId,
250
+ content: getTextContent(modelMessage.content),
251
+ state: "complete"
252
+ });
253
+ else if (Array.isArray(modelMessage.content)) for (const part of modelMessage.content) parts.push(part);
254
+ else {
255
+ const textContent = getTextContent(modelMessage.content);
256
+ if (textContent) parts.push({
257
+ type: "text",
258
+ content: textContent
259
+ });
260
+ }
261
+ if (modelMessage.toolCalls && modelMessage.toolCalls.length > 0) for (const toolCall of modelMessage.toolCalls) {
262
+ let input;
263
+ try {
264
+ input = JSON.parse(toolCall.function.arguments);
265
+ } catch {
266
+ input = void 0;
267
+ }
268
+ parts.push({
269
+ type: "tool-call",
270
+ id: toolCall.id,
271
+ name: toolCall.function.name,
272
+ arguments: toolCall.function.arguments,
273
+ state: "input-complete",
274
+ ...input !== void 0 && { input },
275
+ ...toolCall.metadata !== void 0 && { metadata: toolCall.metadata }
276
+ });
277
+ }
278
+ return {
279
+ id: id || generateMessageId(),
280
+ role: modelMessage.role === "tool" ? "assistant" : modelMessage.role,
281
+ parts
282
+ };
256
283
  }
284
+ /**
285
+ * Normalize a single AG-UI `MESSAGES_SNAPSHOT` message into a `UIMessage`.
286
+ *
287
+ * AG-UI snapshot messages use the wire shape `{ id, role, content }` and have
288
+ * no `parts` array. Casting them directly to `UIMessage` is unsafe: any code
289
+ * that later reads `message.parts` (e.g. the devtools `onToolCallStateChange`
290
+ * handler) crashes with "Cannot read properties of undefined (reading 'find')".
291
+ *
292
+ * Each role is mapped to the canonical `UIMessage` shape, reusing
293
+ * `modelMessageToUIMessage` for the roles that share `ModelMessage`'s structure.
294
+ * The original AG-UI `id` is preserved so later `TEXT_MESSAGE_CONTENT` /
295
+ * `TOOL_CALL_*` events still route by `messageId` (falling back to a generated
296
+ * id only when the snapshot omits one). Messages that already carry `parts`
297
+ * (e.g. a TanStack server echoing `UIMessage`s back over the wire) pass through
298
+ * unchanged apart from ensuring an id.
299
+ */
257
300
  function aguiSnapshotMessageToUIMessage(message) {
258
- if ("parts" in message) {
259
- return { ...message, id: message.id || generateMessageId() };
260
- }
261
- const id = message.id || generateMessageId();
262
- switch (message.role) {
263
- case "user":
264
- return {
265
- id,
266
- role: "user",
267
- parts: aguiUserContentToParts(message.content)
268
- };
269
- case "assistant":
270
- return modelMessageToUIMessage(
271
- {
272
- role: "assistant",
273
- content: message.content ?? null,
274
- ...message.toolCalls && { toolCalls: message.toolCalls }
275
- },
276
- id
277
- );
278
- case "tool":
279
- return modelMessageToUIMessage(
280
- {
281
- role: "tool",
282
- content: message.content,
283
- toolCallId: message.toolCallId
284
- },
285
- id
286
- );
287
- case "system":
288
- case "developer":
289
- return {
290
- id,
291
- role: "system",
292
- parts: message.content ? [{ type: "text", content: message.content }] : []
293
- };
294
- case "reasoning":
295
- return {
296
- id,
297
- role: "assistant",
298
- parts: message.content ? [{ type: "thinking", content: message.content }] : []
299
- };
300
- case "activity":
301
- default:
302
- return { id, role: "assistant", parts: [] };
303
- }
301
+ if ("parts" in message) return {
302
+ ...message,
303
+ id: message.id || generateMessageId()
304
+ };
305
+ const id = message.id || generateMessageId();
306
+ switch (message.role) {
307
+ case "user": return {
308
+ id,
309
+ role: "user",
310
+ parts: aguiUserContentToParts(message.content)
311
+ };
312
+ case "assistant": return modelMessageToUIMessage({
313
+ role: "assistant",
314
+ content: message.content ?? null,
315
+ ...message.toolCalls && { toolCalls: message.toolCalls }
316
+ }, id);
317
+ case "tool": return modelMessageToUIMessage({
318
+ role: "tool",
319
+ content: message.content,
320
+ toolCallId: message.toolCallId
321
+ }, id);
322
+ case "system":
323
+ case "developer": return {
324
+ id,
325
+ role: "system",
326
+ parts: message.content ? [{
327
+ type: "text",
328
+ content: message.content
329
+ }] : []
330
+ };
331
+ case "reasoning": return {
332
+ id,
333
+ role: "assistant",
334
+ parts: message.content ? [{
335
+ type: "thinking",
336
+ content: message.content
337
+ }] : []
338
+ };
339
+ default: return {
340
+ id,
341
+ role: "assistant",
342
+ parts: []
343
+ };
344
+ }
304
345
  }
346
+ /**
347
+ * Convert AG-UI user message content into `UIMessage` parts.
348
+ *
349
+ * AG-UI user content is either a plain string or a multimodal array whose text
350
+ * entries use `{ type: 'text', text }` (vs. TanStack's `{ type: 'text', content }`).
351
+ * Text entries are rewritten to the TanStack shape; image/audio/video/document
352
+ * entries already match `ContentPart` and pass through. `binary` entries have no
353
+ * TanStack equivalent and are dropped.
354
+ */
305
355
  function aguiUserContentToParts(content) {
306
- if (typeof content === "string") {
307
- return content ? [{ type: "text", content }] : [];
308
- }
309
- const parts = [];
310
- for (const part of content) {
311
- if (part.type === "text") {
312
- parts.push({ type: "text", content: part.text });
313
- } else if (part.type !== "binary") {
314
- parts.push(part);
315
- }
316
- }
317
- return parts;
356
+ if (typeof content === "string") return content ? [{
357
+ type: "text",
358
+ content
359
+ }] : [];
360
+ const parts = [];
361
+ for (const part of content) if (part.type === "text") parts.push({
362
+ type: "text",
363
+ content: part.text
364
+ });
365
+ else if (part.type !== "binary") parts.push(part);
366
+ return parts;
318
367
  }
368
+ /**
369
+ * Convert an array of ModelMessages to UIMessages
370
+ *
371
+ * This handles merging tool result messages with their corresponding assistant messages
372
+ *
373
+ * @param modelMessages - Array of ModelMessages to convert
374
+ * @returns Array of UIMessages
375
+ */
319
376
  function modelMessagesToUIMessages(modelMessages) {
320
- const uiMessages = [];
321
- let currentAssistantMessage = null;
322
- for (const msg of modelMessages) {
323
- if (msg.role === "tool") {
324
- if (msg.toolCallId !== void 0 && currentAssistantMessage && currentAssistantMessage.role === "assistant") {
325
- const content = getTextContent(msg.content);
326
- const toolCallPart = currentAssistantMessage.parts.find(
327
- (part) => part.type === "tool-call" && part.id === msg.toolCallId
328
- );
329
- if (toolCallPart) {
330
- toolCallPart.output = parseToolResultContent(content);
331
- toolCallPart.state = "complete";
332
- }
333
- currentAssistantMessage.parts.push({
334
- type: "tool-result",
335
- toolCallId: msg.toolCallId,
336
- content,
337
- state: "complete"
338
- });
339
- } else {
340
- const toolResultUIMessage = modelMessageToUIMessage(msg);
341
- uiMessages.push(toolResultUIMessage);
342
- }
343
- } else {
344
- const uiMessage = modelMessageToUIMessage(msg);
345
- uiMessages.push(uiMessage);
346
- if (msg.role === "assistant") {
347
- currentAssistantMessage = uiMessage;
348
- } else {
349
- currentAssistantMessage = null;
350
- }
351
- }
352
- }
353
- return uiMessages;
377
+ const uiMessages = [];
378
+ let currentAssistantMessage = null;
379
+ for (const msg of modelMessages) if (msg.role === "tool") if (msg.toolCallId !== void 0 && currentAssistantMessage && currentAssistantMessage.role === "assistant") {
380
+ const content = getTextContent(msg.content);
381
+ const toolCallPart = currentAssistantMessage.parts.find((part) => part.type === "tool-call" && part.id === msg.toolCallId);
382
+ if (toolCallPart) {
383
+ toolCallPart.output = parseToolResultContent(content);
384
+ toolCallPart.state = "complete";
385
+ }
386
+ currentAssistantMessage.parts.push({
387
+ type: "tool-result",
388
+ toolCallId: msg.toolCallId,
389
+ content,
390
+ state: "complete"
391
+ });
392
+ } else {
393
+ const toolResultUIMessage = modelMessageToUIMessage(msg, msg.id);
394
+ uiMessages.push(toolResultUIMessage);
395
+ }
396
+ else {
397
+ const uiMessage = modelMessageToUIMessage(msg, msg.id);
398
+ uiMessages.push(uiMessage);
399
+ if (msg.role === "assistant") currentAssistantMessage = uiMessage;
400
+ else currentAssistantMessage = null;
401
+ }
402
+ return uiMessages;
354
403
  }
404
+ /**
405
+ * Normalize a message (UIMessage or ModelMessage) to a UIMessage
406
+ * Ensures the message has an ID and createdAt timestamp
407
+ *
408
+ * @param message - Either a UIMessage or ModelMessage
409
+ * @param generateId - Function to generate a message ID if needed
410
+ * @returns A UIMessage with guaranteed id and createdAt
411
+ */
355
412
  function normalizeToUIMessage(message, generateId) {
356
- if ("parts" in message) {
357
- return {
358
- ...message,
359
- id: message.id || generateId(),
360
- createdAt: message.createdAt || /* @__PURE__ */ new Date()
361
- };
362
- } else {
363
- return {
364
- ...modelMessageToUIMessage(message, generateId()),
365
- createdAt: /* @__PURE__ */ new Date()
366
- };
367
- }
413
+ if ("parts" in message) return {
414
+ ...message,
415
+ id: message.id || generateId(),
416
+ createdAt: message.createdAt || /* @__PURE__ */ new Date()
417
+ };
418
+ else return {
419
+ ...modelMessageToUIMessage(message, generateId()),
420
+ createdAt: /* @__PURE__ */ new Date()
421
+ };
368
422
  }
423
+ /**
424
+ * Generate a unique message ID
425
+ */
369
426
  function generateMessageId() {
370
- return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
427
+ return `msg-${Date.now()}-${Math.random().toString(36).substring(7)}`;
371
428
  }
372
- export {
373
- aguiSnapshotMessageToUIMessage,
374
- convertMessagesToModelMessages,
375
- generateMessageId,
376
- modelMessageToUIMessage,
377
- modelMessagesToUIMessages,
378
- normalizeToUIMessage,
379
- uiMessageToModelMessages
380
- };
381
- //# sourceMappingURL=messages.js.map
429
+ //#endregion
430
+ export { aguiSnapshotMessageToUIMessage, convertMessagesToModelMessages, generateMessageId, modelMessageToUIMessage, modelMessagesToUIMessages, normalizeToUIMessage, uiMessageToModelMessages };
431
+
432
+ //# sourceMappingURL=messages.js.map