@athenaintel/react 0.20.0 → 0.21.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 (347) hide show
  1. package/README.md +74 -0
  2. package/dist/assets/use-asset-embed.cjs +8 -0
  3. package/dist/assets/use-asset-embed.cjs.map +1 -1
  4. package/dist/assets/use-asset-embed.js +8 -0
  5. package/dist/assets/use-asset-embed.js.map +1 -1
  6. package/dist/chat/StatewireApprovalCard.cjs +22 -4
  7. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  8. package/dist/chat/StatewireApprovalCard.d.ts +14 -0
  9. package/dist/chat/StatewireApprovalCard.js +23 -5
  10. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  11. package/dist/chat/StatewireClientToolBridge.cjs +2 -1
  12. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  13. package/dist/chat/StatewireClientToolBridge.js +2 -1
  14. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  15. package/dist/chat/StatewireQueuedMessages.cjs +2 -1
  16. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  17. package/dist/chat/StatewireQueuedMessages.js +2 -1
  18. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  19. package/dist/chat/athena-citation-links.cjs +17 -1
  20. package/dist/chat/athena-citation-links.cjs.map +1 -1
  21. package/dist/chat/athena-citation-links.js +17 -1
  22. package/dist/chat/athena-citation-links.js.map +1 -1
  23. package/dist/chat/statewire-client-tools.cjs +2 -1
  24. package/dist/chat/statewire-client-tools.cjs.map +1 -1
  25. package/dist/chat/statewire-client-tools.js +2 -1
  26. package/dist/chat/statewire-client-tools.js.map +1 -1
  27. package/dist/chat/super-grouping/SuperGroupingCard.cjs +69 -187
  28. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  29. package/dist/chat/super-grouping/SuperGroupingCard.d.ts +10 -6
  30. package/dist/chat/super-grouping/SuperGroupingCard.js +73 -191
  31. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  32. package/dist/chat/super-grouping/ToolTimeline.cjs +131 -0
  33. package/dist/chat/super-grouping/ToolTimeline.cjs.map +1 -0
  34. package/dist/chat/super-grouping/ToolTimeline.d.ts +16 -0
  35. package/dist/chat/super-grouping/ToolTimeline.js +131 -0
  36. package/dist/chat/super-grouping/ToolTimeline.js.map +1 -0
  37. package/dist/chat/super-grouping/activity-timeline.cjs +14 -8
  38. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  39. package/dist/chat/super-grouping/activity-timeline.d.ts +3 -0
  40. package/dist/chat/super-grouping/activity-timeline.js +14 -8
  41. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  42. package/dist/chat/super-grouping/timeline-step.cjs +65 -0
  43. package/dist/chat/super-grouping/timeline-step.cjs.map +1 -0
  44. package/dist/chat/super-grouping/timeline-step.d.ts +17 -0
  45. package/dist/chat/super-grouping/timeline-step.js +65 -0
  46. package/dist/chat/super-grouping/timeline-step.js.map +1 -0
  47. package/dist/chat/tiptap-text.cjs +8 -8
  48. package/dist/chat/tiptap-text.cjs.map +1 -1
  49. package/dist/chat/use-athena-run-live.cjs +2 -1
  50. package/dist/chat/use-athena-run-live.cjs.map +1 -1
  51. package/dist/chat/use-athena-run-live.js +2 -1
  52. package/dist/chat/use-athena-run-live.js.map +1 -1
  53. package/dist/chat/use-statewire-approval-request.cjs +7 -2
  54. package/dist/chat/use-statewire-approval-request.cjs.map +1 -1
  55. package/dist/chat/use-statewire-approval-request.d.ts +12 -2
  56. package/dist/chat/use-statewire-approval-request.js +8 -3
  57. package/dist/chat/use-statewire-approval-request.js.map +1 -1
  58. package/dist/chat-ui/composer/tiptap-composer.cjs +24 -65
  59. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  60. package/dist/chat-ui/composer/tiptap-composer.js +24 -65
  61. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  62. package/dist/chat-ui/index.cjs +2 -1
  63. package/dist/chat-ui/index.cjs.map +1 -1
  64. package/dist/chat-ui/index.js +2 -1
  65. package/dist/chat-ui/index.js.map +1 -1
  66. package/dist/chat-ui/mentions/mention-chip.cjs +49 -4
  67. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  68. package/dist/chat-ui/mentions/mention-chip.d.ts +11 -0
  69. package/dist/chat-ui/mentions/mention-chip.js +50 -5
  70. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  71. package/dist/chat-ui/mentions/mention-extension.cjs +18 -6
  72. package/dist/chat-ui/mentions/mention-extension.cjs.map +1 -1
  73. package/dist/chat-ui/mentions/mention-extension.d.ts +2 -0
  74. package/dist/chat-ui/mentions/mention-extension.js +18 -6
  75. package/dist/chat-ui/mentions/mention-extension.js.map +1 -1
  76. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.cjs +1 -1
  77. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.cjs.map +1 -1
  78. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.js +1 -1
  79. package/dist/chat-ui/mentions/suggestions/components/mention-suggestion-list.js.map +1 -1
  80. package/dist/chat-ui/mentions/suggestions/components/menu-item.cjs +9 -6
  81. package/dist/chat-ui/mentions/suggestions/components/menu-item.cjs.map +1 -1
  82. package/dist/chat-ui/mentions/suggestions/components/menu-item.d.ts +2 -2
  83. package/dist/chat-ui/mentions/suggestions/components/menu-item.js +10 -7
  84. package/dist/chat-ui/mentions/suggestions/components/menu-item.js.map +1 -1
  85. package/dist/chat-ui/mentions/suggestions/components/menu-list.cjs +2 -2
  86. package/dist/chat-ui/mentions/suggestions/components/menu-list.cjs.map +1 -1
  87. package/dist/chat-ui/mentions/suggestions/components/menu-list.d.ts +2 -2
  88. package/dist/chat-ui/mentions/suggestions/components/menu-list.js +2 -2
  89. package/dist/chat-ui/mentions/suggestions/components/menu-list.js.map +1 -1
  90. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +2 -1
  91. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  92. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +3 -2
  93. package/dist/chat-ui/mentions/suggestions/data/api-client.js +2 -1
  94. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  95. package/dist/chat-ui/messages/message-components.cjs +6 -29
  96. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  97. package/dist/chat-ui/messages/message-components.d.ts +1 -13
  98. package/dist/chat-ui/messages/message-components.js +8 -31
  99. package/dist/chat-ui/messages/message-components.js.map +1 -1
  100. package/dist/chat-ui/messages/runtime-message.cjs +3 -2
  101. package/dist/chat-ui/messages/runtime-message.cjs.map +1 -1
  102. package/dist/chat-ui/messages/runtime-message.js +3 -2
  103. package/dist/chat-ui/messages/runtime-message.js.map +1 -1
  104. package/dist/chat-ui/messages/statewire-continue-run.cjs +23 -8
  105. package/dist/chat-ui/messages/statewire-continue-run.cjs.map +1 -1
  106. package/dist/chat-ui/messages/statewire-continue-run.d.ts +14 -4
  107. package/dist/chat-ui/messages/statewire-continue-run.js +23 -8
  108. package/dist/chat-ui/messages/statewire-continue-run.js.map +1 -1
  109. package/dist/chat-ui/messages/user-edit-composer.cjs +2 -1
  110. package/dist/chat-ui/messages/user-edit-composer.cjs.map +1 -1
  111. package/dist/chat-ui/messages/user-edit-composer.js +2 -1
  112. package/dist/chat-ui/messages/user-edit-composer.js.map +1 -1
  113. package/dist/chat-ui/rich-text/chat-message-body.cjs +76 -0
  114. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -0
  115. package/dist/chat-ui/rich-text/chat-message-body.d.ts +5 -0
  116. package/dist/chat-ui/rich-text/chat-message-body.js +76 -0
  117. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -0
  118. package/dist/chat-ui/rich-text/chat-node-views.cjs +51 -0
  119. package/dist/chat-ui/rich-text/chat-node-views.cjs.map +1 -0
  120. package/dist/chat-ui/rich-text/chat-node-views.d.ts +13 -0
  121. package/dist/chat-ui/rich-text/chat-node-views.js +51 -0
  122. package/dist/chat-ui/rich-text/chat-node-views.js.map +1 -0
  123. package/dist/chat-ui/rich-text/chat-text-schema.cjs +70 -0
  124. package/dist/chat-ui/rich-text/chat-text-schema.cjs.map +1 -0
  125. package/dist/chat-ui/rich-text/chat-text-schema.d.ts +7 -0
  126. package/dist/chat-ui/rich-text/chat-text-schema.js +70 -0
  127. package/dist/chat-ui/rich-text/chat-text-schema.js.map +1 -0
  128. package/dist/chat-ui/rich-text/dom-output-spec.cjs +83 -0
  129. package/dist/chat-ui/rich-text/dom-output-spec.cjs.map +1 -0
  130. package/dist/chat-ui/rich-text/dom-output-spec.d.ts +11 -0
  131. package/dist/chat-ui/rich-text/dom-output-spec.js +83 -0
  132. package/dist/chat-ui/rich-text/dom-output-spec.js.map +1 -0
  133. package/dist/chat-ui/statewire.cjs +2 -1
  134. package/dist/chat-ui/statewire.cjs.map +1 -1
  135. package/dist/chat-ui/statewire.js +2 -1
  136. package/dist/chat-ui/statewire.js.map +1 -1
  137. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -1
  138. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  139. package/dist/chat-ui/thread/statewire-history-edge.js +2 -1
  140. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  141. package/dist/index.cjs +2 -1
  142. package/dist/index.cjs.map +1 -1
  143. package/dist/index.js +2 -1
  144. package/dist/index.js.map +1 -1
  145. package/dist/node_modules/qrcode-generator/dist/qrcode.cjs +32 -0
  146. package/dist/node_modules/qrcode-generator/dist/qrcode.cjs.map +1 -0
  147. package/dist/node_modules/qrcode-generator/dist/qrcode.js +31 -0
  148. package/dist/node_modules/qrcode-generator/dist/qrcode.js.map +1 -0
  149. package/dist/node_modules/zod/v4/classic/errors.cjs +44 -0
  150. package/dist/node_modules/zod/v4/classic/errors.cjs.map +1 -0
  151. package/dist/node_modules/zod/v4/classic/errors.js +44 -0
  152. package/dist/node_modules/zod/v4/classic/errors.js.map +1 -0
  153. package/dist/node_modules/zod/v4/classic/iso.cjs +43 -0
  154. package/dist/node_modules/zod/v4/classic/iso.cjs.map +1 -0
  155. package/dist/node_modules/zod/v4/classic/iso.js +43 -0
  156. package/dist/node_modules/zod/v4/classic/iso.js.map +1 -0
  157. package/dist/node_modules/zod/v4/classic/parse.cjs +29 -0
  158. package/dist/node_modules/zod/v4/classic/parse.cjs.map +1 -0
  159. package/dist/node_modules/zod/v4/classic/parse.js +29 -0
  160. package/dist/node_modules/zod/v4/classic/parse.js.map +1 -0
  161. package/dist/node_modules/zod/v4/classic/schemas.cjs +871 -0
  162. package/dist/node_modules/zod/v4/classic/schemas.cjs.map +1 -0
  163. package/dist/node_modules/zod/v4/classic/schemas.js +871 -0
  164. package/dist/node_modules/zod/v4/classic/schemas.js.map +1 -0
  165. package/dist/node_modules/zod/v4/core/api.cjs +570 -0
  166. package/dist/node_modules/zod/v4/core/api.cjs.map +1 -0
  167. package/dist/node_modules/zod/v4/core/api.js +570 -0
  168. package/dist/node_modules/zod/v4/core/api.js.map +1 -0
  169. package/dist/node_modules/zod/v4/core/checks.cjs +411 -0
  170. package/dist/node_modules/zod/v4/core/checks.cjs.map +1 -0
  171. package/dist/node_modules/zod/v4/core/checks.js +411 -0
  172. package/dist/node_modules/zod/v4/core/checks.js.map +1 -0
  173. package/dist/node_modules/zod/v4/core/core.cjs +77 -0
  174. package/dist/node_modules/zod/v4/core/core.cjs.map +1 -0
  175. package/dist/node_modules/zod/v4/core/core.js +77 -0
  176. package/dist/node_modules/zod/v4/core/core.js.map +1 -0
  177. package/dist/node_modules/zod/v4/core/doc.cjs +38 -0
  178. package/dist/node_modules/zod/v4/core/doc.cjs.map +1 -0
  179. package/dist/node_modules/zod/v4/core/doc.js +38 -0
  180. package/dist/node_modules/zod/v4/core/doc.js.map +1 -0
  181. package/dist/node_modules/zod/v4/core/errors.cjs +76 -0
  182. package/dist/node_modules/zod/v4/core/errors.cjs.map +1 -0
  183. package/dist/node_modules/zod/v4/core/errors.js +76 -0
  184. package/dist/node_modules/zod/v4/core/errors.js.map +1 -0
  185. package/dist/node_modules/zod/v4/core/json-schema-processors.cjs +312 -0
  186. package/dist/node_modules/zod/v4/core/json-schema-processors.cjs.map +1 -0
  187. package/dist/node_modules/zod/v4/core/json-schema-processors.js +312 -0
  188. package/dist/node_modules/zod/v4/core/json-schema-processors.js.map +1 -0
  189. package/dist/node_modules/zod/v4/core/parse.cjs +96 -0
  190. package/dist/node_modules/zod/v4/core/parse.cjs.map +1 -0
  191. package/dist/node_modules/zod/v4/core/parse.js +96 -0
  192. package/dist/node_modules/zod/v4/core/parse.js.map +1 -0
  193. package/dist/node_modules/zod/v4/core/regexes.cjs +86 -0
  194. package/dist/node_modules/zod/v4/core/regexes.cjs.map +1 -0
  195. package/dist/node_modules/zod/v4/core/regexes.js +86 -0
  196. package/dist/node_modules/zod/v4/core/regexes.js.map +1 -0
  197. package/dist/node_modules/zod/v4/core/registries.cjs +52 -0
  198. package/dist/node_modules/zod/v4/core/registries.cjs.map +1 -0
  199. package/dist/node_modules/zod/v4/core/registries.js +52 -0
  200. package/dist/node_modules/zod/v4/core/registries.js.map +1 -0
  201. package/dist/node_modules/zod/v4/core/schemas.cjs +1412 -0
  202. package/dist/node_modules/zod/v4/core/schemas.cjs.map +1 -0
  203. package/dist/node_modules/zod/v4/core/schemas.js +1413 -0
  204. package/dist/node_modules/zod/v4/core/schemas.js.map +1 -0
  205. package/dist/node_modules/zod/v4/core/to-json-schema.cjs +370 -0
  206. package/dist/node_modules/zod/v4/core/to-json-schema.cjs.map +1 -0
  207. package/dist/node_modules/zod/v4/core/to-json-schema.js +370 -0
  208. package/dist/node_modules/zod/v4/core/to-json-schema.js.map +1 -0
  209. package/dist/node_modules/zod/v4/core/util.cjs +444 -0
  210. package/dist/node_modules/zod/v4/core/util.cjs.map +1 -0
  211. package/dist/node_modules/zod/v4/core/util.js +444 -0
  212. package/dist/node_modules/zod/v4/core/util.js.map +1 -0
  213. package/dist/node_modules/zod/v4/core/versions.cjs +9 -0
  214. package/dist/node_modules/zod/v4/core/versions.cjs.map +1 -0
  215. package/dist/node_modules/zod/v4/core/versions.js +9 -0
  216. package/dist/node_modules/zod/v4/core/versions.js.map +1 -0
  217. package/dist/packages/agent-runtime/src/client-tool-bridge.cjs +7 -1
  218. package/dist/packages/agent-runtime/src/client-tool-bridge.cjs.map +1 -1
  219. package/dist/packages/agent-runtime/src/client-tool-bridge.js +7 -1
  220. package/dist/packages/agent-runtime/src/client-tool-bridge.js.map +1 -1
  221. package/dist/packages/agent-runtime/src/conversation-list.cjs +4 -0
  222. package/dist/packages/agent-runtime/src/conversation-list.cjs.map +1 -1
  223. package/dist/packages/agent-runtime/src/conversation-list.js +4 -0
  224. package/dist/packages/agent-runtime/src/conversation-list.js.map +1 -1
  225. package/dist/packages/agent-runtime/src/edit-command.cjs.map +1 -1
  226. package/dist/packages/agent-runtime/src/edit-command.js.map +1 -1
  227. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs +2 -0
  228. package/dist/packages/agent-runtime/src/generated/runconfig-contract.cjs.map +1 -1
  229. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js +2 -0
  230. package/dist/packages/agent-runtime/src/generated/runconfig-contract.js.map +1 -1
  231. package/dist/packages/agent-runtime/src/interrupts.cjs +31 -7
  232. package/dist/packages/agent-runtime/src/interrupts.cjs.map +1 -1
  233. package/dist/packages/agent-runtime/src/interrupts.js +31 -7
  234. package/dist/packages/agent-runtime/src/interrupts.js.map +1 -1
  235. package/dist/packages/agent-runtime/src/messages.cjs +35 -5
  236. package/dist/packages/agent-runtime/src/messages.cjs.map +1 -1
  237. package/dist/packages/agent-runtime/src/messages.js +35 -5
  238. package/dist/packages/agent-runtime/src/messages.js.map +1 -1
  239. package/dist/packages/agent-runtime/src/protocol.cjs +4 -6
  240. package/dist/packages/agent-runtime/src/protocol.cjs.map +1 -1
  241. package/dist/packages/agent-runtime/src/protocol.js +4 -6
  242. package/dist/packages/agent-runtime/src/protocol.js.map +1 -1
  243. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs +296 -14
  244. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  245. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js +293 -11
  246. package/dist/packages/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  247. package/dist/packages/agent-runtime/src/screen-point.cjs +68 -0
  248. package/dist/packages/agent-runtime/src/screen-point.cjs.map +1 -0
  249. package/dist/packages/agent-runtime/src/screen-point.js +68 -0
  250. package/dist/packages/agent-runtime/src/screen-point.js.map +1 -0
  251. package/dist/packages/agent-runtime/src/statewire-transport.cjs +6 -1
  252. package/dist/packages/agent-runtime/src/statewire-transport.cjs.map +1 -1
  253. package/dist/packages/agent-runtime/src/statewire-transport.js +6 -1
  254. package/dist/packages/agent-runtime/src/statewire-transport.js.map +1 -1
  255. package/dist/packages/agent-runtime/src/thread-handoff.cjs +85 -0
  256. package/dist/packages/agent-runtime/src/thread-handoff.cjs.map +1 -0
  257. package/dist/packages/agent-runtime/src/thread-handoff.js +85 -0
  258. package/dist/packages/agent-runtime/src/thread-handoff.js.map +1 -0
  259. package/dist/packages/agent-runtime/src/thread-snapshot-cache.cjs +1 -1
  260. package/dist/packages/agent-runtime/src/thread-snapshot-cache.cjs.map +1 -1
  261. package/dist/packages/agent-runtime/src/thread-snapshot-cache.js +1 -1
  262. package/dist/packages/agent-runtime/src/thread-snapshot-cache.js.map +1 -1
  263. package/dist/packages/agent-runtime/src/tool-metadata.cjs +0 -8
  264. package/dist/packages/agent-runtime/src/tool-metadata.cjs.map +1 -1
  265. package/dist/packages/agent-runtime/src/tool-metadata.js +0 -8
  266. package/dist/packages/agent-runtime/src/tool-metadata.js.map +1 -1
  267. package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs +14 -0
  268. package/dist/packages/agent-runtime/src/use-deep-agent-config.cjs.map +1 -0
  269. package/dist/packages/agent-runtime/src/use-deep-agent-config.js +14 -0
  270. package/dist/packages/agent-runtime/src/use-deep-agent-config.js.map +1 -0
  271. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs +21 -5
  272. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.cjs.map +1 -1
  273. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js +21 -5
  274. package/dist/packages/agent-runtime/src/use-deep-agent-edit-submit.js.map +1 -1
  275. package/dist/packages/agent-runtime/src/use-deep-agent-runtime.cjs +13 -3
  276. package/dist/packages/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  277. package/dist/packages/agent-runtime/src/use-deep-agent-runtime.js +14 -4
  278. package/dist/packages/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  279. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs +35 -15
  280. package/dist/packages/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  281. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js +35 -15
  282. package/dist/packages/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  283. package/dist/packages/agent-runtime/src/voice-presentation.cjs.map +1 -1
  284. package/dist/packages/agent-runtime/src/voice-presentation.js.map +1 -1
  285. package/dist/packages/react/package.json.cjs +1 -1
  286. package/dist/packages/react/package.json.js +1 -1
  287. package/dist/provider/AthenaProvider.cjs +2 -1
  288. package/dist/provider/AthenaProvider.cjs.map +1 -1
  289. package/dist/provider/AthenaProvider.js +2 -1
  290. package/dist/provider/AthenaProvider.js.map +1 -1
  291. package/dist/runtime/statewire-lifecycle.cjs +2 -1
  292. package/dist/runtime/statewire-lifecycle.cjs.map +1 -1
  293. package/dist/runtime/statewire-lifecycle.js +2 -1
  294. package/dist/runtime/statewire-lifecycle.js.map +1 -1
  295. package/dist/runtime/useAthenaStatewireRuntime.cjs +2 -1
  296. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  297. package/dist/runtime/useAthenaStatewireRuntime.js +2 -1
  298. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  299. package/dist/styles.css +1 -1
  300. package/dist/threads/conversation-list-fetcher.cjs +4 -0
  301. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  302. package/dist/threads/conversation-list-fetcher.js +4 -0
  303. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  304. package/dist/threads/statewire-thread-list.cjs +2 -1
  305. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  306. package/dist/threads/statewire-thread-list.js +2 -1
  307. package/dist/threads/statewire-thread-list.js.map +1 -1
  308. package/dist/threads/use-rest-conversation-history.cjs +2 -1
  309. package/dist/threads/use-rest-conversation-history.cjs.map +1 -1
  310. package/dist/threads/use-rest-conversation-history.js +2 -1
  311. package/dist/threads/use-rest-conversation-history.js.map +1 -1
  312. package/dist/threads/use-statewire-conversation-history.cjs +2 -1
  313. package/dist/threads/use-statewire-conversation-history.cjs.map +1 -1
  314. package/dist/threads/use-statewire-conversation-history.d.ts +1 -0
  315. package/dist/threads/use-statewire-conversation-history.js +2 -1
  316. package/dist/threads/use-statewire-conversation-history.js.map +1 -1
  317. package/dist/tools/index.cjs +2 -1
  318. package/dist/tools/index.cjs.map +1 -1
  319. package/dist/tools/index.js +2 -1
  320. package/dist/tools/index.js.map +1 -1
  321. package/dist/tools/misc-tool-uis.cjs +2 -1
  322. package/dist/tools/misc-tool-uis.cjs.map +1 -1
  323. package/dist/tools/misc-tool-uis.js +2 -1
  324. package/dist/tools/misc-tool-uis.js.map +1 -1
  325. package/dist/tools/primitives.cjs +2 -1
  326. package/dist/tools/primitives.cjs.map +1 -1
  327. package/dist/tools/primitives.js +2 -1
  328. package/dist/tools/primitives.js.map +1 -1
  329. package/dist/tools/tool-fallback.cjs +2 -1
  330. package/dist/tools/tool-fallback.cjs.map +1 -1
  331. package/dist/tools/tool-fallback.js +2 -1
  332. package/dist/tools/tool-fallback.js.map +1 -1
  333. package/dist/tools/voice-update-tool-ui.cjs +2 -1
  334. package/dist/tools/voice-update-tool-ui.cjs.map +1 -1
  335. package/dist/tools/voice-update-tool-ui.js +2 -1
  336. package/dist/tools/voice-update-tool-ui.js.map +1 -1
  337. package/dist/voice/index.cjs +6 -0
  338. package/dist/voice/index.cjs.map +1 -0
  339. package/dist/voice/index.d.ts +2 -0
  340. package/dist/voice/index.js +6 -0
  341. package/dist/voice/index.js.map +1 -0
  342. package/dist/voice/use-athena-voice-session.cjs +315 -0
  343. package/dist/voice/use-athena-voice-session.cjs.map +1 -0
  344. package/dist/voice/use-athena-voice-session.d.ts +145 -0
  345. package/dist/voice/use-athena-voice-session.js +315 -0
  346. package/dist/voice/use-athena-voice-session.js.map +1 -0
  347. package/package.json +17 -3
@@ -1 +1 @@
1
- {"version":3,"file":"interrupts.js","sources":["../../../../../agent-runtime/src/interrupts.ts"],"sourcesContent":["/**\n * Human-in-the-loop interrupt primitives shared by every statewire chat\n * surface (Olympus, the React SDK, the Office add-in, the Chrome extension,\n * mobile).\n *\n * The agora deep-agent runtime parks a gated run on an `interrupt` input\n * request whose payload carries the Athena `hitl_approval` shape\n * (`athena_deep_agent/hitl_bridge.py`). A surface answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. This module holds the\n * payload contract (narrowing, resume values) and the shared resume-lock state\n * machine; rendering stays with each surface.\n *\n * Deliberately parameterized rather than self-wiring: these helpers never call\n * `useStatewireRuns`/`useAuiState` themselves. Each surface reads its own\n * statewire hooks (which its tests already mock) and passes the values in, so\n * the shared semantics cannot drift while per-surface wiring stays testable.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { hasClientToolInterruptSource } from './client-tools';\nimport { readDeepAgentStreamValues } from './statewire-state';\n\n/** Human-in-the-loop approval payload the agora deep-agent runtime parks a run\n * on (shaped by `athena_deep_agent/hitl_bridge.py`). */\nexport interface HitlApprovalPayload {\n source: string;\n type: string;\n message: string;\n /** Risk headline/facts/warnings, when the host includes them. */\n approval_context?: unknown;\n context?: {\n tool_name?: string;\n tool_id?: string;\n tool_call_id?: string;\n tool_args?: Record<string, unknown>;\n pending_action_count?: number;\n approval_context?: unknown;\n /**\n * Every pending gated call (name + args), in decision order. Absent on\n * older backends — fall back to the single `tool_name`/`tool_args` pair.\n * Per-dispatch payloads also carry each lane's `tool_call_id` (the lane\n * card key) and `interrupt_id` (the lane resume key).\n */\n action_requests?: {\n name?: string;\n args?: Record<string, unknown>;\n tool_call_id?: string;\n interrupt_id?: string;\n }[];\n /**\n * True when each dispatch parked on its own interrupt: decisions submit\n * per lane via `lane_decisions` and each approved lane starts immediately,\n * instead of the legacy batch where one resume must answer every lane.\n */\n per_dispatch?: boolean;\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A pending `interrupt` input request; a present `response` key marks it answered. */\nexport function isPendingInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n return request.type === 'interrupt' && !('response' in request);\n}\n\n/**\n * A pending interrupt an approval card may answer. Client-tool parks are\n * excluded — the client-tool bridge owns those, and a generic Continue resume\n * would corrupt the parked tool call. The source check is deliberately loose\n * (any payload stamped with the client-tool source) so a skewed deploy can\n * never route one here.\n */\nexport function isApprovalCardInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (!isPendingInterrupt(request)) return false;\n return !hasClientToolInterruptSource((request as { payload?: unknown }).payload);\n}\n\n/** Narrow an interrupt payload to the Athena HITL approval shape. */\nexport function asHitlApproval(value: unknown): HitlApprovalPayload | null {\n if (!isRecord(value)) return null;\n return value.type === 'hitl' &&\n value.source === 'hitl_approval' &&\n typeof value.message === 'string'\n ? (value as unknown as HitlApprovalPayload)\n : null;\n}\n\n/** Default paused-run copy when an interrupt payload carries no message. */\nexport const DEFAULT_INTERRUPT_PAUSED_MESSAGE =\n 'The agent is paused and waiting for input.';\n\n/** Best-effort human-readable message from any interrupt payload. */\nexport function readInterruptMessage(\n value: unknown,\n fallback: string = DEFAULT_INTERRUPT_PAUSED_MESSAGE,\n): string {\n if (isRecord(value) && typeof value.message === 'string' && value.message.length > 0) {\n return value.message;\n }\n return fallback;\n}\n\nexport interface PendingActionRequest {\n name: string;\n args: Record<string, unknown>;\n /** The lane's `task` tool-call id (present on per-dispatch payloads). */\n toolCallId?: string;\n /** The lane's langgraph interrupt id — the `lane_decisions` resume key. */\n interruptId?: string;\n}\n\n/**\n * Every gated call this approval covers, in decision order. One resume answers\n * ALL of them, so every entry must stay reviewable somewhere. Falls back to\n * the single first-call fields on older backends without `action_requests`.\n */\nexport function pendingActionRequests(hitl: HitlApprovalPayload | null): PendingActionRequest[] {\n const context = hitl?.context;\n if (!context) return [];\n // The payload crosses the wire with only its top-level shape narrowed\n // (`asHitlApproval`), so the nested collection must be validated before any\n // array/property access: a malformed host value must degrade to \"no\n // reviewable entries\", never crash the approval surface.\n const requests: Record<string, unknown>[] = Array.isArray(context.action_requests)\n ? context.action_requests.filter(isRecord)\n : context.tool_name && context.tool_args\n ? [{ name: context.tool_name, args: context.tool_args }]\n : [];\n return requests.map((request) => ({\n name: typeof request.name === 'string' ? request.name : 'tool',\n args: isRecord(request.args) ? request.args : {},\n ...(typeof request.tool_call_id === 'string' && { toolCallId: request.tool_call_id }),\n ...(typeof request.interrupt_id === 'string' && { interruptId: request.interrupt_id }),\n }));\n}\n\n/** An interrupt a non-resume dispatch dropped, published by the host. */\nexport interface AbandonedInputRequest {\n id: string;\n type: string;\n}\n\n/**\n * `useStatewireState` selector body for the host-published abandoned input\n * requests. Reads through {@link readDeepAgentStreamValues} so the selector is\n * safe on the pre-attach (`undefined`) wire state, and returns the raw array\n * reference so the subscription stays shallow-stable — derive (`.find`) on the\n * result, never inside the selector.\n */\nexport function readAbandonedInputRequests(\n state: unknown,\n): readonly AbandonedInputRequest[] | undefined {\n const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;\n // Wire value: anything non-array (including a malformed host publication)\n // reads as \"none\" rather than crashing the consuming surface. Identity-\n // preserving for the valid case, so it stays selector-safe.\n return Array.isArray(abandoned)\n ? (abandoned as readonly AbandonedInputRequest[])\n : undefined;\n}\n\n/** The abandoned `interrupt` entry, if any — the \"approval was cancelled\" notice. */\nexport function findAbandonedInterrupt(\n abandoned: readonly AbandonedInputRequest[] | undefined,\n): AbandonedInputRequest | undefined {\n return abandoned?.find(\n (request) => isRecord(request) && request.type === 'interrupt' && typeof request.id === 'string',\n );\n}\n\n/** Canonical reason string carried by a user rejection. */\nexport const HITL_REJECT_REASON = 'User rejected the action.';\n\n/**\n * Default {@link UseApprovalResumeLockOptions.pendingResumeGraceMs} for every\n * approval surface: how long a clicked decision waits for its resume run to\n * start before the actions unlock again. Covers a `run/input` the host\n * rejected or a disconnected transport swallowed before any run began — the\n * one failure neither identity-change nor observed-run recovery can detect.\n */\nexport const HITL_PENDING_RESUME_GRACE_MS = 15_000;\n\n/** `run/input` resume value approving the gated call(s). */\nexport function hitlResumeApprove(): Record<string, string> {\n return { action: 'approve' };\n}\n\n/** `run/input` resume value rejecting the gated call(s). */\nexport function hitlResumeReject(reason: string = HITL_REJECT_REASON): Record<string, string> {\n return { action: 'reject', reason };\n}\n\n/** `run/input` resume value for a non-HITL park (generic Continue). */\nexport function hitlResumeContinue(): Record<string, string> {\n return { action: 'continue' };\n}\n\nexport interface UseApprovalResumeLockOptions<TValue> {\n /** The pending interrupt's request id (`undefined` when nothing is parked). */\n requestId: string | undefined;\n /** The surface's thread `isRunning` read (mockable per surface). */\n isRunning: boolean;\n /** Dispatch the `run/input` resume for `requestId` on the surface's transport. */\n sendResume: (requestId: string, value: TValue) => void;\n /**\n * Re-enable the actions when no resume run started within this window —\n * covers a `run/input` that was rejected or lost before any run began, so\n * neither reset path below can fire. Omit to keep the pre-existing surface\n * behavior of staying locked until a resume run is observed.\n */\n pendingResumeGraceMs?: number;\n}\n\nexport interface ApprovalResumeLock<TValue> {\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /** Send the resume value, locking the actions. No-op while pending or unparked. */\n resume: (value: TValue) => void;\n}\n\n/**\n * Optimistic action lock shared by every approval surface: disable the\n * decision buttons the moment one is clicked, until the request identity\n * changes (it clears after the resume, or a fresh interrupt arrives). This\n * closes the double-click → duplicate-resume window.\n *\n * Recovery paths, in order of specificity:\n * 1. Identity change — the resume landed (or a new interrupt replaced the\n * request). Reset during render via React's \"previous value\" pattern.\n * 2. A resume run was observed starting and then settling with the request\n * still pending (failed/cancelled resume) — clear the lock so the user can\n * answer again.\n * 3. Optional grace window ({@link UseApprovalResumeLockOptions.pendingResumeGraceMs})\n * for a `run/input` that never started a run at all.\n */\nexport function useApprovalResumeLock<TValue = Record<string, string>>(\n options: UseApprovalResumeLockOptions<TValue>,\n): ApprovalResumeLock<TValue> {\n const { requestId, isRunning, sendResume, pendingResumeGraceMs } = options;\n\n const [pending, setPending] = useState(false);\n const prevRequestIdRef = useRef(requestId);\n if (prevRequestIdRef.current !== requestId) {\n prevRequestIdRef.current = requestId;\n if (pending) setPending(false);\n }\n\n // Track the resume run we kicked off (isRunning observed true→false while\n // pending) and clear `pending` when it ends with the request unresolved.\n const sawResumeRunRef = useRef(false);\n useEffect(() => {\n if (!pending) {\n sawResumeRunRef.current = false;\n return;\n }\n if (isRunning) {\n sawResumeRunRef.current = true;\n return;\n }\n if (sawResumeRunRef.current) {\n sawResumeRunRef.current = false;\n setPending(false);\n }\n }, [pending, isRunning]);\n\n // Pre-start failure recovery: re-enable after the grace window when no\n // resume run began; answering the still-parked request again is exactly\n // what the user needs to be able to do.\n useEffect(() => {\n if (!pending || pendingResumeGraceMs === undefined) return;\n const timer = setTimeout(() => {\n if (!sawResumeRunRef.current) setPending(false);\n }, pendingResumeGraceMs);\n return () => clearTimeout(timer);\n }, [pending, requestId, pendingResumeGraceMs]);\n\n const sendResumeRef = useRef(sendResume);\n sendResumeRef.current = sendResume;\n const pendingRef = useRef(pending);\n pendingRef.current = pending;\n\n const resume = useCallback(\n (value: TValue) => {\n if (requestId === undefined || pendingRef.current) return;\n setPending(true);\n pendingRef.current = true;\n sendResumeRef.current(requestId, value);\n },\n [requestId],\n );\n\n return { pending, resume };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"interrupts.js","sources":["../../../../../agent-runtime/src/interrupts.ts"],"sourcesContent":["/**\n * Human-in-the-loop interrupt primitives shared by every statewire chat\n * surface (Olympus, the React SDK, the Office add-in, the Chrome extension,\n * mobile).\n *\n * The agora deep-agent runtime parks a gated run on an `interrupt` input\n * request whose payload carries the Athena `hitl_approval` shape\n * (`athena_deep_agent/hitl_bridge.py`). A surface answers it with\n * `{ type: 'run/input', requestId, response: { type: 'resume', value } }` —\n * the exact command the backend's resume path consumes. This module holds the\n * payload contract (narrowing, resume values) and the shared resume-lock state\n * machine; rendering stays with each surface.\n *\n * Deliberately parameterized rather than self-wiring: these helpers never call\n * `useStatewireRuns`/`useAuiState` themselves. Each surface reads its own\n * statewire hooks (which its tests already mock) and passes the values in, so\n * the shared semantics cannot drift while per-surface wiring stays testable.\n */\n\nimport type { StatewireThread } from '@assistant-ui/react-statewire';\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { hasClientToolInterruptSource } from './client-tools';\nimport { readDeepAgentStreamValues } from './statewire-state';\n\n/** Human-in-the-loop approval payload the agora deep-agent runtime parks a run\n * on (shaped by `athena_deep_agent/hitl_bridge.py`). */\nexport interface HitlApprovalPayload {\n source: string;\n type: string;\n message: string;\n /** Risk headline/facts/warnings, when the host includes them. */\n approval_context?: unknown;\n context?: {\n tool_name?: string;\n tool_id?: string;\n tool_call_id?: string;\n tool_args?: Record<string, unknown>;\n pending_action_count?: number;\n approval_context?: unknown;\n /**\n * Every pending gated call (name + args), in decision order. Absent on\n * older backends — fall back to the single `tool_name`/`tool_args` pair.\n * Per-dispatch payloads also carry each lane's `tool_call_id` (the lane\n * card key) and `interrupt_id` (the lane resume key).\n */\n action_requests?: {\n name?: string;\n args?: Record<string, unknown>;\n tool_call_id?: string;\n interrupt_id?: string;\n }[];\n /**\n * True when each dispatch parked on its own interrupt: decisions submit\n * per lane via `lane_decisions` and each approved lane starts immediately,\n * instead of the legacy batch where one resume must answer every lane.\n */\n per_dispatch?: boolean;\n };\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\n/** A pending `interrupt` input request; a present `response` key marks it answered. */\nexport function isPendingInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n return request.type === 'interrupt' && !('response' in request);\n}\n\n/**\n * Interrupt sources that were system pauses before the host stamped\n * `requires_user_action: false` on them (a background deep-research wait). Kept\n * only for the deploy skew between an older agora and a newer client; delete\n * once every deployment's raiser stamps the flag.\n */\nconst LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES: ReadonlySet<string> = new Set(['deep_research_pending']);\n\n/**\n * A park nobody answers: the agent is still working (a background research\n * wait) and the backend resumes it itself. The raiser stamps\n * `requires_user_action: false`; older raisers are recognised by source.\n */\nexport function isSystemPauseInterrupt(payload: unknown): boolean {\n if (!isRecord(payload)) return false;\n if (payload.requires_user_action === false) return true;\n return (\n typeof payload.source === 'string' && LEGACY_SYSTEM_PAUSE_INTERRUPT_SOURCES.has(payload.source)\n );\n}\n\n/**\n * A pending interrupt an approval card may answer. Two kinds are excluded —\n * client-tool parks (the client-tool bridge owns those, and a generic resume\n * would corrupt the parked tool call) and system pauses (nothing to answer; the\n * backend resumes them). The source checks are deliberately loose so a skewed\n * deploy can never route one here.\n */\nexport function isApprovalCardInterrupt(\n request: StatewireThread.InputRequestState,\n): request is StatewireThread.CustomInputRequest {\n if (!isPendingInterrupt(request)) return false;\n const payload = (request as { payload?: unknown }).payload;\n return !hasClientToolInterruptSource(payload) && !isSystemPauseInterrupt(payload);\n}\n\n/** Narrow an interrupt payload to the Athena HITL approval shape. */\nexport function asHitlApproval(value: unknown): HitlApprovalPayload | null {\n if (!isRecord(value)) return null;\n return value.type === 'hitl' &&\n value.source === 'hitl_approval' &&\n typeof value.message === 'string'\n ? (value as unknown as HitlApprovalPayload)\n : null;\n}\n\n/** Default paused-run copy when an interrupt payload carries no message. */\nexport const DEFAULT_INTERRUPT_PAUSED_MESSAGE =\n 'The agent is paused and waiting for input.';\n\n/** Best-effort human-readable message from any interrupt payload. */\nexport function readInterruptMessage(\n value: unknown,\n fallback: string = DEFAULT_INTERRUPT_PAUSED_MESSAGE,\n): string {\n if (isRecord(value) && typeof value.message === 'string' && value.message.length > 0) {\n return value.message;\n }\n return fallback;\n}\n\nexport interface PendingActionRequest {\n name: string;\n args: Record<string, unknown>;\n /** The lane's `task` tool-call id (present on per-dispatch payloads). */\n toolCallId?: string;\n /** The lane's langgraph interrupt id — the `lane_decisions` resume key. */\n interruptId?: string;\n}\n\n/**\n * Every gated call this approval covers, in decision order. One resume answers\n * ALL of them, so every entry must stay reviewable somewhere. Falls back to\n * the single first-call fields on older backends without `action_requests`.\n */\nexport function pendingActionRequests(hitl: HitlApprovalPayload | null): PendingActionRequest[] {\n const context = hitl?.context;\n if (!context) return [];\n // The payload crosses the wire with only its top-level shape narrowed\n // (`asHitlApproval`), so the nested collection must be validated before any\n // array/property access: a malformed host value must degrade to \"no\n // reviewable entries\", never crash the approval surface.\n const requests: Record<string, unknown>[] = Array.isArray(context.action_requests)\n ? context.action_requests.filter(isRecord)\n : context.tool_name && context.tool_args\n ? [{ name: context.tool_name, args: context.tool_args }]\n : [];\n return requests.map((request) => ({\n name: typeof request.name === 'string' ? request.name : 'tool',\n args: isRecord(request.args) ? request.args : {},\n ...(typeof request.tool_call_id === 'string' && { toolCallId: request.tool_call_id }),\n ...(typeof request.interrupt_id === 'string' && { interruptId: request.interrupt_id }),\n }));\n}\n\n/** An interrupt a non-resume dispatch dropped, published by the host. */\nexport interface AbandonedInputRequest {\n id: string;\n type: string;\n}\n\n/**\n * `useStatewireState` selector body for the host-published abandoned input\n * requests. Reads through {@link readDeepAgentStreamValues} so the selector is\n * safe on the pre-attach (`undefined`) wire state, and returns the raw array\n * reference so the subscription stays shallow-stable — derive (`.find`) on the\n * result, never inside the selector.\n */\nexport function readAbandonedInputRequests(\n state: unknown,\n): readonly AbandonedInputRequest[] | undefined {\n const abandoned = readDeepAgentStreamValues(state).abandonedInputRequests;\n // Wire value: anything non-array (including a malformed host publication)\n // reads as \"none\" rather than crashing the consuming surface. Identity-\n // preserving for the valid case, so it stays selector-safe.\n return Array.isArray(abandoned)\n ? (abandoned as readonly AbandonedInputRequest[])\n : undefined;\n}\n\n/** The abandoned `interrupt` entry, if any — the \"approval was cancelled\" notice. */\nexport function findAbandonedInterrupt(\n abandoned: readonly AbandonedInputRequest[] | undefined,\n): AbandonedInputRequest | undefined {\n return abandoned?.find(\n (request) => isRecord(request) && request.type === 'interrupt' && typeof request.id === 'string',\n );\n}\n\n/** Canonical reason string carried by a user rejection. */\nexport const HITL_REJECT_REASON = 'User rejected the action.';\n\n/**\n * Default {@link UseApprovalResumeLockOptions.pendingResumeGraceMs} for every\n * approval surface: how long a clicked decision waits for its resume run to\n * start before the actions unlock again. Covers a `run/input` the host\n * rejected or a disconnected transport swallowed before any run began — the\n * one failure neither identity-change nor observed-run recovery can detect.\n */\nexport const HITL_PENDING_RESUME_GRACE_MS = 15_000;\n\n/** `run/input` resume value approving the gated call(s). */\nexport function hitlResumeApprove(): Record<string, string> {\n return { action: 'approve' };\n}\n\n/** `run/input` resume value rejecting the gated call(s). */\nexport function hitlResumeReject(reason: string = HITL_REJECT_REASON): Record<string, string> {\n return { action: 'reject', reason };\n}\n\n/** Canonical reason string carried by a user declining a non-HITL park. */\nexport const INTERRUPT_DECLINE_REASON = 'User declined the request.';\n\n/**\n * `run/input` resume value declining a non-HITL park. `{ action: 'reject' }`\n * is the one value every typed raiser treats as a clean user decline\n * (`FormCancelledError` / `ToolRejectedError`); a generic `continue` would be\n * rejected by their resume schemas and surface as a tool error the agent then\n * improvises around.\n */\nexport function interruptResumeDecline(\n reason: string = INTERRUPT_DECLINE_REASON,\n): Record<string, string> {\n return { action: 'reject', reason };\n}\n\nexport interface UseApprovalResumeLockOptions<TValue> {\n /** The pending interrupt's request id (`undefined` when nothing is parked). */\n requestId: string | undefined;\n /** The surface's thread `isRunning` read (mockable per surface). */\n isRunning: boolean;\n /**\n * Dispatch the `run/input` resume for `requestId` on the surface's transport.\n * Return the dispatch promise so a refused or lost command releases the lock\n * at once and reaches `onResumeRejected`; a `void` return leaves the\n * recovery paths below as the only release.\n */\n sendResume: (requestId: string, value: TValue) => void | Promise<unknown>;\n /**\n * The dispatch `sendResume` returned rejected — the host refused the\n * `run/input` or the transport lost it. The lock is already released when\n * this fires; the surface tells the user, since the request is back on\n * screen with nothing else saying why.\n */\n onResumeRejected?: (error: unknown, requestId: string) => void;\n /**\n * Re-enable the actions when no resume run started within this window —\n * covers a `run/input` that was rejected or lost before any run began, so\n * neither reset path below can fire. Omit to keep the pre-existing surface\n * behavior of staying locked until a resume run is observed.\n */\n pendingResumeGraceMs?: number;\n}\n\nexport interface ApprovalResumeLock<TValue> {\n /** True after a resume until the request identity changes (or recovery fires). */\n pending: boolean;\n /**\n * Send the resume value, locking the actions. No-op (returns `undefined`)\n * while pending or unparked. Returns the dispatch promise when `sendResume`\n * produced one, so a surface can hold its own state until the host settles.\n */\n resume: (value: TValue) => Promise<unknown> | undefined;\n}\n\n/**\n * Optimistic action lock shared by every approval surface: disable the\n * decision buttons the moment one is clicked, until the request identity\n * changes (it clears after the resume, or a fresh interrupt arrives). This\n * closes the double-click → duplicate-resume window.\n *\n * Recovery paths, in order of specificity:\n * 0. The dispatch itself rejected (a `sendResume` that returned a promise) —\n * released at once, and reported through `onResumeRejected`.\n * 1. Identity change — the resume landed (or a new interrupt replaced the\n * request). Reset during render via React's \"previous value\" pattern.\n * 2. A resume run was observed starting and then settling with the request\n * still pending (failed/cancelled resume) — clear the lock so the user can\n * answer again.\n * 3. Optional grace window ({@link UseApprovalResumeLockOptions.pendingResumeGraceMs})\n * for a `run/input` that never started a run at all.\n */\nexport function useApprovalResumeLock<TValue = Record<string, string>>(\n options: UseApprovalResumeLockOptions<TValue>,\n): ApprovalResumeLock<TValue> {\n const { requestId, isRunning, sendResume, pendingResumeGraceMs, onResumeRejected } = options;\n\n const [pending, setPending] = useState(false);\n const prevRequestIdRef = useRef(requestId);\n if (prevRequestIdRef.current !== requestId) {\n prevRequestIdRef.current = requestId;\n if (pending) setPending(false);\n }\n\n // Track the resume run we kicked off (isRunning observed true→false while\n // pending) and clear `pending` when it ends with the request unresolved.\n const sawResumeRunRef = useRef(false);\n useEffect(() => {\n if (!pending) {\n sawResumeRunRef.current = false;\n return;\n }\n if (isRunning) {\n sawResumeRunRef.current = true;\n return;\n }\n if (sawResumeRunRef.current) {\n sawResumeRunRef.current = false;\n setPending(false);\n }\n }, [pending, isRunning]);\n\n // Pre-start failure recovery: re-enable after the grace window when no\n // resume run began; answering the still-parked request again is exactly\n // what the user needs to be able to do.\n useEffect(() => {\n if (!pending || pendingResumeGraceMs === undefined) return;\n const timer = setTimeout(() => {\n if (!sawResumeRunRef.current) setPending(false);\n }, pendingResumeGraceMs);\n return () => clearTimeout(timer);\n }, [pending, requestId, pendingResumeGraceMs]);\n\n const sendResumeRef = useRef(sendResume);\n sendResumeRef.current = sendResume;\n const onResumeRejectedRef = useRef(onResumeRejected);\n onResumeRejectedRef.current = onResumeRejected;\n const currentRequestIdRef = useRef(requestId);\n currentRequestIdRef.current = requestId;\n const pendingRef = useRef(pending);\n pendingRef.current = pending;\n\n const resume = useCallback(\n (value: TValue): Promise<unknown> | undefined => {\n if (requestId === undefined || pendingRef.current) return undefined;\n setPending(true);\n pendingRef.current = true;\n const dispatched: unknown = sendResumeRef.current(requestId, value);\n if (!(dispatched instanceof Promise)) return undefined;\n dispatched.then(undefined, (error: unknown) => {\n // A late refusal for a request the lock has since moved past (the\n // park was re-keyed, or a newer answer is in flight) must neither\n // release that answer's lock nor be reported as a failure of it.\n const current = currentRequestIdRef.current;\n if (current !== undefined && current !== requestId) return;\n setPending(false);\n pendingRef.current = false;\n onResumeRejectedRef.current?.(error, requestId);\n });\n return dispatched;\n },\n [requestId],\n );\n\n return { pending, resume };\n}\n"],"names":[],"mappings":"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"}
@@ -155,6 +155,23 @@ function liftTerminationIntoMetadata(message) {
155
155
  }
156
156
  };
157
157
  }
158
+ const ATHENA_ERRORS_KEY = "errors";
159
+ function liftErrorsIntoMetadata(message) {
160
+ if (message.type !== "ai") return message;
161
+ const kwargs = message.additional_kwargs;
162
+ const errors = kwargs == null ? void 0 : kwargs[ATHENA_ERRORS_KEY];
163
+ if (!Array.isArray(errors) || errors.length === 0) return message;
164
+ const existingMetadata = kwargs == null ? void 0 : kwargs.metadata;
165
+ const metadata = typeof existingMetadata === "object" && existingMetadata !== null ? existingMetadata : {};
166
+ if (Array.isArray(metadata[ATHENA_ERRORS_KEY])) return message;
167
+ return {
168
+ ...message,
169
+ additional_kwargs: {
170
+ ...kwargs,
171
+ metadata: { ...metadata, [ATHENA_ERRORS_KEY]: errors }
172
+ }
173
+ };
174
+ }
158
175
  function liftModelIntoMetadata(message) {
159
176
  if (message.type !== "ai") return message;
160
177
  const responseMetadata = message.response_metadata;
@@ -360,7 +377,9 @@ function liftMessageIntoMetadataChannels(message) {
360
377
  liftCompactionBoundaryIntoMetadata(
361
378
  liftConversationStatsIntoMetadata(
362
379
  liftModelIntoMetadata(
363
- liftTerminationIntoMetadata(liftRunMarkerIntoMetadata(message))
380
+ liftErrorsIntoMetadata(
381
+ liftTerminationIntoMetadata(liftRunMarkerIntoMetadata(message))
382
+ )
364
383
  )
365
384
  )
366
385
  )
@@ -382,21 +401,31 @@ function sanitizeLangChainMessages(messages, { toolOutputs } = {}) {
382
401
  function isLangGraphState(value) {
383
402
  return typeof value === "object" && value !== null && "messages" in value && Array.isArray(value.messages);
384
403
  }
385
- function convertDeepAgentState(state, { laneProjection = "both", selfAuthor = null, toolOutputs } = {}) {
404
+ function convertDeepAgentState(state, {
405
+ laneProjection = "both",
406
+ selfAuthor = null,
407
+ toolOutputs,
408
+ withdrawnTurns
409
+ } = {}) {
386
410
  var _a, _b;
387
411
  const values = statewireState.readDeepAgentStreamValues(state);
388
412
  const status = (_b = (_a = values.runs) == null ? void 0 : _a[0]) == null ? void 0 : _b.status;
389
413
  const isRunning = status === "running";
390
- const laneTail = status === "stopped" || status === "error" ? [] : laneTailMessages(values, laneProjection, selfAuthor);
391
- const stateMessages = isLangGraphState(values) ? sanitizeLangChainMessages(values.messages, { toolOutputs }) : [];
414
+ const isWithdrawn = (message) => withdrawnTurns !== void 0 && message.id !== void 0 && withdrawnTurns.isWithdrawn(message.id);
415
+ const laneTail = (status === "stopped" || status === "error" ? [] : laneTailMessages(values, laneProjection, selfAuthor)).filter((message) => !isWithdrawn(message));
416
+ const sanitized = isLangGraphState(values) ? sanitizeLangChainMessages(values.messages, { toolOutputs }) : [];
417
+ const tail = sanitized[sanitized.length - 1];
418
+ const tailWithdrawn = tail !== void 0 && isWithdrawn(tail);
419
+ const stateMessages = sanitized.filter((message) => !isWithdrawn(message));
392
420
  return {
393
421
  messages: messageConverter.toThreadMessages(
394
- [...withPendingAssistantMessage(stateMessages, isRunning), ...laneTail],
422
+ [...withPendingAssistantMessage(stateMessages, isRunning && !tailWithdrawn), ...laneTail],
395
423
  isRunning
396
424
  ),
397
425
  isRunning
398
426
  };
399
427
  }
428
+ exports.ATHENA_ERRORS_KEY = ATHENA_ERRORS_KEY;
400
429
  exports.ATHENA_HANDOFF_KEY = ATHENA_HANDOFF_KEY;
401
430
  exports.ATHENA_QUEUED_LANE_KEY = ATHENA_QUEUED_LANE_KEY;
402
431
  exports.ATHENA_RUN_MARKER_CANCELLED = ATHENA_RUN_MARKER_CANCELLED;
@@ -419,6 +448,7 @@ exports.liftAopSourceIntoMetadata = liftAopSourceIntoMetadata;
419
448
  exports.liftBranchIntoMetadata = liftBranchIntoMetadata;
420
449
  exports.liftCompactionBoundaryIntoMetadata = liftCompactionBoundaryIntoMetadata;
421
450
  exports.liftConversationStatsIntoMetadata = liftConversationStatsIntoMetadata;
451
+ exports.liftErrorsIntoMetadata = liftErrorsIntoMetadata;
422
452
  exports.liftHandoffSummaryIntoMetadata = liftHandoffSummaryIntoMetadata;
423
453
  exports.liftModelIntoMetadata = liftModelIntoMetadata;
424
454
  exports.liftRunMarkerIntoMetadata = liftRunMarkerIntoMetadata;
@@ -1 +1 @@
1
- {"version":3,"file":"messages.cjs","sources":["../../../../../agent-runtime/src/messages.ts"],"sourcesContent":["import { getExternalStoreMessages } from '@assistant-ui/core';\nimport { createMessageConverter } from '@assistant-ui/core/react';\nimport { convertLangChainMessages, type LangChainMessage } from '@assistant-ui/react-langgraph';\nimport { runsMaterialize, type StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n ATHENA_MESSAGE_AUTHOR_KEY,\n type DeepAgentMessageAuthor,\n laneEntryAuthor,\n} from './message-author';\nimport { DEEP_AGENT_TRUNCATED_CONTENT_KEY } from './generated/runconfig-contract';\nimport {\n ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY,\n readTruncatedToolContent,\n} from './message-window';\nimport { buildOptimisticHumanContent, type OptimisticContentPart } from './optimistic-content';\nimport { readDeepAgentStreamValues } from './statewire-state';\nimport type { DeepAgentToolOutputStore } from './tool-outputs';\nimport type {\n DeepAgentLaneProjection,\n DeepAgentStreamValues,\n LangGraphState,\n} from './types';\n\n/**\n * Native message converter for the deep-agent runtime.\n *\n * This is the entire \"conversion\" layer: assistant-ui's shipped\n * `convertLangChainMessages` (from `@assistant-ui/react-langgraph`) wired\n * through `createMessageConverter`. No bespoke per-message mapping — the agora\n * `athena_deep_agent` graph streams standard LangChain messages via\n * `append_langgraph_event`, and the shipped converter renders them.\n */\nexport const messageConverter = createMessageConverter(convertLangChainMessages);\n\nconst wirePartsToOptimisticParts = (\n parts: StatewireThread.UserMessage['parts']\n): OptimisticContentPart[] =>\n parts.flatMap((part): OptimisticContentPart[] => {\n if (part.type === 'text') return [part];\n // Image attachments travel as `file` parts on the run/* commands; map\n // them back to `image` parts so the optimistic bubble renders them.\n if (part.mediaType.startsWith('image/')) return [{ type: 'image', image: part.url }];\n return [];\n });\n\n/**\n * `metadata.custom` flag marking a bubble that is a projection of a run-lane\n * entry — a message the server holds and has NOT dispatched yet.\n *\n * The lane is server-owned and replicated to every client on the thread, so\n * an item queued on one surface projects onto all of them. Surfaces without a\n * queue panel must therefore be able to tell a queued projection apart from a\n * submitted turn, or a message another device queued renders as if this device\n * had already sent it.\n *\n * `convertLangChainMessages` exposes `additional_kwargs.metadata` (and nothing\n * else) as the thread message's `metadata.custom`.\n */\nexport const ATHENA_QUEUED_LANE_KEY = 'athena_queued_lane';\n\n/**\n * Whether a thread message's `metadata.custom` marks it as an undispatched\n * lane projection. Returns a primitive, so it is safe to use directly as a\n * `useAuiState` selector.\n */\nexport function isQueuedLaneMessage(custom: Record<string, unknown> | undefined): boolean {\n return custom?.[ATHENA_QUEUED_LANE_KEY] === true;\n}\n\n/**\n * Project one run-lane entry into the optimistic human message shape.\n *\n * `queued` marks entries still waiting in a lane; a dispatched entry becomes an\n * ordinary submitted turn and carries no marker. Either way the bubble carries\n * its author (see {@link laneEntryAuthor}) under the same `metadata.custom`\n * key the backend stamps on the persisted turn, so the optimistic and\n * authoritative copies of one send render identically — and a second poster's\n * avatar shows on their own send without waiting for the acknowledgment.\n */\nconst laneEntryMessage = ({\n entry,\n queued,\n selfAuthor,\n}: {\n entry: StatewireThread.RunsQueueEntry;\n queued: boolean;\n selfAuthor: DeepAgentMessageAuthor | null;\n}): LangChainMessage => {\n const author = laneEntryAuthor(entry, selfAuthor);\n const metadata: Record<string, unknown> = {\n ...(queued ? { [ATHENA_QUEUED_LANE_KEY]: true } : {}),\n ...(author ? { [ATHENA_MESSAGE_AUTHOR_KEY]: author } : {}),\n };\n return {\n id: entry.id,\n type: 'human',\n content: buildOptimisticHumanContent(wirePartsToOptimisticParts(entry.parts)),\n ...(Object.keys(metadata).length > 0 ? { additional_kwargs: { metadata } } : {}),\n };\n};\n\nexport interface DeepAgentMaterializeOptions {\n /** The viewer, stamped onto this client's own not-yet-acknowledged sends. */\n selfAuthor?: DeepAgentMessageAuthor | null;\n}\n\n/**\n * Materialize fold for the statewire path: makes the server's dispatch record\n * concrete in the LangGraph state itself, so direct state reads see dispatched\n * messages and the lib's post-converter dispatch merge stays off.\n */\nexport const createDeepAgentMaterialize =\n ({ selfAuthor = null }: DeepAgentMaterializeOptions = {}) =>\n (draft: DeepAgentStreamValues): void => {\n runsMaterialize(draft, {\n append: (message) => {\n (draft.messages ??= []).push(\n laneEntryMessage({ entry: message, queued: false, selfAuthor })\n );\n },\n rollback: (to) => {\n if (to === null) {\n draft.messages = [];\n return;\n }\n const messages = draft.messages ?? [];\n const keepIndex = messages.findIndex((message) => message.id === to);\n // Unresolved rollback anchor: fail OPEN with the full history (mirrors the edit fold).\n if (keepIndex !== -1) messages.splice(keepIndex + 1);\n },\n });\n };\n\n/** The materialize fold without a viewer identity (surfaces that pass none). */\nexport const deepAgentMaterialize = createDeepAgentMaterialize();\n\n/**\n * Project run lanes into human messages for the converter tail, so a queued or\n * steered item stays visible below the pending-assistant placeholder until its\n * dispatch. Surfaces with a queue panel project only the steer lane\n * (`'steer-only'`); surfaces without one project both lanes (`'both'`).\n *\n * Every projected entry carries {@link ATHENA_QUEUED_LANE_KEY} so a surface\n * without a queue panel can render it as queued rather than as a submitted\n * turn.\n */\nexport const laneTailMessages = (\n state: unknown,\n laneProjection: DeepAgentLaneProjection = 'both',\n selfAuthor: DeepAgentMessageAuthor | null = null\n): LangChainMessage[] => {\n const run = (state as DeepAgentStreamValues | null)?.runs?.[0];\n if (!run) return [];\n const entries =\n laneProjection === 'steer-only' ? run.steerQueue : [...run.queue, ...run.steerQueue];\n return entries.map((entry) => laneEntryMessage({ entry, queued: true, selfAuthor }));\n};\n\n/** Stable id for the derived running-assistant placeholder. */\nexport const PENDING_ASSISTANT_MESSAGE_ID = '__athena_deep_agent_pending_assistant__';\n\n/**\n * Append an empty assistant turn while a run is in flight and the visible\n * history does not already end in one, so the thread shows its \"Thinking…\"\n * status line from the moment a message is submitted.\n *\n * Mirrors `hasUpcomingMessage` in assistant-ui's legacy external-store runtime,\n * which synthesized this placeholder itself. `StatewireThread` renders the\n * converter's message list verbatim, so the placeholder has to come from here —\n * derived from the replicated run status on every render (never written into\n * transport state) so it cannot outlive the run, and carrying a stable id so\n * the streaming bubble's identity does not churn when the real assistant\n * message arrives.\n *\n * `tool` messages convert into assistant turns too, so a mid-run tool result\n * must not produce a second placeholder row.\n */\nexport function withPendingAssistantMessage(\n messages: LangChainMessage[],\n isRunning: boolean\n): LangChainMessage[] {\n if (!isRunning) return messages;\n const lastType = messages.at(-1)?.type;\n if (lastType === 'ai' || lastType === 'tool') return messages;\n // Empty-array content plus an explicit running status — the thinking shimmer\n // keys on `status running && zero content parts`, and the explicit status\n // keeps it pulsing when lane entries render below (the last-message auto\n // status would demote a mid-list placeholder to an empty complete bubble).\n // Never persisted, so it cannot outlive the run.\n return [\n ...messages,\n {\n type: 'ai',\n content: [],\n id: PENDING_ASSISTANT_MESSAGE_ID,\n status: { type: 'running' },\n } satisfies LangChainMessage,\n ];\n}\n\n/** Stable id for the synthetic assistant turn that carries a failure when the run produced none. */\nconst RUN_ERROR_SYNTHETIC_AI_ID = '__athena_deep_agent_run_error__';\n\n/**\n * Surface a run failure the base-correct way: stamp `status: { type:'incomplete',\n * reason:'error', error }` onto the assistant turn so `<Thread/>` renders it via\n * `ErrorPrimitive`. This is the core runtime's model (see `useLanggraphRuntime`\n * `onError`) — NOT a fabricated `⚠️ The run failed` assistant text message, and\n * NOT a magic-string contract shared with the agora backend.\n *\n * Stamps a trailing assistant message; if the visible history does not already\n * end with an assistant turn, appends a synthetic empty assistant turn with a\n * STABLE id so it never churns across renders. A `null` error is a no-op. The\n * shipped `convertLangChainMessages` converter passes `message.status` through.\n */\nexport function applyRunErrorStatus(\n messages: LangChainMessage[],\n errorMessage: string | null\n): LangChainMessage[] {\n if (errorMessage === null) return messages;\n const status = { type: 'incomplete', reason: 'error', error: errorMessage } as const;\n const lastMessage = messages.at(-1);\n if (lastMessage?.type !== 'ai') {\n return [\n ...messages,\n { type: 'ai', content: '', id: RUN_ERROR_SYNTHETIC_AI_ID, status } satisfies LangChainMessage,\n ];\n }\n return [...messages.slice(0, -1), { ...lastMessage, status }];\n}\n\n/**\n * `additional_kwargs` flag the backend stamps on run-lifecycle marker messages\n * (agora `athena_deep_agent/run_markers.py`) — e.g. the \"⏹️ Run stopped by\n * user.\" message persisted when a run is cancelled.\n */\nexport const ATHENA_RUN_MARKER_KEY = 'athena_run_marker';\n\n/** The {@link ATHENA_RUN_MARKER_KEY} value for a user-cancelled run. */\nexport const ATHENA_RUN_MARKER_CANCELLED = 'cancelled';\n\n/**\n * `additional_kwargs` key carrying the stop source behind a cancellation\n * marker (agora `stop_signals.py`: `user_stop`, `send_now`, `transport_cancel`,\n * `unattributed`).\n */\nexport const ATHENA_RUN_MARKER_SOURCE_KEY = 'athena_run_marker_source';\n\n/** The stop source for a \"Send now\" promotion pre-empting the live run. */\nexport const ATHENA_STOP_SOURCE_SEND_NOW = 'send_now';\n\n/**\n * The stop source for a spoken follow-up that redirected a voice-delegated\n * run through the steer lane: the user spoke, they never pressed Stop, so it\n * renders as a redirect rather than as \"Conversation stopped\".\n */\nexport const ATHENA_STOP_SOURCE_VOICE_STEER = 'voice_steer';\n\n/** The stop source for a pod-drain pause (server update), not a user stop. */\nexport const ATHENA_STOP_SOURCE_POD_DRAIN = 'pod_drain';\n\n/**\n * The {@link ATHENA_RUN_MARKER_SOURCE_KEY} value for a run whose hosting\n * instance closed under it (a server update that reached its eviction\n * deadline mid-step, or a placement lost to a lease lapse). Same family as a\n * pod drain: nobody stopped the run, the backend resumes it automatically.\n */\nexport const ATHENA_STOP_SOURCE_INSTANCE_CLOSED = 'instance_closed';\n\n/**\n * Whether a cancellation marker's source names the infrastructure — a pod\n * drain or a closed instance — rather than a person or a client action. These\n * endings read as a pause while the server resumes the run.\n */\nexport function isServerUpdateStopSource(source: string | undefined): boolean {\n return source === ATHENA_STOP_SOURCE_POD_DRAIN || source === ATHENA_STOP_SOURCE_INSTANCE_CLOSED;\n}\n\n/**\n * The {@link ATHENA_RUN_MARKER_KEY} value for a failed run (graph error,\n * including the `GraphRecursionError` \"tool call limit was reached\" case).\n * The marker message's text content carries the user-facing failure copy.\n */\nexport const ATHENA_RUN_MARKER_FAILED = 'failed';\n\n/**\n * `additional_kwargs` key carrying the canonical reason a run ended (agora\n * `athena_deep_agent/termination.py`): `user_stop`, `recursion_limit`,\n * `duplicate_call_guard`, `step_budget`, `ended_after_tools`, `model_refusal`,\n * `completed`, …\n * Stamped on run-lifecycle markers and on handoff messages.\n */\nexport const ATHENA_TERMINATION_REASON_KEY = 'athena_termination_reason';\n\n/**\n * `additional_kwargs` flag on the closing message the runtime's **handoff\n * turn** wrote (one tools-disabled model call made when a guard or the step\n * budget ends the run). The message text is the agent's closing summary; the\n * flag lets clients frame it as a runtime ending rather than a normal reply.\n */\nexport const ATHENA_HANDOFF_KEY = 'athena_handoff';\n\n/**\n * Read a run-lifecycle marker out of a thread message's **bound source\n * messages**, bypassing the converter's metadata join entirely.\n *\n * Why this exists: assistant-ui chunks consecutive assistant/tool messages\n * into ONE `ThreadMessage`, and the upstream join copies `metadata.custom`\n * only from the FIRST assistant message of the chunk\n * (`external-message-converter.ts`: the merge sits inside\n * `if (assistantMessage.content.length === 0)`). A failure marker appended\n * after the assistant's own turn is therefore never the first message in its\n * chunk, so `message.metadata.custom.athena_run_marker` is silently dropped\n * and the whole-message gate can't fire.\n *\n * `getExternalStoreMessages` returns the raw LangChain messages bound to the\n * joined thread message, so the marker is found wherever it sits in the chunk.\n * Returns the marker value plus its text content (the user-facing copy).\n */\nexport interface RunMarkerInfo {\n marker: string;\n /** The stop source stamped by the backend, when the marker carries one. */\n source: string | undefined;\n /** The marker message's text — the user-facing copy. */\n text: string | undefined;\n /**\n * True when the marker is the ONLY source message behind this thread message,\n * i.e. it owns the whole row (a cancelled run drops its partial turn, so its\n * marker stands alone).\n *\n * False when the chunk also holds the assistant's own work. Replacing the row\n * in that case would hide completed prose and tool cards — the marker must be\n * styled in place instead.\n */\n isSoleSourceMessage: boolean;\n}\n\nexport function readRunMarkerFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n content?: unknown;\n }\n): RunMarkerInfo | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (const source of sources) {\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') continue;\n const markerSource = kwargs?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n return {\n marker,\n source: typeof markerSource === 'string' ? markerSource : undefined,\n text: typeof source.content === 'string' ? source.content : undefined,\n isSoleSourceMessage: sources.length === 1,\n };\n }\n // Fallback for surfaces whose messages carry no bound source messages (or a\n // converter that already merged the flag): read the joined metadata. Without\n // sources to count, a single text part is the best available \"marker owns the\n // row\" signal.\n const joined = message.metadata?.custom?.[ATHENA_RUN_MARKER_KEY];\n if (typeof joined !== 'string') return undefined;\n const joinedSource = message.metadata?.custom?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n const parts = message.content;\n return {\n marker: joined,\n source: typeof joinedSource === 'string' ? joinedSource : undefined,\n text: undefined,\n isSoleSourceMessage: Array.isArray(parts) ? parts.length <= 1 : true,\n };\n}\n\n/**\n * The marker value when the marker is the SOLE source message behind a thread\n * message (it owns the whole row), else `undefined`.\n *\n * Returns a primitive so it is safe to use directly as a `useAuiState`\n * selector: `readRunMarkerFromSourceMessages` builds a fresh object on every\n * call, and selector snapshots are compared with `Object.is` — returning that\n * object from a selector makes `getSnapshot` unstable and loops the render.\n */\nexport function readSoleRunMarker(\n message: Parameters<typeof readRunMarkerFromSourceMessages>[0]\n): string | undefined {\n const found = readRunMarkerFromSourceMessages(message);\n return found?.isSoleSourceMessage ? found.marker : undefined;\n}\n\n/**\n * Whether a run marker renders nothing: a `send_now` promotion cancels the\n * live run as plumbing, not as a user-visible stop, so its cancellation\n * marker stays in the data for provenance but paints no divider.\n */\nexport function isSilentRunMarker(found: RunMarkerInfo): boolean {\n return (\n found.marker === ATHENA_RUN_MARKER_CANCELLED && found.source === ATHENA_STOP_SOURCE_SEND_NOW\n );\n}\n\n/**\n * Whether a cancellation marker records a server-update pause — a cooperative\n * pod drain, or the hosting instance closing under the run — rather than a\n * stop the user asked for. Rendered with distinct \"paused by a server update\"\n * copy instead of the \"Conversation stopped\" pill; the backend resumes the run.\n */\nexport function isDrainRunMarker(found: RunMarkerInfo): boolean {\n return found.marker === ATHENA_RUN_MARKER_CANCELLED && isServerUpdateStopSource(found.source);\n}\n\n/**\n * Surface the backend's top-level `additional_kwargs.athena_run_marker` flag\n * where the shipped converter can see it.\n *\n * `convertLangChainMessages` exposes ONLY `additional_kwargs.metadata` as the\n * thread message's `metadata.custom`; every other `additional_kwargs` key is\n * dropped. Marker messages persisted before the backend mirrored the flag into\n * `metadata` carry it top-level only, so this lift keeps those threads styling\n * correctly. Non-marker messages pass through with their identity unchanged,\n * and already-mirrored markers are left as-is (idempotent).\n */\nexport function liftRunMarkerIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata[ATHENA_RUN_MARKER_KEY] === marker) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_RUN_MARKER_KEY]: marker },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the termination reason and handoff flag into the converter's metadata\n * channel. The backend dual-writes both (top-level + `metadata`), so this lift\n * is the back-compat path for messages persisted before the mirror existed and\n * for any emitter that only stamps the top-level key. Idempotent; identity for\n * messages without the keys.\n */\nexport function liftTerminationIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const reason = kwargs?.[ATHENA_TERMINATION_REASON_KEY];\n const handoff = kwargs?.[ATHENA_HANDOFF_KEY];\n const hasReason = typeof reason === 'string';\n const hasHandoff = handoff === true;\n if (!hasReason && !hasHandoff) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n const reasonMirrored = !hasReason || metadata[ATHENA_TERMINATION_REASON_KEY] === reason;\n const handoffMirrored = !hasHandoff || metadata[ATHENA_HANDOFF_KEY] === true;\n if (reasonMirrored && handoffMirrored) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n ...(hasReason ? { [ATHENA_TERMINATION_REASON_KEY]: reason } : {}),\n ...(hasHandoff ? { [ATHENA_HANDOFF_KEY]: true } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the producing model into the converter's metadata channel. Wire AI\n * messages are full LangChain dumps whose `response_metadata` reports the model\n * that generated them (`model_name` / `model`), but the shipped converter maps\n * only `additional_kwargs.metadata` into `metadata.custom` — so without this\n * lift a surface can only show the CURRENT run config's model, mislabeling\n * earlier messages after a mid-thread model switch. A model already present in\n * the metadata channel (backend-stamped provenance) wins; no-ops are identity.\n */\nexport function liftModelIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const responseMetadata = (\n message as { response_metadata?: { model_name?: unknown; model?: unknown } }\n ).response_metadata;\n let model: string | undefined;\n if (typeof responseMetadata?.model_name === 'string' && responseMetadata.model_name) {\n model = responseMetadata.model_name;\n } else if (typeof responseMetadata?.model === 'string' && responseMetadata.model) {\n model = responseMetadata.model;\n }\n if (!model) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.model === 'string' && metadata.model) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, model },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror a per-message conversation-stats snapshot into the converter's\n * metadata channel. Backends stamp the snapshot at the legacy location —\n * top-level `additional_kwargs.conversation_stats` (the deep-agent stats\n * middleware and the legacy agent node both write it there) — but the shipped\n * converter maps only `additional_kwargs.metadata` into `metadata.custom`, so\n * without this lift the model badge can never show a message's OWN context and\n * falls back to thread-level live numbers. A snapshot already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftConversationStatsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const stats = kwargs?.conversation_stats;\n if (typeof stats !== 'object' || stats === null) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.conversation_stats === 'object' && metadata.conversation_stats !== null) {\n return message;\n }\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, conversation_stats: stats },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the compaction boundary flag into the converter's metadata channel.\n * A manual `/compact` persists its summary as a human message tagged with\n * top-level `additional_kwargs.compaction_start` (plus `_created_at` for the\n * divider timestamp) — the same location the legacy runtime used — but the\n * shipped converter maps only `additional_kwargs.metadata` into\n * `metadata.custom`, so without this lift the transcript's\n * \"Previous conversation summarized\" divider can never render. A flag already\n * present in the metadata channel wins; no-ops are identity.\n */\nexport function liftCompactionBoundaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.compaction_start !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.compaction_start === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n compaction_start: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the handoff summary flag into the converter's metadata channel.\n * Handoff summaries are persisted as human messages tagged with top-level\n * `additional_kwargs.handoff_summary` (plus `_created_at`) — the same location\n * the backend uses for message metadata — but the shipped converter maps only\n * `additional_kwargs.metadata` into `metadata.custom`, so without this lift\n * the summary would render as a normal user bubble. A flag already present in\n * the metadata channel wins; no-ops are identity.\n */\nexport function liftHandoffSummaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.handoff_summary !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.handoff_summary === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n handoff_summary: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror an AOP run's provenance into the converter's metadata channel. The\n * backend stamps the kicking-off user message with top-level\n * `additional_kwargs.aop_source` (agora `events/helpers.py`\n * `_build_aop_source_metadata`), which the legacy converter surfaced as\n * `metadata.custom.aop_source` — where the \"AOP · <title>\" badge reads it. The\n * shipped `convertLangChainMessages` maps only `additional_kwargs.metadata`\n * into `metadata.custom`, so without this lift an AOP run's conversation never\n * shows which AOP asset kicked it off. Provenance already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftAopSourceIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const aopSource = kwargs?.aop_source;\n if (typeof aopSource !== 'object' || aopSource === null || Array.isArray(aopSource)) {\n return message;\n }\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.aop_source === 'object' && metadata.aop_source !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, aop_source: aopSource },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\nexport function liftBranchIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human' && message.type !== 'ai') return message;\n const branch = (message as { branch?: unknown }).branch;\n if (typeof branch !== 'object' || branch === null || Array.isArray(branch)) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.branch === 'object' && metadata.branch !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, branch },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` keys on a ToolMessage that are infrastructure rather than\n * user-set metadata. The filter below additionally skips every `_`-prefixed key.\n */\nconst TOOL_MESSAGE_INTERNAL_KWARGS: ReadonlySet<string> = new Set([\n 'reasoning',\n 'tool_outputs',\n 'hide_from_frontend',\n]);\n\n/**\n * Mirror a ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` — the location `useToolCallMetadata` reads\n * persisted per-tool-call metadata from (`email_sent_at`, draft edits,\n * `calendar_event_created_at`, …).\n *\n * The `setMessageMetadata` mutation persists those keys onto the ToolMessage's\n * `additional_kwargs` in the checkpoint, and the legacy converter lifted them\n * into the artifact. The shipped `convertLangChainMessages` forwards only\n * `artifact` and drops `additional_kwargs` for tool messages, so without this\n * lift the metadata survives in the checkpoint but never reaches a tool card\n * after a reload. Kwargs values win over same-key artifact entries (the kwargs\n * are the durable write target); messages with no user-set kwargs pass through\n * by identity.\n */\nexport function liftToolMessageMetadataIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const kwargs = (message as { additional_kwargs?: Record<string, unknown> }).additional_kwargs;\n if (!kwargs) return message;\n const lifted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(kwargs)) {\n if (key.startsWith('_') || TOOL_MESSAGE_INTERNAL_KWARGS.has(key)) continue;\n lifted[key] = value;\n }\n if (Object.keys(lifted).length === 0) return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n const existing = artifactRecord._tool_message_metadata;\n const existingRecord =\n typeof existing === 'object' && existing !== null && !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n _tool_message_metadata: { ...existingRecord, ...lifted },\n },\n };\n}\n\n/**\n * Lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (`_athena_content_truncated: { chars, messageId }`).\n *\n * The host cuts long plain-text tool outputs on a windowed wire and stamps\n * `athena_content_truncated` on the wire message; the shipped converter joins\n * a ToolMessage into its assistant turn's tool-call part and keeps only\n * `artifact` and the result, so without this lift no tool card could tell the\n * output is cut nor which message to ask `history/tool_output` for. Messages\n * without the marker pass through by identity.\n */\nexport function liftTruncatedToolOutputIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null || typeof message.id !== 'string') return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n [ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY]: { chars: marker.chars, messageId: message.id },\n },\n };\n}\n\n/**\n * Per-source cache for {@link substituteToolOutput}: a substituted copy is\n * reused while the wire message and the fetched output are both unchanged, so\n * the lift cache downstream keeps its identity contract.\n */\nconst substitutedToolOutputCache = new WeakMap<\n LangChainMessage,\n { content: unknown; substituted: LangChainMessage }\n>();\n\n/**\n * A cut tool message with its full output put back, when the store holds one\n * fetched for this very output (`history/tool_output` answered for the same\n * `chars` the wire's marker reports); the marker leaves with the cut.\n * Messages that are whole, whose output has not been fetched, or whose fetched\n * output belongs to a length the host has since replaced, pass through by\n * identity — the loader sees the mismatch and fetches again.\n */\nexport function substituteToolOutput(\n message: LangChainMessage,\n toolOutputs: DeepAgentToolOutputStore\n): LangChainMessage {\n if (message.type !== 'tool' || typeof message.id !== 'string') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null) return message;\n const entry = toolOutputs.entry(message.id);\n if (entry === undefined || entry.chars !== marker.chars) return message;\n const { content } = entry;\n const cached = substitutedToolOutputCache.get(message);\n if (cached !== undefined && cached.content === content) return cached.substituted;\n const { [DEEP_AGENT_TRUNCATED_CONTENT_KEY]: _marker, ...rest } = message as Record<\n string,\n unknown\n >;\n const substituted = { ...rest, content } as unknown as LangChainMessage;\n substitutedToolOutputCache.set(message, { content, substituted });\n return substituted;\n}\n\nconst hasHiddenFrontendFlag = (value: unknown): boolean => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.hide_from_frontend === true) return true;\n return hasHiddenFrontendFlag(record.custom);\n};\n\n/**\n * Internal orchestration messages are useful in a trace export but must never\n * become conversation bubbles. Agora stores the flag in `additional_kwargs`;\n * live converter state can expose the same value under `metadata.custom`.\n */\nexport function isMessageHiddenFromFrontend(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const record = message as Record<string, unknown>;\n return (\n hasHiddenFrontendFlag(record.additional_kwargs) || hasHiddenFrontendFlag(record.metadata)\n );\n}\n\n/**\n * The message `type`s `convertLangChainMessages` actually maps: its\n * `switch (message.type)` covers exactly these four and has no `default`, so it\n * returns `undefined` for anything else (a `RemoveMessage` `type: \"remove\"`\n * from summarization/compaction, a partial chunk, a future type).\n */\nconst CONVERTIBLE_MESSAGE_TYPES: ReadonlySet<string> = new Set(['system', 'human', 'ai', 'tool']);\n\n/**\n * Whether `convertLangChainMessages` can map this message at all.\n *\n * An unmappable entry converts to `undefined`, which assistant-ui's\n * `convertExternalMessages` wraps as `outputs: [undefined]` and then reads\n * `output.role` off — throwing `Cannot read property 'role' of undefined` and\n * taking the entire thread down instead of dropping one message.\n */\nexport function isConvertibleChatMessage(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const type: unknown = (message as Record<string, unknown>).type;\n return typeof type === 'string' && CONVERTIBLE_MESSAGE_TYPES.has(type);\n}\n\n/**\n * Per-source cache for the lift pipeline in {@link sanitizeLangChainMessages}.\n *\n * The pipeline runs on EVERY state pass (once per streamed token), and the two\n * lifts that fire for essentially every message — {@link liftBranchIntoMetadata}\n * (the backend stamps `branch` on all indexed messages) and\n * {@link liftModelIntoMetadata} — allocate a fresh object each time they lift.\n * Without a cache, every message's identity churns on every token, defeating\n * all downstream per-source memoization (`getExternalStoreMessages`-keyed\n * reads, converter callback caches) and re-deriving the whole thread per token.\n *\n * Object identity is a sound cache key here: every statewire fold (op apply,\n * optimistic, materialize) runs through `statewire`'s copy-on-write\n * `recordMutations`, whose `finalize` returns the ORIGINAL object for\n * untouched nodes and a fresh copy for touched ones. A message is therefore\n * REPLACED — never mutated in place — whenever any of its fields change\n * (including the streaming tail's string-insert content growth), and an\n * unchanged message keeps its exact identity across passes. The corpus test\n * (`statewire-corpus.test.ts`) pins this identity contract against the real\n * client, so a statewire upgrade that switched to in-place mutation would fail\n * there rather than silently serving stale lifts from this cache.\n *\n * Module-level on purpose: keys are the wire message objects themselves, so\n * entries die with their state trees and threads cannot collide.\n */\nconst liftedMessageCache = new WeakMap<LangChainMessage, LangChainMessage>();\n\n/** The full lift chain for one message, memoized per source object. */\nfunction liftMessageIntoMetadataChannels(message: LangChainMessage): LangChainMessage {\n const cached = liftedMessageCache.get(message);\n if (cached !== undefined) return cached;\n const lifted = liftBranchIntoMetadata(\n liftTruncatedToolOutputIntoArtifact(\n liftToolMessageMetadataIntoArtifact(\n liftAopSourceIntoMetadata(\n liftHandoffSummaryIntoMetadata(\n liftCompactionBoundaryIntoMetadata(\n liftConversationStatsIntoMetadata(\n liftModelIntoMetadata(\n liftTerminationIntoMetadata(liftRunMarkerIntoMetadata(message))\n )\n )\n )\n )\n )\n )\n )\n );\n liftedMessageCache.set(message, lifted);\n return lifted;\n}\n\n/**\n * The one message pipeline every deep-agent surface renders through:\n *\n * 1. drop messages the shipped converter cannot map (`RemoveMessage` etc. —\n * an unmappable entry would crash the whole thread) and internal\n * orchestration messages flagged hidden-from-frontend;\n * 2. lift run-lifecycle markers into the converter's `metadata` channel\n * (see {@link liftRunMarkerIntoMetadata}) so the cancellation row styles as\n * a status divider instead of a plain assistant bubble, and the termination\n * reason + handoff flag alongside them (see\n * {@link liftTerminationIntoMetadata}) so every surface can render a\n * runtime ending (see `runtime-messages.ts`);\n * 3. lift each AI message's producing model into the same channel\n * (see {@link liftModelIntoMetadata}) so model badges reflect the model that\n * actually generated the message, not the currently configured one;\n * 4. lift each AI message's per-message conversation-stats snapshot into the\n * same channel (see {@link liftConversationStatsIntoMetadata}) so the model\n * badge's hover card shows that message's own context usage;\n * 5. lift the compaction boundary flag on `/compact` summary messages into the\n * same channel (see {@link liftCompactionBoundaryIntoMetadata}) so the\n * transcript renders its \"Previous conversation summarized\" divider;\n * 6. lift the handoff summary flag on carried-over context messages into the\n * same channel (see {@link liftHandoffSummaryIntoMetadata}) so the\n * transcript keeps the summary behind its context panel;\n * 7. lift an AOP run's provenance on its kicking-off user message into the\n * same channel (see {@link liftAopSourceIntoMetadata}) so the message\n * renders its \"AOP · <title>\" origin badge;\n * 8. lift each ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` (see\n * {@link liftToolMessageMetadataIntoArtifact}) so persisted per-tool-call\n * metadata reaches tool cards after a reload;\n * 9. lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (see {@link liftTruncatedToolOutputIntoArtifact}) so a\n * tool card can fetch the whole output — after first putting back any\n * output already fetched (see {@link substituteToolOutput});\n * 10. lift the host's per-message branch stamp into the same channel\n * (see {@link liftBranchIntoMetadata}) so the branch picker renders each\n * message's edit fan-out.\n *\n * Identity-stable per source: an unchanged source message maps to the\n * IDENTICAL lifted object across passes (see {@link liftedMessageCache}).\n */\nexport function sanitizeLangChainMessages(\n messages: readonly LangChainMessage[],\n { toolOutputs }: { toolOutputs?: DeepAgentToolOutputStore } = {}\n): LangChainMessage[] {\n return messages\n .filter((message) => isConvertibleChatMessage(message) && !isMessageHiddenFromFrontend(message))\n .map((message) =>\n liftMessageIntoMetadataChannels(\n toolOutputs ? substituteToolOutput(message, toolOutputs) : message\n )\n );\n}\n\n/** Type guard for the runtime state — an object carrying a `messages` array. */\nexport function isLangGraphState(value: unknown): value is LangGraphState {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'messages' in value &&\n Array.isArray((value as LangGraphState).messages)\n );\n}\n\nexport interface ConvertDeepAgentStateOptions {\n laneProjection?: DeepAgentLaneProjection;\n /**\n * The viewer, as the backend would stamp them: the author of this client's\n * own not-yet-acknowledged lane entries (see {@link laneEntryAuthor}).\n */\n selfAuthor?: DeepAgentMessageAuthor | null;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages\n * (see {@link substituteToolOutput}); omitted on surfaces that never\n * declare a window.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n}\n\n/**\n * Map server state → assistant-ui thread messages.\n *\n * `state.messages` is the authoritative LangChain history streamed by the\n * backend. The replicated run status (`runs[0].status`) drives the\n * pending-assistant placeholder and the running status. Lane entries render at\n * the tail, after the placeholder, while the run is live (the run is still\n * working the history above them); once the thread rests stopped/errored they\n * are omitted (surfaces with a queue panel list them there instead).\n */\nexport function convertDeepAgentState(\n state: unknown,\n { laneProjection = 'both', selfAuthor = null, toolOutputs }: ConvertDeepAgentStateOptions = {}\n) {\n // A wire without a snapshot yet (pre-attach, replay boundary) converts as the\n // typed pre-stream default: an empty resting thread, never `undefined`.\n const values = readDeepAgentStreamValues(state);\n const status = values.runs?.[0]?.status;\n const isRunning = status === 'running';\n const laneTail =\n status === 'stopped' || status === 'error'\n ? []\n : laneTailMessages(values, laneProjection, selfAuthor);\n const stateMessages = isLangGraphState(values)\n ? sanitizeLangChainMessages(values.messages, { toolOutputs })\n : [];\n return {\n messages: messageConverter.toThreadMessages(\n [...withPendingAssistantMessage(stateMessages, isRunning), ...laneTail],\n isRunning\n ),\n isRunning,\n };\n}\n"],"names":["createMessageConverter","convertLangChainMessages","laneEntryAuthor","ATHENA_MESSAGE_AUTHOR_KEY","buildOptimisticHumanContent","runsMaterialize","getExternalStoreMessages","readTruncatedToolContent","ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY","DEEP_AGENT_TRUNCATED_CONTENT_KEY","readDeepAgentStreamValues"],"mappings":"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"}
1
+ {"version":3,"file":"messages.cjs","sources":["../../../../../agent-runtime/src/messages.ts"],"sourcesContent":["import { getExternalStoreMessages } from '@assistant-ui/core';\nimport { createMessageConverter } from '@assistant-ui/core/react';\nimport { convertLangChainMessages, type LangChainMessage } from '@assistant-ui/react-langgraph';\nimport { runsMaterialize, type StatewireThread } from '@assistant-ui/react-statewire';\nimport {\n ATHENA_MESSAGE_AUTHOR_KEY,\n type DeepAgentMessageAuthor,\n laneEntryAuthor,\n} from './message-author';\nimport { DEEP_AGENT_TRUNCATED_CONTENT_KEY } from './generated/runconfig-contract';\nimport {\n ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY,\n readTruncatedToolContent,\n} from './message-window';\nimport { buildOptimisticHumanContent, type OptimisticContentPart } from './optimistic-content';\nimport { readDeepAgentStreamValues } from './statewire-state';\nimport type { DeepAgentToolOutputStore } from './tool-outputs';\nimport type { DeepAgentWithdrawnTurns } from './withdrawn-turns';\nimport type {\n DeepAgentLaneProjection,\n DeepAgentStreamValues,\n LangGraphState,\n} from './types';\n\n/**\n * Native message converter for the deep-agent runtime.\n *\n * This is the entire \"conversion\" layer: assistant-ui's shipped\n * `convertLangChainMessages` (from `@assistant-ui/react-langgraph`) wired\n * through `createMessageConverter`. No bespoke per-message mapping — the agora\n * `athena_deep_agent` graph streams standard LangChain messages via\n * `append_langgraph_event`, and the shipped converter renders them.\n */\nexport const messageConverter = createMessageConverter(convertLangChainMessages);\n\nconst wirePartsToOptimisticParts = (\n parts: StatewireThread.UserMessage['parts']\n): OptimisticContentPart[] =>\n parts.flatMap((part): OptimisticContentPart[] => {\n if (part.type === 'text') return [part];\n // Image attachments travel as `file` parts on the run/* commands; map\n // them back to `image` parts so the optimistic bubble renders them.\n if (part.mediaType.startsWith('image/')) return [{ type: 'image', image: part.url }];\n return [];\n });\n\n/**\n * `metadata.custom` flag marking a bubble that is a projection of a run-lane\n * entry — a message the server holds and has NOT dispatched yet.\n *\n * The lane is server-owned and replicated to every client on the thread, so\n * an item queued on one surface projects onto all of them. Surfaces without a\n * queue panel must therefore be able to tell a queued projection apart from a\n * submitted turn, or a message another device queued renders as if this device\n * had already sent it.\n *\n * `convertLangChainMessages` exposes `additional_kwargs.metadata` (and nothing\n * else) as the thread message's `metadata.custom`.\n */\nexport const ATHENA_QUEUED_LANE_KEY = 'athena_queued_lane';\n\n/**\n * Whether a thread message's `metadata.custom` marks it as an undispatched\n * lane projection. Returns a primitive, so it is safe to use directly as a\n * `useAuiState` selector.\n */\nexport function isQueuedLaneMessage(custom: Record<string, unknown> | undefined): boolean {\n return custom?.[ATHENA_QUEUED_LANE_KEY] === true;\n}\n\n/**\n * Project one run-lane entry into the optimistic human message shape.\n *\n * `queued` marks entries still waiting in a lane; a dispatched entry becomes an\n * ordinary submitted turn and carries no marker. Either way the bubble carries\n * its author (see {@link laneEntryAuthor}) under the same `metadata.custom`\n * key the backend stamps on the persisted turn, so the optimistic and\n * authoritative copies of one send render identically — and a second poster's\n * avatar shows on their own send without waiting for the acknowledgment.\n */\nconst laneEntryMessage = ({\n entry,\n queued,\n selfAuthor,\n}: {\n entry: StatewireThread.RunsQueueEntry;\n queued: boolean;\n selfAuthor: DeepAgentMessageAuthor | null;\n}): LangChainMessage => {\n const author = laneEntryAuthor(entry, selfAuthor);\n const metadata: Record<string, unknown> = {\n ...(queued ? { [ATHENA_QUEUED_LANE_KEY]: true } : {}),\n ...(author ? { [ATHENA_MESSAGE_AUTHOR_KEY]: author } : {}),\n };\n return {\n id: entry.id,\n type: 'human',\n content: buildOptimisticHumanContent(wirePartsToOptimisticParts(entry.parts)),\n ...(Object.keys(metadata).length > 0 ? { additional_kwargs: { metadata } } : {}),\n };\n};\n\nexport interface DeepAgentMaterializeOptions {\n /** The viewer, stamped onto this client's own not-yet-acknowledged sends. */\n selfAuthor?: DeepAgentMessageAuthor | null;\n}\n\n/**\n * Materialize fold for the statewire path: makes the server's dispatch record\n * concrete in the LangGraph state itself, so direct state reads see dispatched\n * messages and the lib's post-converter dispatch merge stays off.\n */\nexport const createDeepAgentMaterialize =\n ({ selfAuthor = null }: DeepAgentMaterializeOptions = {}) =>\n (draft: DeepAgentStreamValues): void => {\n runsMaterialize(draft, {\n append: (message) => {\n (draft.messages ??= []).push(\n laneEntryMessage({ entry: message, queued: false, selfAuthor })\n );\n },\n rollback: (to) => {\n if (to === null) {\n draft.messages = [];\n return;\n }\n const messages = draft.messages ?? [];\n const keepIndex = messages.findIndex((message) => message.id === to);\n // Unresolved rollback anchor: fail OPEN with the full history (mirrors the edit fold).\n if (keepIndex !== -1) messages.splice(keepIndex + 1);\n },\n });\n };\n\n/** The materialize fold without a viewer identity (surfaces that pass none). */\nexport const deepAgentMaterialize = createDeepAgentMaterialize();\n\n/**\n * Project run lanes into human messages for the converter tail, so a queued or\n * steered item stays visible below the pending-assistant placeholder until its\n * dispatch. Surfaces with a queue panel project only the steer lane\n * (`'steer-only'`); surfaces without one project both lanes (`'both'`).\n *\n * Every projected entry carries {@link ATHENA_QUEUED_LANE_KEY} so a surface\n * without a queue panel can render it as queued rather than as a submitted\n * turn.\n */\nexport const laneTailMessages = (\n state: unknown,\n laneProjection: DeepAgentLaneProjection = 'both',\n selfAuthor: DeepAgentMessageAuthor | null = null\n): LangChainMessage[] => {\n const run = (state as DeepAgentStreamValues | null)?.runs?.[0];\n if (!run) return [];\n const entries =\n laneProjection === 'steer-only' ? run.steerQueue : [...run.queue, ...run.steerQueue];\n return entries.map((entry) => laneEntryMessage({ entry, queued: true, selfAuthor }));\n};\n\n/** Stable id for the derived running-assistant placeholder. */\nexport const PENDING_ASSISTANT_MESSAGE_ID = '__athena_deep_agent_pending_assistant__';\n\n/**\n * Append an empty assistant turn while a run is in flight and the visible\n * history does not already end in one, so the thread shows its \"Thinking…\"\n * status line from the moment a message is submitted.\n *\n * Mirrors `hasUpcomingMessage` in assistant-ui's legacy external-store runtime,\n * which synthesized this placeholder itself. `StatewireThread` renders the\n * converter's message list verbatim, so the placeholder has to come from here —\n * derived from the replicated run status on every render (never written into\n * transport state) so it cannot outlive the run, and carrying a stable id so\n * the streaming bubble's identity does not churn when the real assistant\n * message arrives.\n *\n * `tool` messages convert into assistant turns too, so a mid-run tool result\n * must not produce a second placeholder row.\n */\nexport function withPendingAssistantMessage(\n messages: LangChainMessage[],\n isRunning: boolean\n): LangChainMessage[] {\n if (!isRunning) return messages;\n const lastType = messages.at(-1)?.type;\n if (lastType === 'ai' || lastType === 'tool') return messages;\n // Empty-array content plus an explicit running status — the thinking shimmer\n // keys on `status running && zero content parts`, and the explicit status\n // keeps it pulsing when lane entries render below (the last-message auto\n // status would demote a mid-list placeholder to an empty complete bubble).\n // Never persisted, so it cannot outlive the run.\n return [\n ...messages,\n {\n type: 'ai',\n content: [],\n id: PENDING_ASSISTANT_MESSAGE_ID,\n status: { type: 'running' },\n } satisfies LangChainMessage,\n ];\n}\n\n/** Stable id for the synthetic assistant turn that carries a failure when the run produced none. */\nconst RUN_ERROR_SYNTHETIC_AI_ID = '__athena_deep_agent_run_error__';\n\n/**\n * Surface a run failure the base-correct way: stamp `status: { type:'incomplete',\n * reason:'error', error }` onto the assistant turn so `<Thread/>` renders it via\n * `ErrorPrimitive`. This is the core runtime's model (see `useLanggraphRuntime`\n * `onError`) — NOT a fabricated `⚠️ The run failed` assistant text message, and\n * NOT a magic-string contract shared with the agora backend.\n *\n * Stamps a trailing assistant message; if the visible history does not already\n * end with an assistant turn, appends a synthetic empty assistant turn with a\n * STABLE id so it never churns across renders. A `null` error is a no-op. The\n * shipped `convertLangChainMessages` converter passes `message.status` through.\n */\nexport function applyRunErrorStatus(\n messages: LangChainMessage[],\n errorMessage: string | null\n): LangChainMessage[] {\n if (errorMessage === null) return messages;\n const status = { type: 'incomplete', reason: 'error', error: errorMessage } as const;\n const lastMessage = messages.at(-1);\n if (lastMessage?.type !== 'ai') {\n return [\n ...messages,\n { type: 'ai', content: '', id: RUN_ERROR_SYNTHETIC_AI_ID, status } satisfies LangChainMessage,\n ];\n }\n return [...messages.slice(0, -1), { ...lastMessage, status }];\n}\n\n/**\n * `additional_kwargs` flag the backend stamps on run-lifecycle marker messages\n * (agora `athena_deep_agent/run_markers.py`) — e.g. the \"⏹️ Run stopped by\n * user.\" message persisted when a run is cancelled.\n */\nexport const ATHENA_RUN_MARKER_KEY = 'athena_run_marker';\n\n/** The {@link ATHENA_RUN_MARKER_KEY} value for a user-cancelled run. */\nexport const ATHENA_RUN_MARKER_CANCELLED = 'cancelled';\n\n/**\n * `additional_kwargs` key carrying the stop source behind a cancellation\n * marker (agora `stop_signals.py`: `user_stop`, `send_now`, `transport_cancel`,\n * `unattributed`).\n */\nexport const ATHENA_RUN_MARKER_SOURCE_KEY = 'athena_run_marker_source';\n\n/** The stop source for a \"Send now\" promotion pre-empting the live run. */\nexport const ATHENA_STOP_SOURCE_SEND_NOW = 'send_now';\n\n/**\n * The stop source for a spoken follow-up that redirected a voice-delegated\n * run through the steer lane: the user spoke, they never pressed Stop, so it\n * renders as a redirect rather than as \"Conversation stopped\".\n */\nexport const ATHENA_STOP_SOURCE_VOICE_STEER = 'voice_steer';\n\n/** The stop source for a pod-drain pause (server update), not a user stop. */\nexport const ATHENA_STOP_SOURCE_POD_DRAIN = 'pod_drain';\n\n/**\n * The {@link ATHENA_RUN_MARKER_SOURCE_KEY} value for a run whose hosting\n * instance closed under it (a server update that reached its eviction\n * deadline mid-step, or a placement lost to a lease lapse). Same family as a\n * pod drain: nobody stopped the run, the backend resumes it automatically.\n */\nexport const ATHENA_STOP_SOURCE_INSTANCE_CLOSED = 'instance_closed';\n\n/**\n * Whether a cancellation marker's source names the infrastructure — a pod\n * drain or a closed instance — rather than a person or a client action. These\n * endings read as a pause while the server resumes the run.\n */\nexport function isServerUpdateStopSource(source: string | undefined): boolean {\n return source === ATHENA_STOP_SOURCE_POD_DRAIN || source === ATHENA_STOP_SOURCE_INSTANCE_CLOSED;\n}\n\n/**\n * The {@link ATHENA_RUN_MARKER_KEY} value for a failed run (graph error,\n * including the `GraphRecursionError` \"tool call limit was reached\" case).\n * The marker message's text content carries the user-facing failure copy.\n */\nexport const ATHENA_RUN_MARKER_FAILED = 'failed';\n\n/**\n * `additional_kwargs` key carrying the canonical reason a run ended (agora\n * `athena_deep_agent/termination.py`): `user_stop`, `recursion_limit`,\n * `duplicate_call_guard`, `step_budget`, `ended_after_tools`, `model_refusal`,\n * `completed`, …\n * Stamped on run-lifecycle markers and on handoff messages.\n */\nexport const ATHENA_TERMINATION_REASON_KEY = 'athena_termination_reason';\n\n/**\n * `additional_kwargs` flag on the closing message the runtime's **handoff\n * turn** wrote (one tools-disabled model call made when a guard or the step\n * budget ends the run). The message text is the agent's closing summary; the\n * flag lets clients frame it as a runtime ending rather than a normal reply.\n */\nexport const ATHENA_HANDOFF_KEY = 'athena_handoff';\n\n/**\n * Read a run-lifecycle marker out of a thread message's **bound source\n * messages**, bypassing the converter's metadata join entirely.\n *\n * Why this exists: assistant-ui chunks consecutive assistant/tool messages\n * into ONE `ThreadMessage`, and the upstream join copies `metadata.custom`\n * only from the FIRST assistant message of the chunk\n * (`external-message-converter.ts`: the merge sits inside\n * `if (assistantMessage.content.length === 0)`). A failure marker appended\n * after the assistant's own turn is therefore never the first message in its\n * chunk, so `message.metadata.custom.athena_run_marker` is silently dropped\n * and the whole-message gate can't fire.\n *\n * `getExternalStoreMessages` returns the raw LangChain messages bound to the\n * joined thread message, so the marker is found wherever it sits in the chunk.\n * Returns the marker value plus its text content (the user-facing copy).\n */\nexport interface RunMarkerInfo {\n marker: string;\n /** The stop source stamped by the backend, when the marker carries one. */\n source: string | undefined;\n /** The marker message's text — the user-facing copy. */\n text: string | undefined;\n /**\n * True when the marker is the ONLY source message behind this thread message,\n * i.e. it owns the whole row (a cancelled run drops its partial turn, so its\n * marker stands alone).\n *\n * False when the chunk also holds the assistant's own work. Replacing the row\n * in that case would hide completed prose and tool cards — the marker must be\n * styled in place instead.\n */\n isSoleSourceMessage: boolean;\n}\n\nexport function readRunMarkerFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n content?: unknown;\n }\n): RunMarkerInfo | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (const source of sources) {\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') continue;\n const markerSource = kwargs?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n return {\n marker,\n source: typeof markerSource === 'string' ? markerSource : undefined,\n text: typeof source.content === 'string' ? source.content : undefined,\n isSoleSourceMessage: sources.length === 1,\n };\n }\n // Fallback for surfaces whose messages carry no bound source messages (or a\n // converter that already merged the flag): read the joined metadata. Without\n // sources to count, a single text part is the best available \"marker owns the\n // row\" signal.\n const joined = message.metadata?.custom?.[ATHENA_RUN_MARKER_KEY];\n if (typeof joined !== 'string') return undefined;\n const joinedSource = message.metadata?.custom?.[ATHENA_RUN_MARKER_SOURCE_KEY];\n const parts = message.content;\n return {\n marker: joined,\n source: typeof joinedSource === 'string' ? joinedSource : undefined,\n text: undefined,\n isSoleSourceMessage: Array.isArray(parts) ? parts.length <= 1 : true,\n };\n}\n\n/**\n * The marker value when the marker is the SOLE source message behind a thread\n * message (it owns the whole row), else `undefined`.\n *\n * Returns a primitive so it is safe to use directly as a `useAuiState`\n * selector: `readRunMarkerFromSourceMessages` builds a fresh object on every\n * call, and selector snapshots are compared with `Object.is` — returning that\n * object from a selector makes `getSnapshot` unstable and loops the render.\n */\nexport function readSoleRunMarker(\n message: Parameters<typeof readRunMarkerFromSourceMessages>[0]\n): string | undefined {\n const found = readRunMarkerFromSourceMessages(message);\n return found?.isSoleSourceMessage ? found.marker : undefined;\n}\n\n/**\n * Whether a run marker renders nothing: a `send_now` promotion cancels the\n * live run as plumbing, not as a user-visible stop, so its cancellation\n * marker stays in the data for provenance but paints no divider.\n */\nexport function isSilentRunMarker(found: RunMarkerInfo): boolean {\n return (\n found.marker === ATHENA_RUN_MARKER_CANCELLED && found.source === ATHENA_STOP_SOURCE_SEND_NOW\n );\n}\n\n/**\n * Whether a cancellation marker records a server-update pause — a cooperative\n * pod drain, or the hosting instance closing under the run — rather than a\n * stop the user asked for. Rendered with distinct \"paused by a server update\"\n * copy instead of the \"Conversation stopped\" pill; the backend resumes the run.\n */\nexport function isDrainRunMarker(found: RunMarkerInfo): boolean {\n return found.marker === ATHENA_RUN_MARKER_CANCELLED && isServerUpdateStopSource(found.source);\n}\n\n/**\n * Surface the backend's top-level `additional_kwargs.athena_run_marker` flag\n * where the shipped converter can see it.\n *\n * `convertLangChainMessages` exposes ONLY `additional_kwargs.metadata` as the\n * thread message's `metadata.custom`; every other `additional_kwargs` key is\n * dropped. Marker messages persisted before the backend mirrored the flag into\n * `metadata` carry it top-level only, so this lift keeps those threads styling\n * correctly. Non-marker messages pass through with their identity unchanged,\n * and already-mirrored markers are left as-is (idempotent).\n */\nexport function liftRunMarkerIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const marker = kwargs?.[ATHENA_RUN_MARKER_KEY];\n if (typeof marker !== 'string') return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata[ATHENA_RUN_MARKER_KEY] === marker) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_RUN_MARKER_KEY]: marker },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the termination reason and handoff flag into the converter's metadata\n * channel. The backend dual-writes both (top-level + `metadata`), so this lift\n * is the back-compat path for messages persisted before the mirror existed and\n * for any emitter that only stamps the top-level key. Idempotent; identity for\n * messages without the keys.\n */\nexport function liftTerminationIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const reason = kwargs?.[ATHENA_TERMINATION_REASON_KEY];\n const handoff = kwargs?.[ATHENA_HANDOFF_KEY];\n const hasReason = typeof reason === 'string';\n const hasHandoff = handoff === true;\n if (!hasReason && !hasHandoff) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n const reasonMirrored = !hasReason || metadata[ATHENA_TERMINATION_REASON_KEY] === reason;\n const handoffMirrored = !hasHandoff || metadata[ATHENA_HANDOFF_KEY] === true;\n if (reasonMirrored && handoffMirrored) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n ...(hasReason ? { [ATHENA_TERMINATION_REASON_KEY]: reason } : {}),\n ...(hasHandoff ? { [ATHENA_HANDOFF_KEY]: true } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` key (and `metadata` mirror) carrying a failure marker's\n * structured error records (agora `athena_deep_agent/run_markers.py`\n * `failure_error_records`): `[{ error, provider?, message? }]`, where `error`\n * is the client code a surface branches on — `context_length_exceeded`,\n * `transient_api_error` (with `provider: 'azure_foundry' |\n * 'external_model_provider'`), `content_filter`, `request_too_large`, … — the\n * same shape the legacy agent node emitted.\n */\nexport const ATHENA_ERRORS_KEY = 'errors';\n\n/**\n * Mirror a failure marker's structured `errors` records into the converter's\n * metadata channel. The backend dual-writes them (top-level + `metadata`), so\n * this lift is the back-compat path for messages that carry only the top-level\n * key (an emitter that never mirrored, or a record persisted before the mirror\n * existed). Records already present in the metadata channel win; no-ops are\n * identity.\n */\nexport function liftErrorsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const errors = kwargs?.[ATHENA_ERRORS_KEY];\n if (!Array.isArray(errors) || errors.length === 0) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (Array.isArray(metadata[ATHENA_ERRORS_KEY])) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, [ATHENA_ERRORS_KEY]: errors },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Read a failure's structured `errors` records out of a thread message's\n * **bound source messages**, bypassing the converter's metadata join.\n *\n * Same reason as {@link readRunMarkerFromSourceMessages}: assistant-ui chunks\n * consecutive assistant/tool messages into ONE `ThreadMessage` and copies\n * `metadata.custom` only from the FIRST assistant message of the chunk. The\n * failure marker that carries the records is appended after the assistant's\n * own turn, so `message.metadata.custom.errors` is silently dropped whenever\n * the run produced anything before failing. The bound sources are walked from\n * the tail (the terminal failure wins); the joined metadata is the fallback\n * for surfaces whose messages carry no bound sources. Returns the raw records\n * (callers validate the shape) or `undefined`.\n */\nexport function readMessageErrorsFromSourceMessages(\n message: Parameters<typeof getExternalStoreMessages>[0] & {\n metadata?: { custom?: Record<string, unknown> | undefined } | undefined;\n }\n): unknown[] | undefined {\n const sources = getExternalStoreMessages<LangChainMessage>(message);\n for (let index = sources.length - 1; index >= 0; index -= 1) {\n const source = sources[index];\n if (source?.type !== 'ai') continue;\n const kwargs = source.additional_kwargs as Record<string, unknown> | undefined;\n const metadata = kwargs?.metadata;\n const mirrored =\n typeof metadata === 'object' && metadata !== null\n ? (metadata as Record<string, unknown>)[ATHENA_ERRORS_KEY]\n : undefined;\n const errors = Array.isArray(mirrored) ? mirrored : kwargs?.[ATHENA_ERRORS_KEY];\n if (Array.isArray(errors) && errors.length > 0) return errors;\n }\n const joined = message.metadata?.custom?.[ATHENA_ERRORS_KEY];\n return Array.isArray(joined) && joined.length > 0 ? joined : undefined;\n}\n\n/**\n * Mirror the producing model into the converter's metadata channel. Wire AI\n * messages are full LangChain dumps whose `response_metadata` reports the model\n * that generated them (`model_name` / `model`), but the shipped converter maps\n * only `additional_kwargs.metadata` into `metadata.custom` — so without this\n * lift a surface can only show the CURRENT run config's model, mislabeling\n * earlier messages after a mid-thread model switch. A model already present in\n * the metadata channel (backend-stamped provenance) wins; no-ops are identity.\n */\nexport function liftModelIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const responseMetadata = (\n message as { response_metadata?: { model_name?: unknown; model?: unknown } }\n ).response_metadata;\n let model: string | undefined;\n if (typeof responseMetadata?.model_name === 'string' && responseMetadata.model_name) {\n model = responseMetadata.model_name;\n } else if (typeof responseMetadata?.model === 'string' && responseMetadata.model) {\n model = responseMetadata.model;\n }\n if (!model) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.model === 'string' && metadata.model) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, model },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror a per-message conversation-stats snapshot into the converter's\n * metadata channel. Backends stamp the snapshot at the legacy location —\n * top-level `additional_kwargs.conversation_stats` (the deep-agent stats\n * middleware and the legacy agent node both write it there) — but the shipped\n * converter maps only `additional_kwargs.metadata` into `metadata.custom`, so\n * without this lift the model badge can never show a message's OWN context and\n * falls back to thread-level live numbers. A snapshot already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftConversationStatsIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'ai') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const stats = kwargs?.conversation_stats;\n if (typeof stats !== 'object' || stats === null) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.conversation_stats === 'object' && metadata.conversation_stats !== null) {\n return message;\n }\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, conversation_stats: stats },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the compaction boundary flag into the converter's metadata channel.\n * A manual `/compact` persists its summary as a human message tagged with\n * top-level `additional_kwargs.compaction_start` (plus `_created_at` for the\n * divider timestamp) — the same location the legacy runtime used — but the\n * shipped converter maps only `additional_kwargs.metadata` into\n * `metadata.custom`, so without this lift the transcript's\n * \"Previous conversation summarized\" divider can never render. A flag already\n * present in the metadata channel wins; no-ops are identity.\n */\nexport function liftCompactionBoundaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.compaction_start !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.compaction_start === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n compaction_start: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror the handoff summary flag into the converter's metadata channel.\n * Handoff summaries are persisted as human messages tagged with top-level\n * `additional_kwargs.handoff_summary` (plus `_created_at`) — the same location\n * the backend uses for message metadata — but the shipped converter maps only\n * `additional_kwargs.metadata` into `metadata.custom`, so without this lift\n * the summary would render as a normal user bubble. A flag already present in\n * the metadata channel wins; no-ops are identity.\n */\nexport function liftHandoffSummaryIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n if (kwargs?.handoff_summary !== true) return message;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (metadata.handoff_summary === true) return message;\n const createdAt = kwargs?._created_at;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: {\n ...metadata,\n handoff_summary: true,\n ...(typeof createdAt === 'string' ? { _created_at: createdAt } : {}),\n },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * Mirror an AOP run's provenance into the converter's metadata channel. The\n * backend stamps the kicking-off user message with top-level\n * `additional_kwargs.aop_source` (agora `events/helpers.py`\n * `_build_aop_source_metadata`), which the legacy converter surfaced as\n * `metadata.custom.aop_source` — where the \"AOP · <title>\" badge reads it. The\n * shipped `convertLangChainMessages` maps only `additional_kwargs.metadata`\n * into `metadata.custom`, so without this lift an AOP run's conversation never\n * shows which AOP asset kicked it off. Provenance already present in the\n * metadata channel wins; no-ops are identity.\n */\nexport function liftAopSourceIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human') return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const aopSource = kwargs?.aop_source;\n if (typeof aopSource !== 'object' || aopSource === null || Array.isArray(aopSource)) {\n return message;\n }\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.aop_source === 'object' && metadata.aop_source !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, aop_source: aopSource },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\nexport function liftBranchIntoMetadata(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'human' && message.type !== 'ai') return message;\n const branch = (message as { branch?: unknown }).branch;\n if (typeof branch !== 'object' || branch === null || Array.isArray(branch)) return message;\n const kwargs = message.additional_kwargs as Record<string, unknown> | undefined;\n const existingMetadata = kwargs?.metadata;\n const metadata =\n typeof existingMetadata === 'object' && existingMetadata !== null\n ? (existingMetadata as Record<string, unknown>)\n : {};\n if (typeof metadata.branch === 'object' && metadata.branch !== null) return message;\n return {\n ...message,\n additional_kwargs: {\n ...kwargs,\n metadata: { ...metadata, branch },\n } as (typeof message)['additional_kwargs'],\n };\n}\n\n/**\n * `additional_kwargs` keys on a ToolMessage that are infrastructure rather than\n * user-set metadata. The filter below additionally skips every `_`-prefixed key.\n */\nconst TOOL_MESSAGE_INTERNAL_KWARGS: ReadonlySet<string> = new Set([\n 'reasoning',\n 'tool_outputs',\n 'hide_from_frontend',\n]);\n\n/**\n * Mirror a ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` — the location `useToolCallMetadata` reads\n * persisted per-tool-call metadata from (`email_sent_at`, draft edits,\n * `calendar_event_created_at`, …).\n *\n * The `setMessageMetadata` mutation persists those keys onto the ToolMessage's\n * `additional_kwargs` in the checkpoint, and the legacy converter lifted them\n * into the artifact. The shipped `convertLangChainMessages` forwards only\n * `artifact` and drops `additional_kwargs` for tool messages, so without this\n * lift the metadata survives in the checkpoint but never reaches a tool card\n * after a reload. Kwargs values win over same-key artifact entries (the kwargs\n * are the durable write target); messages with no user-set kwargs pass through\n * by identity.\n */\nexport function liftToolMessageMetadataIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const kwargs = (message as { additional_kwargs?: Record<string, unknown> }).additional_kwargs;\n if (!kwargs) return message;\n const lifted: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(kwargs)) {\n if (key.startsWith('_') || TOOL_MESSAGE_INTERNAL_KWARGS.has(key)) continue;\n lifted[key] = value;\n }\n if (Object.keys(lifted).length === 0) return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n const existing = artifactRecord._tool_message_metadata;\n const existingRecord =\n typeof existing === 'object' && existing !== null && !Array.isArray(existing)\n ? (existing as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n _tool_message_metadata: { ...existingRecord, ...lifted },\n },\n };\n}\n\n/**\n * Lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (`_athena_content_truncated: { chars, messageId }`).\n *\n * The host cuts long plain-text tool outputs on a windowed wire and stamps\n * `athena_content_truncated` on the wire message; the shipped converter joins\n * a ToolMessage into its assistant turn's tool-call part and keeps only\n * `artifact` and the result, so without this lift no tool card could tell the\n * output is cut nor which message to ask `history/tool_output` for. Messages\n * without the marker pass through by identity.\n */\nexport function liftTruncatedToolOutputIntoArtifact(message: LangChainMessage): LangChainMessage {\n if (message.type !== 'tool') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null || typeof message.id !== 'string') return message;\n const artifact: unknown = message.artifact;\n const artifactRecord =\n typeof artifact === 'object' && artifact !== null && !Array.isArray(artifact)\n ? (artifact as Record<string, unknown>)\n : {};\n return {\n ...message,\n artifact: {\n ...artifactRecord,\n [ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY]: { chars: marker.chars, messageId: message.id },\n },\n };\n}\n\n/**\n * Per-source cache for {@link substituteToolOutput}: a substituted copy is\n * reused while the wire message and the fetched output are both unchanged, so\n * the lift cache downstream keeps its identity contract.\n */\nconst substitutedToolOutputCache = new WeakMap<\n LangChainMessage,\n { content: unknown; substituted: LangChainMessage }\n>();\n\n/**\n * A cut tool message with its full output put back, when the store holds one\n * fetched for this very output (`history/tool_output` answered for the same\n * `chars` the wire's marker reports); the marker leaves with the cut.\n * Messages that are whole, whose output has not been fetched, or whose fetched\n * output belongs to a length the host has since replaced, pass through by\n * identity — the loader sees the mismatch and fetches again.\n */\nexport function substituteToolOutput(\n message: LangChainMessage,\n toolOutputs: DeepAgentToolOutputStore\n): LangChainMessage {\n if (message.type !== 'tool' || typeof message.id !== 'string') return message;\n const marker = readTruncatedToolContent(message);\n if (marker === null) return message;\n const entry = toolOutputs.entry(message.id);\n if (entry === undefined || entry.chars !== marker.chars) return message;\n const { content } = entry;\n const cached = substitutedToolOutputCache.get(message);\n if (cached !== undefined && cached.content === content) return cached.substituted;\n const { [DEEP_AGENT_TRUNCATED_CONTENT_KEY]: _marker, ...rest } = message as Record<\n string,\n unknown\n >;\n const substituted = { ...rest, content } as unknown as LangChainMessage;\n substitutedToolOutputCache.set(message, { content, substituted });\n return substituted;\n}\n\nconst hasHiddenFrontendFlag = (value: unknown): boolean => {\n if (typeof value !== 'object' || value === null || Array.isArray(value)) {\n return false;\n }\n const record = value as Record<string, unknown>;\n if (record.hide_from_frontend === true) return true;\n return hasHiddenFrontendFlag(record.custom);\n};\n\n/**\n * Internal orchestration messages are useful in a trace export but must never\n * become conversation bubbles. Agora stores the flag in `additional_kwargs`;\n * live converter state can expose the same value under `metadata.custom`.\n */\nexport function isMessageHiddenFromFrontend(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const record = message as Record<string, unknown>;\n return (\n hasHiddenFrontendFlag(record.additional_kwargs) || hasHiddenFrontendFlag(record.metadata)\n );\n}\n\n/**\n * The message `type`s `convertLangChainMessages` actually maps: its\n * `switch (message.type)` covers exactly these four and has no `default`, so it\n * returns `undefined` for anything else (a `RemoveMessage` `type: \"remove\"`\n * from summarization/compaction, a partial chunk, a future type).\n */\nconst CONVERTIBLE_MESSAGE_TYPES: ReadonlySet<string> = new Set(['system', 'human', 'ai', 'tool']);\n\n/**\n * Whether `convertLangChainMessages` can map this message at all.\n *\n * An unmappable entry converts to `undefined`, which assistant-ui's\n * `convertExternalMessages` wraps as `outputs: [undefined]` and then reads\n * `output.role` off — throwing `Cannot read property 'role' of undefined` and\n * taking the entire thread down instead of dropping one message.\n */\nexport function isConvertibleChatMessage(message: unknown): boolean {\n if (typeof message !== 'object' || message === null || Array.isArray(message)) {\n return false;\n }\n const type: unknown = (message as Record<string, unknown>).type;\n return typeof type === 'string' && CONVERTIBLE_MESSAGE_TYPES.has(type);\n}\n\n/**\n * Per-source cache for the lift pipeline in {@link sanitizeLangChainMessages}.\n *\n * The pipeline runs on EVERY state pass (once per streamed token), and the two\n * lifts that fire for essentially every message — {@link liftBranchIntoMetadata}\n * (the backend stamps `branch` on all indexed messages) and\n * {@link liftModelIntoMetadata} — allocate a fresh object each time they lift.\n * Without a cache, every message's identity churns on every token, defeating\n * all downstream per-source memoization (`getExternalStoreMessages`-keyed\n * reads, converter callback caches) and re-deriving the whole thread per token.\n *\n * Object identity is a sound cache key here: every statewire fold (op apply,\n * optimistic, materialize) runs through `statewire`'s copy-on-write\n * `recordMutations`, whose `finalize` returns the ORIGINAL object for\n * untouched nodes and a fresh copy for touched ones. A message is therefore\n * REPLACED — never mutated in place — whenever any of its fields change\n * (including the streaming tail's string-insert content growth), and an\n * unchanged message keeps its exact identity across passes. The corpus test\n * (`statewire-corpus.test.ts`) pins this identity contract against the real\n * client, so a statewire upgrade that switched to in-place mutation would fail\n * there rather than silently serving stale lifts from this cache.\n *\n * Module-level on purpose: keys are the wire message objects themselves, so\n * entries die with their state trees and threads cannot collide.\n */\nconst liftedMessageCache = new WeakMap<LangChainMessage, LangChainMessage>();\n\n/** The full lift chain for one message, memoized per source object. */\nfunction liftMessageIntoMetadataChannels(message: LangChainMessage): LangChainMessage {\n const cached = liftedMessageCache.get(message);\n if (cached !== undefined) return cached;\n const lifted = liftBranchIntoMetadata(\n liftTruncatedToolOutputIntoArtifact(\n liftToolMessageMetadataIntoArtifact(\n liftAopSourceIntoMetadata(\n liftHandoffSummaryIntoMetadata(\n liftCompactionBoundaryIntoMetadata(\n liftConversationStatsIntoMetadata(\n liftModelIntoMetadata(\n liftErrorsIntoMetadata(\n liftTerminationIntoMetadata(liftRunMarkerIntoMetadata(message))\n )\n )\n )\n )\n )\n )\n )\n )\n );\n liftedMessageCache.set(message, lifted);\n return lifted;\n}\n\n/**\n * The one message pipeline every deep-agent surface renders through:\n *\n * 1. drop messages the shipped converter cannot map (`RemoveMessage` etc. —\n * an unmappable entry would crash the whole thread) and internal\n * orchestration messages flagged hidden-from-frontend;\n * 2. lift run-lifecycle markers into the converter's `metadata` channel\n * (see {@link liftRunMarkerIntoMetadata}) so the cancellation row styles as\n * a status divider instead of a plain assistant bubble, and the termination\n * reason + handoff flag alongside them (see\n * {@link liftTerminationIntoMetadata}) so every surface can render a\n * runtime ending (see `runtime-messages.ts`);\n * 3. lift each AI message's producing model into the same channel\n * (see {@link liftModelIntoMetadata}) so model badges reflect the model that\n * actually generated the message, not the currently configured one;\n * 4. lift each AI message's per-message conversation-stats snapshot into the\n * same channel (see {@link liftConversationStatsIntoMetadata}) so the model\n * badge's hover card shows that message's own context usage;\n * 5. lift the compaction boundary flag on `/compact` summary messages into the\n * same channel (see {@link liftCompactionBoundaryIntoMetadata}) so the\n * transcript renders its \"Previous conversation summarized\" divider;\n * 6. lift the handoff summary flag on carried-over context messages into the\n * same channel (see {@link liftHandoffSummaryIntoMetadata}) so the\n * transcript keeps the summary behind its context panel;\n * 7. lift an AOP run's provenance on its kicking-off user message into the\n * same channel (see {@link liftAopSourceIntoMetadata}) so the message\n * renders its \"AOP · <title>\" origin badge;\n * 8. lift each ToolMessage's user-set `additional_kwargs` into\n * `artifact._tool_message_metadata` (see\n * {@link liftToolMessageMetadataIntoArtifact}) so persisted per-tool-call\n * metadata reaches tool cards after a reload;\n * 9. lift a windowed wire's tool-output truncation marker into the tool-call\n * part's `artifact` (see {@link liftTruncatedToolOutputIntoArtifact}) so a\n * tool card can fetch the whole output — after first putting back any\n * output already fetched (see {@link substituteToolOutput});\n * 10. lift the host's per-message branch stamp into the same channel\n * (see {@link liftBranchIntoMetadata}) so the branch picker renders each\n * message's edit fan-out.\n *\n * Identity-stable per source: an unchanged source message maps to the\n * IDENTICAL lifted object across passes (see {@link liftedMessageCache}).\n */\nexport function sanitizeLangChainMessages(\n messages: readonly LangChainMessage[],\n { toolOutputs }: { toolOutputs?: DeepAgentToolOutputStore } = {}\n): LangChainMessage[] {\n return messages\n .filter((message) => isConvertibleChatMessage(message) && !isMessageHiddenFromFrontend(message))\n .map((message) =>\n liftMessageIntoMetadataChannels(\n toolOutputs ? substituteToolOutput(message, toolOutputs) : message\n )\n );\n}\n\n/** Type guard for the runtime state — an object carrying a `messages` array. */\nexport function isLangGraphState(value: unknown): value is LangGraphState {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'messages' in value &&\n Array.isArray((value as LangGraphState).messages)\n );\n}\n\nexport interface ConvertDeepAgentStateOptions {\n laneProjection?: DeepAgentLaneProjection;\n /**\n * The viewer, as the backend would stamp them: the author of this client's\n * own not-yet-acknowledged lane entries (see {@link laneEntryAuthor}).\n */\n selfAuthor?: DeepAgentMessageAuthor | null;\n /**\n * Full tool outputs fetched back for a windowed wire's cut tool messages\n * (see {@link substituteToolOutput}); omitted on surfaces that never\n * declare a window.\n */\n toolOutputs?: DeepAgentToolOutputStore;\n /**\n * User turns this client took back ahead of the wire (see\n * {@link DeepAgentWithdrawnTurns}): omitted from the transcript, together\n * with the pending-assistant placeholder a withdrawn tail would earn.\n */\n withdrawnTurns?: DeepAgentWithdrawnTurns;\n}\n\n/**\n * Map server state → assistant-ui thread messages.\n *\n * `state.messages` is the authoritative LangChain history streamed by the\n * backend. The replicated run status (`runs[0].status`) drives the\n * pending-assistant placeholder and the running status. Lane entries render at\n * the tail, after the placeholder, while the run is live (the run is still\n * working the history above them); once the thread rests stopped/errored they\n * are omitted (surfaces with a queue panel list them there instead).\n */\nexport function convertDeepAgentState(\n state: unknown,\n {\n laneProjection = 'both',\n selfAuthor = null,\n toolOutputs,\n withdrawnTurns,\n }: ConvertDeepAgentStateOptions = {}\n) {\n // A wire without a snapshot yet (pre-attach, replay boundary) converts as the\n // typed pre-stream default: an empty resting thread, never `undefined`.\n const values = readDeepAgentStreamValues(state);\n const status = values.runs?.[0]?.status;\n const isRunning = status === 'running';\n const isWithdrawn = (message: LangChainMessage): boolean =>\n withdrawnTurns !== undefined && message.id !== undefined && withdrawnTurns.isWithdrawn(message.id);\n const laneTail = (\n status === 'stopped' || status === 'error'\n ? []\n : laneTailMessages(values, laneProjection, selfAuthor)\n ).filter((message) => !isWithdrawn(message));\n const sanitized: LangChainMessage[] = isLangGraphState(values)\n ? sanitizeLangChainMessages(values.messages, { toolOutputs })\n : [];\n // A withdrawn tail is a turn nobody is answering: no placeholder for it.\n const tail: LangChainMessage | undefined = sanitized[sanitized.length - 1];\n const tailWithdrawn: boolean = tail !== undefined && isWithdrawn(tail);\n const stateMessages = sanitized.filter((message) => !isWithdrawn(message));\n return {\n messages: messageConverter.toThreadMessages(\n [...withPendingAssistantMessage(stateMessages, isRunning && !tailWithdrawn), ...laneTail],\n isRunning\n ),\n isRunning,\n };\n}\n"],"names":["createMessageConverter","convertLangChainMessages","laneEntryAuthor","ATHENA_MESSAGE_AUTHOR_KEY","buildOptimisticHumanContent","runsMaterialize","getExternalStoreMessages","readTruncatedToolContent","ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY","DEEP_AGENT_TRUNCATED_CONTENT_KEY","readDeepAgentStreamValues"],"mappings":"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"}
@@ -153,6 +153,23 @@ function liftTerminationIntoMetadata(message) {
153
153
  }
154
154
  };
155
155
  }
156
+ const ATHENA_ERRORS_KEY = "errors";
157
+ function liftErrorsIntoMetadata(message) {
158
+ if (message.type !== "ai") return message;
159
+ const kwargs = message.additional_kwargs;
160
+ const errors = kwargs == null ? void 0 : kwargs[ATHENA_ERRORS_KEY];
161
+ if (!Array.isArray(errors) || errors.length === 0) return message;
162
+ const existingMetadata = kwargs == null ? void 0 : kwargs.metadata;
163
+ const metadata = typeof existingMetadata === "object" && existingMetadata !== null ? existingMetadata : {};
164
+ if (Array.isArray(metadata[ATHENA_ERRORS_KEY])) return message;
165
+ return {
166
+ ...message,
167
+ additional_kwargs: {
168
+ ...kwargs,
169
+ metadata: { ...metadata, [ATHENA_ERRORS_KEY]: errors }
170
+ }
171
+ };
172
+ }
156
173
  function liftModelIntoMetadata(message) {
157
174
  if (message.type !== "ai") return message;
158
175
  const responseMetadata = message.response_metadata;
@@ -358,7 +375,9 @@ function liftMessageIntoMetadataChannels(message) {
358
375
  liftCompactionBoundaryIntoMetadata(
359
376
  liftConversationStatsIntoMetadata(
360
377
  liftModelIntoMetadata(
361
- liftTerminationIntoMetadata(liftRunMarkerIntoMetadata(message))
378
+ liftErrorsIntoMetadata(
379
+ liftTerminationIntoMetadata(liftRunMarkerIntoMetadata(message))
380
+ )
362
381
  )
363
382
  )
364
383
  )
@@ -380,22 +399,32 @@ function sanitizeLangChainMessages(messages, { toolOutputs } = {}) {
380
399
  function isLangGraphState(value) {
381
400
  return typeof value === "object" && value !== null && "messages" in value && Array.isArray(value.messages);
382
401
  }
383
- function convertDeepAgentState(state, { laneProjection = "both", selfAuthor = null, toolOutputs } = {}) {
402
+ function convertDeepAgentState(state, {
403
+ laneProjection = "both",
404
+ selfAuthor = null,
405
+ toolOutputs,
406
+ withdrawnTurns
407
+ } = {}) {
384
408
  var _a, _b;
385
409
  const values = readDeepAgentStreamValues(state);
386
410
  const status = (_b = (_a = values.runs) == null ? void 0 : _a[0]) == null ? void 0 : _b.status;
387
411
  const isRunning = status === "running";
388
- const laneTail = status === "stopped" || status === "error" ? [] : laneTailMessages(values, laneProjection, selfAuthor);
389
- const stateMessages = isLangGraphState(values) ? sanitizeLangChainMessages(values.messages, { toolOutputs }) : [];
412
+ const isWithdrawn = (message) => withdrawnTurns !== void 0 && message.id !== void 0 && withdrawnTurns.isWithdrawn(message.id);
413
+ const laneTail = (status === "stopped" || status === "error" ? [] : laneTailMessages(values, laneProjection, selfAuthor)).filter((message) => !isWithdrawn(message));
414
+ const sanitized = isLangGraphState(values) ? sanitizeLangChainMessages(values.messages, { toolOutputs }) : [];
415
+ const tail = sanitized[sanitized.length - 1];
416
+ const tailWithdrawn = tail !== void 0 && isWithdrawn(tail);
417
+ const stateMessages = sanitized.filter((message) => !isWithdrawn(message));
390
418
  return {
391
419
  messages: messageConverter.toThreadMessages(
392
- [...withPendingAssistantMessage(stateMessages, isRunning), ...laneTail],
420
+ [...withPendingAssistantMessage(stateMessages, isRunning && !tailWithdrawn), ...laneTail],
393
421
  isRunning
394
422
  ),
395
423
  isRunning
396
424
  };
397
425
  }
398
426
  export {
427
+ ATHENA_ERRORS_KEY,
399
428
  ATHENA_HANDOFF_KEY,
400
429
  ATHENA_QUEUED_LANE_KEY,
401
430
  ATHENA_RUN_MARKER_CANCELLED,
@@ -418,6 +447,7 @@ export {
418
447
  liftBranchIntoMetadata,
419
448
  liftCompactionBoundaryIntoMetadata,
420
449
  liftConversationStatsIntoMetadata,
450
+ liftErrorsIntoMetadata,
421
451
  liftHandoffSummaryIntoMetadata,
422
452
  liftModelIntoMetadata,
423
453
  liftRunMarkerIntoMetadata,