@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 +1 @@
1
- {"version":3,"file":"messages.js","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 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\nfunction 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 role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\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-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 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 role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\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 role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\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 }\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 role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\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 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 })\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 role: 'assistant',\n content: null,\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 === '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 }\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)\n uiMessages.push(toolResultUIMessage)\n }\n } else {\n // Regular message\n const uiMessage = modelMessageToUIMessage(msg)\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: 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"],"names":[],"mappings":"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"}
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 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\nfunction 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 role: uiMessage.role as 'user' | 'assistant' | 'tool',\n content: collapseContentParts(contentParts),\n }\n}\n\n// Accumulator for building an assistant segment (content + tool calls)\ninterface AssistantSegment {\n contentParts: Array<ContentPart>\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 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 role: 'assistant',\n content,\n ...(hasToolCalls && { toolCalls: current.toolCalls }),\n ...(pendingThinking.length > 0 && { thinking: pendingThinking }),\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 role: 'tool',\n content: part.content,\n toolCallId: part.toolCallId,\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 }\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 role: 'tool',\n content: normalizeToolResult(part.output),\n toolCallId: part.id,\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 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 })\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 role: 'assistant',\n content: null,\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 === '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 }\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: 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,17 +1,19 @@
1
+ //#region src/activities/chat/middleware/builder.ts
2
+ /** Create an order-aware middleware builder. */
1
3
  function createChatMiddleware() {
2
- const list = [];
3
- const builder = {
4
- use(middleware) {
5
- list.push(middleware);
6
- return builder;
7
- },
8
- build() {
9
- return list;
10
- }
11
- };
12
- return builder;
4
+ const list = [];
5
+ const builder = {
6
+ use(middleware) {
7
+ list.push(middleware);
8
+ return builder;
9
+ },
10
+ build() {
11
+ return list;
12
+ }
13
+ };
14
+ return builder;
13
15
  }
14
- export {
15
- createChatMiddleware
16
- };
17
- //# sourceMappingURL=builder.js.map
16
+ //#endregion
17
+ export { createChatMiddleware };
18
+
19
+ //# sourceMappingURL=builder.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"builder.js","sources":["../../../../../src/activities/chat/middleware/builder.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { AnyChatMiddleware, ChatMiddleware } from './types'\nimport type { DefinedChatMiddleware } from './define'\n\n/** Union of capability NAME literals from a tuple of handles. */\nexport type NamesOf<T extends ReadonlyArray<CapabilityHandle>> =\n T[number]['capabilityName']\n\n/** Names provided across a middleware array (imprecise middleware → `string`). */\nexport type ProvidedNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['provides']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/** Names required across a middleware array. */\nexport type RequiredNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['requires']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/**\n * Branded marker surfaced when required capability names are missing from the\n * provided set, so the compiler error names the gap instead of emitting an\n * opaque \"not assignable\".\n */\nexport type MissingCapabilities<TMissing extends string> = {\n // The human-readable message lives in the property KEY, so TypeScript's\n // \"Property '<key>' is missing in type ... but required in type ...\" error\n // prints the explanation instead of an opaque `__missingCapabilities`. The\n // key distributes over a union of missing names (one required key each).\n [K in `✖ Missing capability \"${TMissing}\": no configured middleware provides it. Add a middleware whose \\`provides\\` includes it (and, with createChatMiddleware().use(), order the provider before this consumer).`]: never\n}\n\n/**\n * Missing capability names. When required names are imprecise (`string`, i.e.\n * plain `ChatMiddleware` not authored via `defineChatMiddleware`), we cannot\n * prove a gap, so we allow it (→ `never`). Otherwise the precise literals not\n * present in the provided set.\n */\ntype MissingNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n string extends RequiredNames<TList>\n ? never\n : Exclude<RequiredNames<TList>, ProvidedNames<TList>>\n\n/**\n * Resolves to `TList` when coverage holds, otherwise to a `MissingCapabilities`\n * marker (not assignable to a middleware array) — producing a compile error at\n * the `middleware` option that names the missing capability.\n */\nexport type CheckCoverage<TList extends ReadonlyArray<AnyChatMiddleware>> = [\n MissingNames<TList>,\n] extends [never]\n ? TList\n : MissingCapabilities<MissingNames<TList>>\n\n/**\n * Order-aware middleware builder. Each `.use()` requires that the middleware's\n * required capability names are already in the accumulated provided set, then\n * adds its provided names. `.build()` returns the ordered array.\n *\n * `TProvided` is the running union of provided capability name literals.\n */\nexport interface ChatMiddlewareBuilder<\n TList extends ReadonlyArray<AnyChatMiddleware>,\n TProvided extends string,\n> {\n use: <\n TRequires extends ReadonlyArray<CapabilityHandle>,\n TProvides extends ReadonlyArray<CapabilityHandle>,\n TContext = unknown,\n >(\n middleware: [NamesOf<TRequires>] extends [TProvided]\n ? DefinedChatMiddleware<TContext, TRequires, TProvides>\n : DefinedChatMiddleware<TContext, TRequires, TProvides> &\n MissingCapabilities<Exclude<NamesOf<TRequires>, TProvided>>,\n ) => ChatMiddlewareBuilder<\n readonly [...TList, DefinedChatMiddleware<TContext, TRequires, TProvides>],\n TProvided | NamesOf<TProvides>\n >\n\n build: () => [...TList]\n}\n\n/** Create an order-aware middleware builder. */\nexport function createChatMiddleware(): ChatMiddlewareBuilder<\n readonly [],\n never\n> {\n const list: Array<ChatMiddleware<unknown>> = []\n const builder = {\n use(middleware: ChatMiddleware<unknown>) {\n list.push(middleware)\n return builder\n },\n build() {\n return list\n },\n }\n // The only sanctioned assertion in this PR: the runtime `builder` is a single\n // object reused across `.use()` calls, but the type accumulates `TProvided`\n // and `TList` per call — TypeScript cannot derive that from runtime values, so\n // a structural `as` is impossible and the double assertion is irreducible.\n // eslint-disable-next-line no-restricted-syntax -- irreducible: type-level accumulation cannot be expressed from a single runtime object\n return builder as unknown as ChatMiddlewareBuilder<readonly [], never>\n}\n"],"names":[],"mappings":"AAwFO,SAAS,uBAGd;AACA,QAAM,OAAuC,CAAA;AAC7C,QAAM,UAAU;AAAA,IACd,IAAI,YAAqC;AACvC,WAAK,KAAK,UAAU;AACpB,aAAO;AAAA,IACT;AAAA,IACA,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EAAA;AAOF,SAAO;AACT;"}
1
+ {"version":3,"file":"builder.js","names":[],"sources":["../../../../../src/activities/chat/middleware/builder.ts"],"sourcesContent":["import type { CapabilityHandle } from './capabilities'\nimport type { AnyChatMiddleware, ChatMiddleware } from './types'\nimport type { DefinedChatMiddleware } from './define'\n\n/** Union of capability NAME literals from a tuple of handles. */\nexport type NamesOf<T extends ReadonlyArray<CapabilityHandle>> =\n T[number]['capabilityName']\n\n/** Names provided across a middleware array (imprecise middleware → `string`). */\nexport type ProvidedNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['provides']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/** Names required across a middleware array. */\nexport type RequiredNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n NonNullable<TList[number]['requires']> extends infer P\n ? P extends ReadonlyArray<CapabilityHandle>\n ? NamesOf<P>\n : never\n : never\n\n/**\n * Branded marker surfaced when required capability names are missing from the\n * provided set, so the compiler error names the gap instead of emitting an\n * opaque \"not assignable\".\n */\nexport type MissingCapabilities<TMissing extends string> = {\n // The human-readable message lives in the property KEY, so TypeScript's\n // \"Property '<key>' is missing in type ... but required in type ...\" error\n // prints the explanation instead of an opaque `__missingCapabilities`. The\n // key distributes over a union of missing names (one required key each).\n [K in `✖ Missing capability \"${TMissing}\": no configured middleware provides it. Add a middleware whose \\`provides\\` includes it (and, with createChatMiddleware().use(), order the provider before this consumer).`]: never\n}\n\n/**\n * Missing capability names. When required names are imprecise (`string`, i.e.\n * plain `ChatMiddleware` not authored via `defineChatMiddleware`), we cannot\n * prove a gap, so we allow it (→ `never`). Otherwise the precise literals not\n * present in the provided set.\n */\ntype MissingNames<TList extends ReadonlyArray<AnyChatMiddleware>> =\n string extends RequiredNames<TList>\n ? never\n : Exclude<RequiredNames<TList>, ProvidedNames<TList>>\n\n/**\n * Resolves to `TList` when coverage holds, otherwise to a `MissingCapabilities`\n * marker (not assignable to a middleware array) — producing a compile error at\n * the `middleware` option that names the missing capability.\n */\nexport type CheckCoverage<TList extends ReadonlyArray<AnyChatMiddleware>> = [\n MissingNames<TList>,\n] extends [never]\n ? TList\n : MissingCapabilities<MissingNames<TList>>\n\n/**\n * Order-aware middleware builder. Each `.use()` requires that the middleware's\n * required capability names are already in the accumulated provided set, then\n * adds its provided names. `.build()` returns the ordered array.\n *\n * `TProvided` is the running union of provided capability name literals.\n */\nexport interface ChatMiddlewareBuilder<\n TList extends ReadonlyArray<AnyChatMiddleware>,\n TProvided extends string,\n> {\n use: <\n TRequires extends ReadonlyArray<CapabilityHandle>,\n TProvides extends ReadonlyArray<CapabilityHandle>,\n TContext = unknown,\n >(\n middleware: [NamesOf<TRequires>] extends [TProvided]\n ? DefinedChatMiddleware<TContext, TRequires, TProvides>\n : DefinedChatMiddleware<TContext, TRequires, TProvides> &\n MissingCapabilities<Exclude<NamesOf<TRequires>, TProvided>>,\n ) => ChatMiddlewareBuilder<\n readonly [...TList, DefinedChatMiddleware<TContext, TRequires, TProvides>],\n TProvided | NamesOf<TProvides>\n >\n\n build: () => [...TList]\n}\n\n/** Create an order-aware middleware builder. */\nexport function createChatMiddleware(): ChatMiddlewareBuilder<\n readonly [],\n never\n> {\n const list: Array<ChatMiddleware<unknown>> = []\n const builder = {\n use(middleware: ChatMiddleware<unknown>) {\n list.push(middleware)\n return builder\n },\n build() {\n return list\n },\n }\n // The only sanctioned assertion in this PR: the runtime `builder` is a single\n // object reused across `.use()` calls, but the type accumulates `TProvided`\n // and `TList` per call — TypeScript cannot derive that from runtime values, so\n // a structural `as` is impossible and the double assertion is irreducible.\n // oxlint-disable-next-line eslint-js/no-restricted-syntax -- irreducible: type-level accumulation cannot be expressed from a single runtime object\n return builder as unknown as ChatMiddlewareBuilder<readonly [], never>\n}\n"],"mappings":";;AAwFA,SAAgB,uBAGd;CACA,MAAM,OAAuC,CAAC;CAC9C,MAAM,UAAU;EACd,IAAI,YAAqC;GACvC,KAAK,KAAK,UAAU;GACpB,OAAO;EACT;EACA,QAAQ;GACN,OAAO;EACT;CACF;CAMA,OAAO;AACT"}
@@ -1,45 +1,80 @@
1
- class CapabilityRegistry {
2
- provided = /* @__PURE__ */ new Set();
3
- onDuplicate;
4
- /** Register a callback fired when a handle is provided more than once. */
5
- setOnDuplicate(cb) {
6
- this.onDuplicate = cb;
7
- }
8
- /** Record that `handle` was provided; fire the duplicate callback on repeats. */
9
- markProvided(handle) {
10
- if (this.provided.has(handle)) this.onDuplicate?.(handle.capabilityName);
11
- this.provided.add(handle);
12
- }
13
- has(handle) {
14
- return this.provided.has(handle);
15
- }
16
- }
1
+ //#region src/activities/chat/middleware/capabilities.ts
2
+ /**
3
+ * Per-request bookkeeping: which capabilities were provided, plus the
4
+ * duplicate-provide notification. Capability VALUES live in per-capability
5
+ * WeakMaps (see `createCapability`), not here — this only tracks presence.
6
+ */
7
+ var CapabilityRegistry = class {
8
+ provided = /* @__PURE__ */ new Set();
9
+ onDuplicate;
10
+ /** Register a callback fired when a handle is provided more than once. */
11
+ setOnDuplicate(cb) {
12
+ this.onDuplicate = cb;
13
+ }
14
+ /** Record that `handle` was provided; fire the duplicate callback on repeats. */
15
+ markProvided(handle) {
16
+ if (this.provided.has(handle)) this.onDuplicate?.(handle.capabilityName);
17
+ this.provided.add(handle);
18
+ }
19
+ has(handle) {
20
+ return this.provided.has(handle);
21
+ }
22
+ };
23
+ /**
24
+ * Create a capability. Returns a hybrid handle that destructures to
25
+ * `[get, provide]` and is itself the identity for `requires`/`provides`.
26
+ *
27
+ * Curried so the value type is supplied explicitly while the name literal is
28
+ * INFERRED from the argument: `createCapability<T>()('name')`. (A single call
29
+ * `createCapability<T>('name')` cannot work — supplying `T` explicitly stops
30
+ * TypeScript inferring the name, collapsing it to `string` and defeating the
31
+ * compile-time coverage check that keys on the literal name.)
32
+ *
33
+ * @example Provider + consumer middleware
34
+ * ```ts
35
+ * const counterCapability = createCapability<{ value: number }>()('counter')
36
+ * const [getCounter, provideCounter] = counterCapability
37
+ *
38
+ * const withCounter = defineChatMiddleware({
39
+ * name: 'counter',
40
+ * provides: [counterCapability],
41
+ * setup(ctx) { provideCounter(ctx, { value: 0 }) },
42
+ * })
43
+ *
44
+ * const readsCounter = defineChatMiddleware({
45
+ * name: 'reads-counter',
46
+ * requires: [counterCapability],
47
+ * onChunk(ctx) { getCounter(ctx).value++ },
48
+ * })
49
+ *
50
+ * chat({ adapter, messages, middleware: [withCounter, readsCounter] })
51
+ * ```
52
+ *
53
+ * @remarks Capability `name`s must be unique across your app: compile-time
54
+ * coverage tracking keys on the name literal (runtime keys on reference).
55
+ */
17
56
  function createCapability() {
18
- return (name) => {
19
- const values = /* @__PURE__ */ new WeakMap();
20
- function get(ctx, opts) {
21
- if (!values.has(ctx)) {
22
- if (opts?.optional) return void 0;
23
- throw new Error(
24
- `Capability "${name}" was requested but never provided. Ensure a middleware provides it in setup(), ordered before this consumer.`
25
- );
26
- }
27
- return values.get(ctx);
28
- }
29
- const provide = (ctx, value) => {
30
- values.set(ctx, value);
31
- ctx.capabilities.markProvided(handle);
32
- };
33
- const pair = [get, provide];
34
- const handle = Object.assign(pair, {
35
- capabilityName: name,
36
- has: (ctx) => values.has(ctx)
37
- });
38
- return handle;
39
- };
57
+ return (name) => {
58
+ const values = /* @__PURE__ */ new WeakMap();
59
+ function get(ctx, opts) {
60
+ if (!values.has(ctx)) {
61
+ if (opts?.optional) return void 0;
62
+ throw new Error(`Capability "${name}" was requested but never provided. Ensure a middleware provides it in setup(), ordered before this consumer.`);
63
+ }
64
+ return values.get(ctx);
65
+ }
66
+ const provide = (ctx, value) => {
67
+ values.set(ctx, value);
68
+ ctx.capabilities.markProvided(handle);
69
+ };
70
+ const handle = Object.assign([get, provide], {
71
+ capabilityName: name,
72
+ has: (ctx) => values.has(ctx)
73
+ });
74
+ return handle;
75
+ };
40
76
  }
41
- export {
42
- CapabilityRegistry,
43
- createCapability
44
- };
45
- //# sourceMappingURL=capabilities.js.map
77
+ //#endregion
78
+ export { CapabilityRegistry, createCapability };
79
+
80
+ //# sourceMappingURL=capabilities.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.js","sources":["../../../../../src/activities/chat/middleware/capabilities.ts"],"sourcesContent":["/** Options accepted by a capability getter. */\nexport interface CapabilityGetOptions {\n /** When true, return undefined instead of throwing if the capability is absent. */\n optional?: boolean\n}\n\n/**\n * The minimal context shape a capability accessor needs. The full\n * `ChatMiddlewareContext` satisfies this (it has `capabilities`), so accessors\n * accept any middleware context without referencing `any`.\n */\nexport interface CapabilityContext {\n capabilities: CapabilityRegistry\n}\n\n/** Reads a capability value off a context. Overloaded so the flag narrows the return. */\nexport interface CapabilityGetter<TValue> {\n (ctx: CapabilityContext): TValue\n (ctx: CapabilityContext, opts: { optional: true }): TValue | undefined\n}\n\n/** Writes a capability value onto a context. */\nexport type CapabilityProvider<TValue> = (\n ctx: CapabilityContext,\n value: TValue,\n) => void\n\n/**\n * A capability handle. It is BOTH a `[get, provide]` tuple (array-destructurable)\n * AND the identity used in middleware `requires`/`provides` declarations.\n *\n * Runtime identity is this object's reference. The `capabilityName` literal is\n * used for diagnostics and COMPILE-TIME tracking only — capability names MUST be\n * unique across an app or the type-level coverage check conflates them.\n */\nexport type Capability<\n TValue = unknown,\n TName extends string = string,\n> = readonly [\n get: CapabilityGetter<TValue>,\n provide: CapabilityProvider<TValue>,\n] & {\n readonly capabilityName: TName\n /** @internal Presence check for the post-setup assertion. */\n has: (ctx: CapabilityContext) => boolean\n}\n\n/**\n * A capability handle with permissive value/name — for use as a constraint in\n * `requires`/`provides` arrays. Concentrates `any` in one named alias (same\n * convention as `AnyTextAdapter`/`AnyTool`); needed so `Capability<SpecificT>`\n * is assignable to the handle-array element type.\n */\nexport type CapabilityHandle = Capability<any, string>\n\n/**\n * Per-request bookkeeping: which capabilities were provided, plus the\n * duplicate-provide notification. Capability VALUES live in per-capability\n * WeakMaps (see `createCapability`), not here — this only tracks presence.\n */\nexport class CapabilityRegistry {\n private readonly provided = new Set<CapabilityHandle>()\n private onDuplicate?: (name: string) => void\n\n /** Register a callback fired when a handle is provided more than once. */\n setOnDuplicate(cb: (name: string) => void): void {\n this.onDuplicate = cb\n }\n\n /** Record that `handle` was provided; fire the duplicate callback on repeats. */\n markProvided(handle: CapabilityHandle): void {\n if (this.provided.has(handle)) this.onDuplicate?.(handle.capabilityName)\n this.provided.add(handle)\n }\n\n has(handle: CapabilityHandle): boolean {\n return this.provided.has(handle)\n }\n}\n\n/**\n * Create a capability. Returns a hybrid handle that destructures to\n * `[get, provide]` and is itself the identity for `requires`/`provides`.\n *\n * Curried so the value type is supplied explicitly while the name literal is\n * INFERRED from the argument: `createCapability<T>()('name')`. (A single call\n * `createCapability<T>('name')` cannot work — supplying `T` explicitly stops\n * TypeScript inferring the name, collapsing it to `string` and defeating the\n * compile-time coverage check that keys on the literal name.)\n *\n * @example Provider + consumer middleware\n * ```ts\n * const counterCapability = createCapability<{ value: number }>()('counter')\n * const [getCounter, provideCounter] = counterCapability\n *\n * const withCounter = defineChatMiddleware({\n * name: 'counter',\n * provides: [counterCapability],\n * setup(ctx) { provideCounter(ctx, { value: 0 }) },\n * })\n *\n * const readsCounter = defineChatMiddleware({\n * name: 'reads-counter',\n * requires: [counterCapability],\n * onChunk(ctx) { getCounter(ctx).value++ },\n * })\n *\n * chat({ adapter, messages, middleware: [withCounter, readsCounter] })\n * ```\n *\n * @remarks Capability `name`s must be unique across your app: compile-time\n * coverage tracking keys on the name literal (runtime keys on reference).\n */\nexport function createCapability<TValue = unknown>(): <\n const TName extends string,\n>(\n name: TName,\n) => Capability<TValue, TName> {\n return <const TName extends string>(\n name: TName,\n ): Capability<TValue, TName> => {\n // Each capability owns a typed WeakMap keyed by the context object. Because\n // the value type is TValue, reads are typed with no assertion.\n const values = new WeakMap<CapabilityContext, TValue>()\n\n function get(ctx: CapabilityContext): TValue\n function get(\n ctx: CapabilityContext,\n opts: { optional: true },\n ): TValue | undefined\n function get(\n ctx: CapabilityContext,\n opts?: CapabilityGetOptions,\n ): TValue | undefined {\n if (!values.has(ctx)) {\n if (opts?.optional) return undefined\n throw new Error(\n `Capability \"${name}\" was requested but never provided. Ensure a ` +\n `middleware provides it in setup(), ordered before this consumer.`,\n )\n }\n return values.get(ctx)\n }\n\n const provide: CapabilityProvider<TValue> = (ctx, value) => {\n values.set(ctx, value)\n ctx.capabilities.markProvided(handle)\n }\n\n const pair: readonly [\n CapabilityGetter<TValue>,\n CapabilityProvider<TValue>,\n ] = [get, provide]\n // Object.assign's return type is the intersection of the tuple and the\n // props, which IS Capability<TValue, TName> — no cast needed.\n const handle = Object.assign(pair, {\n capabilityName: name,\n has: (ctx: CapabilityContext) => values.has(ctx),\n })\n return handle\n }\n}\n"],"names":[],"mappings":"AA4DO,MAAM,mBAAmB;AAAA,EACb,+BAAe,IAAA;AAAA,EACxB;AAAA;AAAA,EAGR,eAAe,IAAkC;AAC/C,SAAK,cAAc;AAAA,EACrB;AAAA;AAAA,EAGA,aAAa,QAAgC;AAC3C,QAAI,KAAK,SAAS,IAAI,MAAM,EAAG,MAAK,cAAc,OAAO,cAAc;AACvE,SAAK,SAAS,IAAI,MAAM;AAAA,EAC1B;AAAA,EAEA,IAAI,QAAmC;AACrC,WAAO,KAAK,SAAS,IAAI,MAAM;AAAA,EACjC;AACF;AAmCO,SAAS,mBAIe;AAC7B,SAAO,CACL,SAC8B;AAG9B,UAAM,6BAAa,QAAA;AAOnB,aAAS,IACP,KACA,MACoB;AACpB,UAAI,CAAC,OAAO,IAAI,GAAG,GAAG;AACpB,YAAI,MAAM,SAAU,QAAO;AAC3B,cAAM,IAAI;AAAA,UACR,eAAe,IAAI;AAAA,QAAA;AAAA,MAGvB;AACA,aAAO,OAAO,IAAI,GAAG;AAAA,IACvB;AAEA,UAAM,UAAsC,CAAC,KAAK,UAAU;AAC1D,aAAO,IAAI,KAAK,KAAK;AACrB,UAAI,aAAa,aAAa,MAAM;AAAA,IACtC;AAEA,UAAM,OAGF,CAAC,KAAK,OAAO;AAGjB,UAAM,SAAS,OAAO,OAAO,MAAM;AAAA,MACjC,gBAAgB;AAAA,MAChB,KAAK,CAAC,QAA2B,OAAO,IAAI,GAAG;AAAA,IAAA,CAChD;AACD,WAAO;AAAA,EACT;AACF;"}
1
+ {"version":3,"file":"capabilities.js","names":[],"sources":["../../../../../src/activities/chat/middleware/capabilities.ts"],"sourcesContent":["/** Options accepted by a capability getter. */\nexport interface CapabilityGetOptions {\n /** When true, return undefined instead of throwing if the capability is absent. */\n optional?: boolean\n}\n\n/**\n * The minimal context shape a capability accessor needs. The full\n * `ChatMiddlewareContext` satisfies this (it has `capabilities`), so accessors\n * accept any middleware context without referencing `any`.\n */\nexport interface CapabilityContext {\n capabilities: CapabilityRegistry\n}\n\n/** Reads a capability value off a context. Overloaded so the flag narrows the return. */\nexport interface CapabilityGetter<TValue> {\n (ctx: CapabilityContext): TValue\n (ctx: CapabilityContext, opts: { optional: true }): TValue | undefined\n}\n\n/** Writes a capability value onto a context. */\nexport type CapabilityProvider<TValue> = (\n ctx: CapabilityContext,\n value: TValue,\n) => void\n\n/**\n * A capability handle. It is BOTH a `[get, provide]` tuple (array-destructurable)\n * AND the identity used in middleware `requires`/`provides` declarations.\n *\n * Runtime identity is this object's reference. The `capabilityName` literal is\n * used for diagnostics and COMPILE-TIME tracking only — capability names MUST be\n * unique across an app or the type-level coverage check conflates them.\n */\nexport type Capability<\n TValue = unknown,\n TName extends string = string,\n> = readonly [\n get: CapabilityGetter<TValue>,\n provide: CapabilityProvider<TValue>,\n] & {\n readonly capabilityName: TName\n /** @internal Presence check for the post-setup assertion. */\n has: (ctx: CapabilityContext) => boolean\n}\n\n/**\n * A capability handle with permissive value/name — for use as a constraint in\n * `requires`/`provides` arrays. Concentrates `any` in one named alias (same\n * convention as `AnyTextAdapter`/`AnyTool`); needed so `Capability<SpecificT>`\n * is assignable to the handle-array element type.\n */\nexport type CapabilityHandle = Capability<any, string>\n\n/**\n * Per-request bookkeeping: which capabilities were provided, plus the\n * duplicate-provide notification. Capability VALUES live in per-capability\n * WeakMaps (see `createCapability`), not here — this only tracks presence.\n */\nexport class CapabilityRegistry {\n private readonly provided = new Set<CapabilityHandle>()\n private onDuplicate?: (name: string) => void\n\n /** Register a callback fired when a handle is provided more than once. */\n setOnDuplicate(cb: (name: string) => void): void {\n this.onDuplicate = cb\n }\n\n /** Record that `handle` was provided; fire the duplicate callback on repeats. */\n markProvided(handle: CapabilityHandle): void {\n if (this.provided.has(handle)) this.onDuplicate?.(handle.capabilityName)\n this.provided.add(handle)\n }\n\n has(handle: CapabilityHandle): boolean {\n return this.provided.has(handle)\n }\n}\n\n/**\n * Create a capability. Returns a hybrid handle that destructures to\n * `[get, provide]` and is itself the identity for `requires`/`provides`.\n *\n * Curried so the value type is supplied explicitly while the name literal is\n * INFERRED from the argument: `createCapability<T>()('name')`. (A single call\n * `createCapability<T>('name')` cannot work — supplying `T` explicitly stops\n * TypeScript inferring the name, collapsing it to `string` and defeating the\n * compile-time coverage check that keys on the literal name.)\n *\n * @example Provider + consumer middleware\n * ```ts\n * const counterCapability = createCapability<{ value: number }>()('counter')\n * const [getCounter, provideCounter] = counterCapability\n *\n * const withCounter = defineChatMiddleware({\n * name: 'counter',\n * provides: [counterCapability],\n * setup(ctx) { provideCounter(ctx, { value: 0 }) },\n * })\n *\n * const readsCounter = defineChatMiddleware({\n * name: 'reads-counter',\n * requires: [counterCapability],\n * onChunk(ctx) { getCounter(ctx).value++ },\n * })\n *\n * chat({ adapter, messages, middleware: [withCounter, readsCounter] })\n * ```\n *\n * @remarks Capability `name`s must be unique across your app: compile-time\n * coverage tracking keys on the name literal (runtime keys on reference).\n */\nexport function createCapability<TValue = unknown>(): <\n const TName extends string,\n>(\n name: TName,\n) => Capability<TValue, TName> {\n return <const TName extends string>(\n name: TName,\n ): Capability<TValue, TName> => {\n // Each capability owns a typed WeakMap keyed by the context object. Because\n // the value type is TValue, reads are typed with no assertion.\n const values = new WeakMap<CapabilityContext, TValue>()\n\n function get(ctx: CapabilityContext): TValue\n function get(\n ctx: CapabilityContext,\n opts: { optional: true },\n ): TValue | undefined\n function get(\n ctx: CapabilityContext,\n opts?: CapabilityGetOptions,\n ): TValue | undefined {\n if (!values.has(ctx)) {\n if (opts?.optional) return undefined\n throw new Error(\n `Capability \"${name}\" was requested but never provided. Ensure a ` +\n `middleware provides it in setup(), ordered before this consumer.`,\n )\n }\n return values.get(ctx)\n }\n\n const provide: CapabilityProvider<TValue> = (ctx, value) => {\n values.set(ctx, value)\n ctx.capabilities.markProvided(handle)\n }\n\n const pair: readonly [\n CapabilityGetter<TValue>,\n CapabilityProvider<TValue>,\n ] = [get, provide]\n // Object.assign's return type is the intersection of the tuple and the\n // props, which IS Capability<TValue, TName> — no cast needed.\n const handle = Object.assign(pair, {\n capabilityName: name,\n has: (ctx: CapabilityContext) => values.has(ctx),\n })\n return handle\n }\n}\n"],"mappings":";;;;;;AA4DA,IAAa,qBAAb,MAAgC;CAC9B,2BAA4B,IAAI,IAAsB;CACtD;;CAGA,eAAe,IAAkC;EAC/C,KAAK,cAAc;CACrB;;CAGA,aAAa,QAAgC;EAC3C,IAAI,KAAK,SAAS,IAAI,MAAM,GAAG,KAAK,cAAc,OAAO,cAAc;EACvE,KAAK,SAAS,IAAI,MAAM;CAC1B;CAEA,IAAI,QAAmC;EACrC,OAAO,KAAK,SAAS,IAAI,MAAM;CACjC;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmCA,SAAgB,mBAIe;CAC7B,QACE,SAC8B;EAG9B,MAAM,yBAAS,IAAI,QAAmC;EAOtD,SAAS,IACP,KACA,MACoB;GACpB,IAAI,CAAC,OAAO,IAAI,GAAG,GAAG;IACpB,IAAI,MAAM,UAAU,OAAO,KAAA;IAC3B,MAAM,IAAI,MACR,eAAe,KAAK,8GAEtB;GACF;GACA,OAAO,OAAO,IAAI,GAAG;EACvB;EAEA,MAAM,WAAuC,KAAK,UAAU;GAC1D,OAAO,IAAI,KAAK,KAAK;GACrB,IAAI,aAAa,aAAa,MAAM;EACtC;EAQA,MAAM,SAAS,OAAO,OAAO,CAHxB,KAAK,OAGmB,GAAM;GACjC,gBAAgB;GAChB,MAAM,QAA2B,OAAO,IAAI,GAAG;EACjD,CAAC;EACD,OAAO;CACT;AACF"}
@@ -1,4 +1,4 @@
1
- import { StreamChunk } from '../../../types.js';
1
+ import { AgentLoopState, StreamChunk } from '../../../types.js';
2
2
  import { InternalLogger } from '../../../logger/internal-logger.js';
3
3
  import { AbortInfo, AfterToolCallInfo, BeforeToolCallDecision, ChatMiddleware, ChatMiddlewareConfig, ChatMiddlewareContext, ErrorInfo, FinishInfo, IterationInfo, SandboxFileHookEvent, StructuredOutputMiddlewareConfig, ToolCallHookContext, ToolPhaseCompleteInfo, UsageInfo } from './types.js';
4
4
  /**
@@ -68,16 +68,103 @@ export declare class MiddlewareRunner<TContext = unknown> {
68
68
  * Run onUsage on all middleware in order.
69
69
  */
70
70
  runOnUsage(ctx: ChatMiddlewareContext<TContext>, usage: UsageInfo): Promise<void>;
71
+ /**
72
+ * Await ONE terminal hook and RETURN its throw instead of letting it escape
73
+ * the caller's loop, logging it on the `errors` channel first so the failure
74
+ * is never invisible. `undefined` means the hook completed.
75
+ *
76
+ * Capturing (rather than swallowing at this level) is what lets isolation and
77
+ * reporting coexist: every caller gives every middleware its turn, and then
78
+ * each decides on its own whether the collected failures are worth telling the
79
+ * caller about. See {@link runOnFinish} vs {@link runOnAbort} /
80
+ * {@link runOnError}.
81
+ */
82
+ private captureTerminalHook;
71
83
  /**
72
84
  * Run onFinish on all middleware in order.
85
+ *
86
+ * ISOLATED **and** REPORTED. `onFinish` is the only terminal fan-out on the
87
+ * SUCCESS path, and it is where `withPersistence.onFinish` writes the
88
+ * assistant turn through the store. So the two properties are needed together
89
+ * and neither may be traded for the other:
90
+ *
91
+ * - ISOLATION: every middleware's hook runs even if an earlier one threw, so a
92
+ * transient store error cannot skip a later middleware's own bookkeeping.
93
+ * Each failure is captured by {@link captureTerminalHook}, not propagated
94
+ * mid-loop.
95
+ * - REPORTING: after the loop, the failures are rethrown. `chat()`'s catch
96
+ * treats what we throw as a genuine error (it is not a
97
+ * `MiddlewareAbortError`, and `structuralInterruptFailure` does not match
98
+ * it) and rethrows it out of the generator.
99
+ *
100
+ * What that rethrow can and cannot achieve depends on the transport, because
101
+ * this fan-out is awaited AFTER the adapter's `RUN_FINISHED` has already been
102
+ * yielded (`chat()` yields terminal chunks while streaming, then awaits this
103
+ * hook on its way out). The success terminal is therefore already gone; the
104
+ * rethrow can only append to what the consumer saw, never retract it:
105
+ *
106
+ * - NON-DURABLE transport: the throw escapes the generator mid-response, and
107
+ * the SSE / HTTP-stream encoder turns it into a TRAILING `RUN_ERROR` on the
108
+ * wire carrying the store's own message and `code`. `ai-client` surfaces
109
+ * that as an error status, so the user is not told the turn was saved when
110
+ * it was not.
111
+ * - DURABLE transport: the throw reaches the durability sink instead. The
112
+ * terminal was already persisted AND forwarded, so the sink deliberately
113
+ * does NOT append a second, contradictory terminal, and `terminalForwarded`
114
+ * (see `stream-to-response.ts`) suppresses the rethrow to the live consumer.
115
+ * The `RUN_FINISHED` stands and the failure is RECORDED SERVER-SIDE on the
116
+ * sink's `errors` channel. That is the intended outcome, not a gap: the save
117
+ * failed, not the run — the consumer did receive the complete stream, so
118
+ * telling it the run errored would be the lie. What the rethrow buys here is
119
+ * that the sink sees the failure at all; while this loop swallowed, the only
120
+ * trace anywhere was {@link captureTerminalHook}'s log line.
121
+ *
122
+ * Either way, swallowing is the one option ruled out: a failed
123
+ * `messages.append` would otherwise leave a `completed` run record with the
124
+ * assistant turn missing from storage and nothing beyond a middleware log
125
+ * line, and the client would go on to send a history the server has no record
126
+ * of.
127
+ *
128
+ * A single failure is rethrown AS-IS so the store's own error — its message,
129
+ * `cause`, `code` and `instanceof` identity — is what reaches the caller and
130
+ * the wire; wrapping the common case would bury it. Two or more become an
131
+ * `AggregateError` (never a `MiddlewareAbortError`, so it cannot be mistaken
132
+ * for an abort) rather than picking a winner and dropping the rest.
73
133
  */
74
134
  runOnFinish(ctx: ChatMiddlewareContext<TContext>, info: FinishInfo): Promise<void>;
75
135
  /**
76
136
  * Run onAbort on all middleware in order.
137
+ *
138
+ * ISOLATED and DELIBERATELY SWALLOWED. `onAbort` is a pure teardown fan-out
139
+ * released from `chat()`'s `finally`, on a path where the outcome is already
140
+ * decided: the run stopped, and the caller is being told why. A throw here has
141
+ * nothing better to report than the abort reason it would DISPLACE — the
142
+ * `finally` would surface a flaky store's error in place of "client
143
+ * disconnected" — so failures are logged on the `errors` channel and go no
144
+ * further. That is not a silent failure; it is refusing to let teardown
145
+ * rewrite an outcome it did not produce.
146
+ *
147
+ * Isolation matters independently: these hooks release PER-MIDDLEWARE
148
+ * resources (`withSandbox.onAbort` detaches or destroys the sandbox and stamps
149
+ * `detachedSince`; `withPersistence.onAbort` records the run status through the
150
+ * store), so an unguarded loop turns one transient store error into a
151
+ * permanently leaked sandbox for every middleware ordered after it.
77
152
  */
78
153
  runOnAbort(ctx: ChatMiddlewareContext<TContext>, info: AbortInfo): Promise<void>;
79
154
  /**
80
155
  * Run onError on all middleware in order.
156
+ *
157
+ * ISOLATED and DELIBERATELY SWALLOWED, for the same reason as
158
+ * {@link runOnAbort} and NOT merely because it is teardown: the run has
159
+ * already failed, `info.error` IS that failure, and `chat()` rethrows it to the
160
+ * caller the moment this fan-out returns. A propagated hook throw could only
161
+ * REPLACE the run's real error with a teardown artifact — strictly less
162
+ * information for the caller, who is already learning the run failed. Reporting
163
+ * would buy nothing and cost the diagnosis, so failures are logged on the
164
+ * `errors` channel and stop there.
165
+ *
166
+ * Contrast {@link runOnFinish}, where nothing else is telling the caller
167
+ * anything is wrong — which is why that one reports.
81
168
  */
82
169
  runOnError(ctx: ChatMiddlewareContext<TContext>, info: ErrorInfo): Promise<void>;
83
170
  /**
@@ -85,6 +172,12 @@ export declare class MiddlewareRunner<TContext = unknown> {
85
172
  * Called at the start of each agent loop iteration.
86
173
  */
87
174
  runOnIteration(ctx: ChatMiddlewareContext<TContext>, info: IterationInfo): Promise<void>;
175
+ /**
176
+ * Run onShouldContinue through middleware in order (AND semantics).
177
+ * Any explicit `false` stops further iterations; `true` / void / undefined pass.
178
+ * Called after `agentLoopStrategy` has already approved continuation.
179
+ */
180
+ runOnShouldContinue(ctx: ChatMiddlewareContext<TContext>, state: AgentLoopState): Promise<boolean>;
88
181
  /**
89
182
  * Run onToolPhaseComplete on all middleware in order.
90
183
  * Called after all tool calls in an iteration have been processed.