@artooi/agents-web-components 0.0.0-stage → 0.1.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 (543) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/LICENSE +21 -0
  3. package/README.md +4397 -2
  4. package/dist/agents-web-components.bundle.js +5708 -0
  5. package/dist/agents-web-components.bundle.js.map +7 -0
  6. package/dist/bundle.d.ts +3 -0
  7. package/dist/bundle.d.ts.map +1 -0
  8. package/dist/chat.d.ts +3 -0
  9. package/dist/chat.d.ts.map +1 -0
  10. package/dist/chat.js +16373 -0
  11. package/dist/chat.js.map +7 -0
  12. package/dist/chunk-ARNLFGCC.js +7715 -0
  13. package/dist/chunk-ARNLFGCC.js.map +7 -0
  14. package/dist/constants.d.ts +575 -0
  15. package/dist/constants.d.ts.map +1 -0
  16. package/dist/core/activity_registration.d.ts +19 -0
  17. package/dist/core/activity_registration.d.ts.map +1 -0
  18. package/dist/core/activity_registry.d.ts +46 -0
  19. package/dist/core/activity_registry.d.ts.map +1 -0
  20. package/dist/core/activity_renderer.d.ts +20 -0
  21. package/dist/core/activity_renderer.d.ts.map +1 -0
  22. package/dist/core/ag_ui_chat.d.ts +836 -0
  23. package/dist/core/ag_ui_chat.d.ts.map +1 -0
  24. package/dist/core/agui_client.d.ts +370 -0
  25. package/dist/core/agui_client.d.ts.map +1 -0
  26. package/dist/core/attachment.d.ts +42 -0
  27. package/dist/core/attachment.d.ts.map +1 -0
  28. package/dist/core/chat_registry.d.ts +23 -0
  29. package/dist/core/chat_registry.d.ts.map +1 -0
  30. package/dist/core/client_seed.d.ts +27 -0
  31. package/dist/core/client_seed.d.ts.map +1 -0
  32. package/dist/core/conversation_store.d.ts +195 -0
  33. package/dist/core/conversation_store.d.ts.map +1 -0
  34. package/dist/core/create_http_agent.d.ts +57 -0
  35. package/dist/core/create_http_agent.d.ts.map +1 -0
  36. package/dist/core/define_chat.d.ts +13 -0
  37. package/dist/core/define_chat.d.ts.map +1 -0
  38. package/dist/core/events/attachments_detail.d.ts +9 -0
  39. package/dist/core/events/attachments_detail.d.ts.map +1 -0
  40. package/dist/core/events/bar_height_detail.d.ts +6 -0
  41. package/dist/core/events/bar_height_detail.d.ts.map +1 -0
  42. package/dist/core/events/custom_agent_detail.d.ts +8 -0
  43. package/dist/core/events/custom_agent_detail.d.ts.map +1 -0
  44. package/dist/core/events/dock_width_detail.d.ts +8 -0
  45. package/dist/core/events/dock_width_detail.d.ts.map +1 -0
  46. package/dist/core/events/feedback_detail.d.ts +28 -0
  47. package/dist/core/events/feedback_detail.d.ts.map +1 -0
  48. package/dist/core/events/invalidate_detail.d.ts +16 -0
  49. package/dist/core/events/invalidate_detail.d.ts.map +1 -0
  50. package/dist/core/events/maximize_detail.d.ts +5 -0
  51. package/dist/core/events/maximize_detail.d.ts.map +1 -0
  52. package/dist/core/events/run_finished_detail.d.ts +36 -0
  53. package/dist/core/events/run_finished_detail.d.ts.map +1 -0
  54. package/dist/core/events/state_detail.d.ts +5 -0
  55. package/dist/core/events/state_detail.d.ts.map +1 -0
  56. package/dist/core/events/submit_detail.d.ts +8 -0
  57. package/dist/core/events/submit_detail.d.ts.map +1 -0
  58. package/dist/core/events/thread_detail.d.ts +27 -0
  59. package/dist/core/events/thread_detail.d.ts.map +1 -0
  60. package/dist/core/events/thread_title_detail.d.ts +8 -0
  61. package/dist/core/events/thread_title_detail.d.ts.map +1 -0
  62. package/dist/core/events/toggle_detail.d.ts +5 -0
  63. package/dist/core/events/toggle_detail.d.ts.map +1 -0
  64. package/dist/core/events/tool_run.d.ts +11 -0
  65. package/dist/core/events/tool_run.d.ts.map +1 -0
  66. package/dist/core/events/unread_detail.d.ts +11 -0
  67. package/dist/core/events/unread_detail.d.ts.map +1 -0
  68. package/dist/core/message_role.d.ts +4 -0
  69. package/dist/core/message_role.d.ts.map +1 -0
  70. package/dist/core/read_flag.d.ts +11 -0
  71. package/dist/core/read_flag.d.ts.map +1 -0
  72. package/dist/core/read_json_attribute.d.ts +15 -0
  73. package/dist/core/read_json_attribute.d.ts.map +1 -0
  74. package/dist/core/read_max_tool_rounds.d.ts +9 -0
  75. package/dist/core/read_max_tool_rounds.d.ts.map +1 -0
  76. package/dist/core/remote_conversation_store.d.ts +71 -0
  77. package/dist/core/remote_conversation_store.d.ts.map +1 -0
  78. package/dist/core/run_handlers.d.ts +85 -0
  79. package/dist/core/run_handlers.d.ts.map +1 -0
  80. package/dist/core/run_index.d.ts +58 -0
  81. package/dist/core/run_index.d.ts.map +1 -0
  82. package/dist/core/storage_scope.d.ts +118 -0
  83. package/dist/core/storage_scope.d.ts.map +1 -0
  84. package/dist/core/tool_outcome.d.ts +27 -0
  85. package/dist/core/tool_outcome.d.ts.map +1 -0
  86. package/dist/core/transcribe_audio.d.ts +32 -0
  87. package/dist/core/transcribe_audio.d.ts.map +1 -0
  88. package/dist/core/upload_attachment.d.ts +44 -0
  89. package/dist/core/upload_attachment.d.ts.map +1 -0
  90. package/dist/core/utils.d.ts +117 -0
  91. package/dist/core/utils.d.ts.map +1 -0
  92. package/dist/dom/animations.d.ts +122 -0
  93. package/dist/dom/animations.d.ts.map +1 -0
  94. package/dist/dom/dom_driver.d.ts +28 -0
  95. package/dist/dom/dom_driver.d.ts.map +1 -0
  96. package/dist/dom/highlight_overlay.d.ts +39 -0
  97. package/dist/dom/highlight_overlay.d.ts.map +1 -0
  98. package/dist/dom/native_setter.d.ts +5 -0
  99. package/dist/dom/native_setter.d.ts.map +1 -0
  100. package/dist/dom/ring_color.d.ts +16 -0
  101. package/dist/dom/ring_color.d.ts.map +1 -0
  102. package/dist/dom/utils.d.ts +3 -0
  103. package/dist/dom/utils.d.ts.map +1 -0
  104. package/dist/index.d.ts +62 -0
  105. package/dist/index.d.ts.map +1 -0
  106. package/dist/index.js +223 -0
  107. package/dist/index.js.map +7 -0
  108. package/dist/skills/fill_template.d.ts +16 -0
  109. package/dist/skills/fill_template.d.ts.map +1 -0
  110. package/dist/skills/parse_skills.d.ts +8 -0
  111. package/dist/skills/parse_skills.d.ts.map +1 -0
  112. package/dist/skills/skill.d.ts +33 -0
  113. package/dist/skills/skill.d.ts.map +1 -0
  114. package/dist/skills/skill_catalog.d.ts +69 -0
  115. package/dist/skills/skill_catalog.d.ts.map +1 -0
  116. package/dist/skills/skill_name_from.d.ts +12 -0
  117. package/dist/skills/skill_name_from.d.ts.map +1 -0
  118. package/dist/tools/chat_surface_tools.d.ts +77 -0
  119. package/dist/tools/chat_surface_tools.d.ts.map +1 -0
  120. package/dist/tools/client_tool_registry.d.ts +58 -0
  121. package/dist/tools/client_tool_registry.d.ts.map +1 -0
  122. package/dist/tools/is_destructive.d.ts +14 -0
  123. package/dist/tools/is_destructive.d.ts.map +1 -0
  124. package/dist/tools/is_navigates.d.ts +9 -0
  125. package/dist/tools/is_navigates.d.ts.map +1 -0
  126. package/dist/tools/page_action_tools.d.ts +30 -0
  127. package/dist/tools/page_action_tools.d.ts.map +1 -0
  128. package/dist/tools/page_map.d.ts +16 -0
  129. package/dist/tools/page_map.d.ts.map +1 -0
  130. package/dist/tools/page_state.d.ts +27 -0
  131. package/dist/tools/page_state.d.ts.map +1 -0
  132. package/dist/tools/parse_tool_catalog.d.ts +21 -0
  133. package/dist/tools/parse_tool_catalog.d.ts.map +1 -0
  134. package/dist/tools/route_map.d.ts +37 -0
  135. package/dist/tools/route_map.d.ts.map +1 -0
  136. package/dist/tools/tool_catalog.d.ts +86 -0
  137. package/dist/tools/tool_catalog.d.ts.map +1 -0
  138. package/dist/tools/tool_dispatch.d.ts +82 -0
  139. package/dist/tools/tool_dispatch.d.ts.map +1 -0
  140. package/dist/ui/charts/chart_block.d.ts +57 -0
  141. package/dist/ui/charts/chart_block.d.ts.map +1 -0
  142. package/dist/ui/charts/chart_spec_from.d.ts +12 -0
  143. package/dist/ui/charts/chart_spec_from.d.ts.map +1 -0
  144. package/dist/ui/charts/chart_styles.d.ts +2 -0
  145. package/dist/ui/charts/chart_styles.d.ts.map +1 -0
  146. package/dist/ui/charts/chart_tool.d.ts +15 -0
  147. package/dist/ui/charts/chart_tool.d.ts.map +1 -0
  148. package/dist/ui/composer/attachment_styles.d.ts +2 -0
  149. package/dist/ui/composer/attachment_styles.d.ts.map +1 -0
  150. package/dist/ui/composer/attachment_tray.d.ts +53 -0
  151. package/dist/ui/composer/attachment_tray.d.ts.map +1 -0
  152. package/dist/ui/composer/auto_grow.d.ts +10 -0
  153. package/dist/ui/composer/auto_grow.d.ts.map +1 -0
  154. package/dist/ui/composer/build_composer.d.ts +32 -0
  155. package/dist/ui/composer/build_composer.d.ts.map +1 -0
  156. package/dist/ui/composer/composer_attachments.d.ts +57 -0
  157. package/dist/ui/composer/composer_attachments.d.ts.map +1 -0
  158. package/dist/ui/composer/composer_styles.d.ts +2 -0
  159. package/dist/ui/composer/composer_styles.d.ts.map +1 -0
  160. package/dist/ui/composer/composer_voice.d.ts +48 -0
  161. package/dist/ui/composer/composer_voice.d.ts.map +1 -0
  162. package/dist/ui/composer/skill_styles.d.ts +2 -0
  163. package/dist/ui/composer/skill_styles.d.ts.map +1 -0
  164. package/dist/ui/composer/skills_menu.d.ts +34 -0
  165. package/dist/ui/composer/skills_menu.d.ts.map +1 -0
  166. package/dist/ui/composer/voice_input.d.ts +46 -0
  167. package/dist/ui/composer/voice_input.d.ts.map +1 -0
  168. package/dist/ui/excerpts/attach_copy_buttons.d.ts +13 -0
  169. package/dist/ui/excerpts/attach_copy_buttons.d.ts.map +1 -0
  170. package/dist/ui/excerpts/copy_payload.d.ts +30 -0
  171. package/dist/ui/excerpts/copy_payload.d.ts.map +1 -0
  172. package/dist/ui/excerpts/page_quote_offer.d.ts +33 -0
  173. package/dist/ui/excerpts/page_quote_offer.d.ts.map +1 -0
  174. package/dist/ui/excerpts/place_offer.d.ts +9 -0
  175. package/dist/ui/excerpts/place_offer.d.ts.map +1 -0
  176. package/dist/ui/excerpts/quote_selection.d.ts +66 -0
  177. package/dist/ui/excerpts/quote_selection.d.ts.map +1 -0
  178. package/dist/ui/excerpts/transcript_quote_offer.d.ts +57 -0
  179. package/dist/ui/excerpts/transcript_quote_offer.d.ts.map +1 -0
  180. package/dist/ui/fill_ui_string.d.ts +32 -0
  181. package/dist/ui/fill_ui_string.d.ts.map +1 -0
  182. package/dist/ui/history/checkpoint_menu.d.ts +41 -0
  183. package/dist/ui/history/checkpoint_menu.d.ts.map +1 -0
  184. package/dist/ui/history/conversation_history.d.ts +317 -0
  185. package/dist/ui/history/conversation_history.d.ts.map +1 -0
  186. package/dist/ui/history/history_styles.d.ts +2 -0
  187. package/dist/ui/history/history_styles.d.ts.map +1 -0
  188. package/dist/ui/history/relative_time.d.ts +23 -0
  189. package/dist/ui/history/relative_time.d.ts.map +1 -0
  190. package/dist/ui/history/thread_drawer.d.ts +64 -0
  191. package/dist/ui/history/thread_drawer.d.ts.map +1 -0
  192. package/dist/ui/history/utils.d.ts +2 -0
  193. package/dist/ui/history/utils.d.ts.map +1 -0
  194. package/dist/ui/interrupts/approval_card.d.ts +64 -0
  195. package/dist/ui/interrupts/approval_card.d.ts.map +1 -0
  196. package/dist/ui/interrupts/approval_styles.d.ts +2 -0
  197. package/dist/ui/interrupts/approval_styles.d.ts.map +1 -0
  198. package/dist/ui/interrupts/confirmation_card.d.ts +51 -0
  199. package/dist/ui/interrupts/confirmation_card.d.ts.map +1 -0
  200. package/dist/ui/interrupts/confirmation_styles.d.ts +2 -0
  201. package/dist/ui/interrupts/confirmation_styles.d.ts.map +1 -0
  202. package/dist/ui/interrupts/pending_decision.d.ts +22 -0
  203. package/dist/ui/interrupts/pending_decision.d.ts.map +1 -0
  204. package/dist/ui/interrupts/question_card.d.ts +49 -0
  205. package/dist/ui/interrupts/question_card.d.ts.map +1 -0
  206. package/dist/ui/interrupts/question_styles.d.ts +2 -0
  207. package/dist/ui/interrupts/question_styles.d.ts.map +1 -0
  208. package/dist/ui/place/place.d.ts +17 -0
  209. package/dist/ui/place/place.d.ts.map +1 -0
  210. package/dist/ui/place/place_chip.d.ts +26 -0
  211. package/dist/ui/place/place_chip.d.ts.map +1 -0
  212. package/dist/ui/place/place_from_route.d.ts +9 -0
  213. package/dist/ui/place/place_from_route.d.ts.map +1 -0
  214. package/dist/ui/place/place_styles.d.ts +2 -0
  215. package/dist/ui/place/place_styles.d.ts.map +1 -0
  216. package/dist/ui/placement/clamp_launcher.d.ts +20 -0
  217. package/dist/ui/placement/clamp_launcher.d.ts.map +1 -0
  218. package/dist/ui/placement/clamp_panel.d.ts +13 -0
  219. package/dist/ui/placement/clamp_panel.d.ts.map +1 -0
  220. package/dist/ui/placement/collapse_styles.d.ts +2 -0
  221. package/dist/ui/placement/collapse_styles.d.ts.map +1 -0
  222. package/dist/ui/placement/dock_styles.d.ts +2 -0
  223. package/dist/ui/placement/dock_styles.d.ts.map +1 -0
  224. package/dist/ui/placement/embedded_styles.d.ts +2 -0
  225. package/dist/ui/placement/embedded_styles.d.ts.map +1 -0
  226. package/dist/ui/placement/launcher_drag.d.ts +49 -0
  227. package/dist/ui/placement/launcher_drag.d.ts.map +1 -0
  228. package/dist/ui/placement/launcher_placement.d.ts +77 -0
  229. package/dist/ui/placement/launcher_placement.d.ts.map +1 -0
  230. package/dist/ui/placement/launcher_styles.d.ts +2 -0
  231. package/dist/ui/placement/launcher_styles.d.ts.map +1 -0
  232. package/dist/ui/placement/page_styles.d.ts +2 -0
  233. package/dist/ui/placement/page_styles.d.ts.map +1 -0
  234. package/dist/ui/placement/panel_drag.d.ts +40 -0
  235. package/dist/ui/placement/panel_drag.d.ts.map +1 -0
  236. package/dist/ui/placement/panel_placement.d.ts +173 -0
  237. package/dist/ui/placement/panel_placement.d.ts.map +1 -0
  238. package/dist/ui/placement/place_widget.d.ts +31 -0
  239. package/dist/ui/placement/place_widget.d.ts.map +1 -0
  240. package/dist/ui/placement/preset_styles.d.ts +2 -0
  241. package/dist/ui/placement/preset_styles.d.ts.map +1 -0
  242. package/dist/ui/placement/resize_handle.d.ts +78 -0
  243. package/dist/ui/placement/resize_handle.d.ts.map +1 -0
  244. package/dist/ui/placement/resize_styles.d.ts +2 -0
  245. package/dist/ui/placement/resize_styles.d.ts.map +1 -0
  246. package/dist/ui/placement/sidebar_styles.d.ts +2 -0
  247. package/dist/ui/placement/sidebar_styles.d.ts.map +1 -0
  248. package/dist/ui/placement/small_viewport_styles.d.ts +2 -0
  249. package/dist/ui/placement/small_viewport_styles.d.ts.map +1 -0
  250. package/dist/ui/placement/surface_for.d.ts +93 -0
  251. package/dist/ui/placement/surface_for.d.ts.map +1 -0
  252. package/dist/ui/progress/prettify_tool_name.d.ts +11 -0
  253. package/dist/ui/progress/prettify_tool_name.d.ts.map +1 -0
  254. package/dist/ui/progress/run_announcer.d.ts +41 -0
  255. package/dist/ui/progress/run_announcer.d.ts.map +1 -0
  256. package/dist/ui/progress/run_notice.d.ts +21 -0
  257. package/dist/ui/progress/run_notice.d.ts.map +1 -0
  258. package/dist/ui/progress/run_notice_styles.d.ts +2 -0
  259. package/dist/ui/progress/run_notice_styles.d.ts.map +1 -0
  260. package/dist/ui/progress/subagent_panel.d.ts +101 -0
  261. package/dist/ui/progress/subagent_panel.d.ts.map +1 -0
  262. package/dist/ui/progress/subagent_progress.d.ts +57 -0
  263. package/dist/ui/progress/subagent_progress.d.ts.map +1 -0
  264. package/dist/ui/progress/subagent_update.d.ts +28 -0
  265. package/dist/ui/progress/subagent_update.d.ts.map +1 -0
  266. package/dist/ui/progress/thoughts_block.d.ts +57 -0
  267. package/dist/ui/progress/thoughts_block.d.ts.map +1 -0
  268. package/dist/ui/progress/thoughts_styles.d.ts +2 -0
  269. package/dist/ui/progress/thoughts_styles.d.ts.map +1 -0
  270. package/dist/ui/progress/tool_call_card.d.ts +167 -0
  271. package/dist/ui/progress/tool_call_card.d.ts.map +1 -0
  272. package/dist/ui/progress/tool_group_styles.d.ts +2 -0
  273. package/dist/ui/progress/tool_group_styles.d.ts.map +1 -0
  274. package/dist/ui/progress/tool_step_group.d.ts +50 -0
  275. package/dist/ui/progress/tool_step_group.d.ts.map +1 -0
  276. package/dist/ui/progress/tool_step_styles.d.ts +2 -0
  277. package/dist/ui/progress/tool_step_styles.d.ts.map +1 -0
  278. package/dist/ui/shell/adopt_styles.d.ts +28 -0
  279. package/dist/ui/shell/adopt_styles.d.ts.map +1 -0
  280. package/dist/ui/shell/build_header.d.ts +45 -0
  281. package/dist/ui/shell/build_header.d.ts.map +1 -0
  282. package/dist/ui/shell/build_launcher.d.ts +33 -0
  283. package/dist/ui/shell/build_launcher.d.ts.map +1 -0
  284. package/dist/ui/shell/collapse_glyph.d.ts +14 -0
  285. package/dist/ui/shell/collapse_glyph.d.ts.map +1 -0
  286. package/dist/ui/shell/frame_styles.d.ts +2 -0
  287. package/dist/ui/shell/frame_styles.d.ts.map +1 -0
  288. package/dist/ui/shell/glyph_slot.d.ts +7 -0
  289. package/dist/ui/shell/glyph_slot.d.ts.map +1 -0
  290. package/dist/ui/shell/header_button.d.ts +11 -0
  291. package/dist/ui/shell/header_button.d.ts.map +1 -0
  292. package/dist/ui/shell/icon_element.d.ts +12 -0
  293. package/dist/ui/shell/icon_element.d.ts.map +1 -0
  294. package/dist/ui/shell/is_unread_badge_enabled.d.ts +10 -0
  295. package/dist/ui/shell/is_unread_badge_enabled.d.ts.map +1 -0
  296. package/dist/ui/shell/read_launcher_icon_url.d.ts +8 -0
  297. package/dist/ui/shell/read_launcher_icon_url.d.ts.map +1 -0
  298. package/dist/ui/shell/theme_styles.d.ts +2 -0
  299. package/dist/ui/shell/theme_styles.d.ts.map +1 -0
  300. package/dist/ui/shell/token_styles.d.ts +2 -0
  301. package/dist/ui/shell/token_styles.d.ts.map +1 -0
  302. package/dist/ui/styles.d.ts +2 -0
  303. package/dist/ui/styles.d.ts.map +1 -0
  304. package/dist/ui/themes/classic_styles.d.ts +2 -0
  305. package/dist/ui/themes/classic_styles.d.ts.map +1 -0
  306. package/dist/ui/themes/classic_tokens.d.ts +2 -0
  307. package/dist/ui/themes/classic_tokens.d.ts.map +1 -0
  308. package/dist/ui/themes/code_tokens.d.ts +2 -0
  309. package/dist/ui/themes/code_tokens.d.ts.map +1 -0
  310. package/dist/ui/themes/ledger_styles.d.ts +2 -0
  311. package/dist/ui/themes/ledger_styles.d.ts.map +1 -0
  312. package/dist/ui/themes/ledger_tokens.d.ts +2 -0
  313. package/dist/ui/themes/ledger_tokens.d.ts.map +1 -0
  314. package/dist/ui/themes/modern_styles.d.ts +2 -0
  315. package/dist/ui/themes/modern_styles.d.ts.map +1 -0
  316. package/dist/ui/themes/quiet_styles.d.ts +2 -0
  317. package/dist/ui/themes/quiet_styles.d.ts.map +1 -0
  318. package/dist/ui/themes/scheme_styles.d.ts +2 -0
  319. package/dist/ui/themes/scheme_styles.d.ts.map +1 -0
  320. package/dist/ui/themes/soft_styles.d.ts +2 -0
  321. package/dist/ui/themes/soft_styles.d.ts.map +1 -0
  322. package/dist/ui/themes/soft_tokens.d.ts +2 -0
  323. package/dist/ui/themes/soft_tokens.d.ts.map +1 -0
  324. package/dist/ui/themes/theme_hosts.d.ts +15 -0
  325. package/dist/ui/themes/theme_hosts.d.ts.map +1 -0
  326. package/dist/ui/transcript/answer_actions.d.ts +50 -0
  327. package/dist/ui/transcript/answer_actions.d.ts.map +1 -0
  328. package/dist/ui/transcript/answer_stream.d.ts +56 -0
  329. package/dist/ui/transcript/answer_stream.d.ts.map +1 -0
  330. package/dist/ui/transcript/attachment_chip_styles.d.ts +2 -0
  331. package/dist/ui/transcript/attachment_chip_styles.d.ts.map +1 -0
  332. package/dist/ui/transcript/attachment_chips.d.ts +21 -0
  333. package/dist/ui/transcript/attachment_chips.d.ts.map +1 -0
  334. package/dist/ui/transcript/build_transcript.d.ts +21 -0
  335. package/dist/ui/transcript/build_transcript.d.ts.map +1 -0
  336. package/dist/ui/transcript/greeting_styles.d.ts +2 -0
  337. package/dist/ui/transcript/greeting_styles.d.ts.map +1 -0
  338. package/dist/ui/transcript/groups_tool_steps.d.ts +13 -0
  339. package/dist/ui/transcript/groups_tool_steps.d.ts.map +1 -0
  340. package/dist/ui/transcript/message_action_styles.d.ts +2 -0
  341. package/dist/ui/transcript/message_action_styles.d.ts.map +1 -0
  342. package/dist/ui/transcript/message_actions.d.ts +74 -0
  343. package/dist/ui/transcript/message_actions.d.ts.map +1 -0
  344. package/dist/ui/transcript/message_styles.d.ts +2 -0
  345. package/dist/ui/transcript/message_styles.d.ts.map +1 -0
  346. package/dist/ui/transcript/render_markdown.d.ts +40 -0
  347. package/dist/ui/transcript/render_markdown.d.ts.map +1 -0
  348. package/dist/ui/transcript/render_or_warn.d.ts +16 -0
  349. package/dist/ui/transcript/render_or_warn.d.ts.map +1 -0
  350. package/dist/ui/transcript/reveal_words.d.ts +8 -0
  351. package/dist/ui/transcript/reveal_words.d.ts.map +1 -0
  352. package/dist/ui/transcript/starter_chips.d.ts +21 -0
  353. package/dist/ui/transcript/starter_chips.d.ts.map +1 -0
  354. package/dist/ui/transcript/stick_to_bottom.d.ts +55 -0
  355. package/dist/ui/transcript/stick_to_bottom.d.ts.map +1 -0
  356. package/dist/ui/transcript/suggestion_chips.d.ts +29 -0
  357. package/dist/ui/transcript/suggestion_chips.d.ts.map +1 -0
  358. package/dist/ui/transcript/transcript.d.ts +196 -0
  359. package/dist/ui/transcript/transcript.d.ts.map +1 -0
  360. package/dist/ui/transcript/transcript_styles.d.ts +2 -0
  361. package/dist/ui/transcript/transcript_styles.d.ts.map +1 -0
  362. package/dist/ui/ui_strings.d.ts +352 -0
  363. package/dist/ui/ui_strings.d.ts.map +1 -0
  364. package/dist/version.d.ts +2 -0
  365. package/dist/version.d.ts.map +1 -0
  366. package/package.json +86 -3
  367. package/src/bundle.ts +2 -0
  368. package/src/chat.ts +2 -0
  369. package/src/constants.ts +650 -0
  370. package/src/core/activity_registration.ts +19 -0
  371. package/src/core/activity_registry.ts +133 -0
  372. package/src/core/activity_renderer.ts +19 -0
  373. package/src/core/ag_ui_chat.ts +3966 -0
  374. package/src/core/agui_client.ts +1028 -0
  375. package/src/core/attachment.ts +62 -0
  376. package/src/core/chat_registry.ts +53 -0
  377. package/src/core/client_seed.ts +27 -0
  378. package/src/core/conversation_store.ts +478 -0
  379. package/src/core/create_http_agent.ts +97 -0
  380. package/src/core/define_chat.ts +25 -0
  381. package/src/core/events/attachments_detail.ts +9 -0
  382. package/src/core/events/bar_height_detail.ts +5 -0
  383. package/src/core/events/custom_agent_detail.ts +7 -0
  384. package/src/core/events/dock_width_detail.ts +7 -0
  385. package/src/core/events/feedback_detail.ts +27 -0
  386. package/src/core/events/invalidate_detail.ts +15 -0
  387. package/src/core/events/maximize_detail.ts +4 -0
  388. package/src/core/events/run_finished_detail.ts +36 -0
  389. package/src/core/events/state_detail.ts +4 -0
  390. package/src/core/events/submit_detail.ts +8 -0
  391. package/src/core/events/thread_detail.ts +26 -0
  392. package/src/core/events/thread_title_detail.ts +7 -0
  393. package/src/core/events/toggle_detail.ts +4 -0
  394. package/src/core/events/tool_run.ts +10 -0
  395. package/src/core/events/unread_detail.ts +10 -0
  396. package/src/core/message_role.ts +4 -0
  397. package/src/core/read_flag.ts +13 -0
  398. package/src/core/read_json_attribute.ts +30 -0
  399. package/src/core/read_max_tool_rounds.ts +13 -0
  400. package/src/core/remote_conversation_store.ts +279 -0
  401. package/src/core/run_handlers.ts +524 -0
  402. package/src/core/run_index.ts +114 -0
  403. package/src/core/storage_scope.ts +291 -0
  404. package/src/core/tool_outcome.ts +39 -0
  405. package/src/core/transcribe_audio.ts +76 -0
  406. package/src/core/upload_attachment.ts +130 -0
  407. package/src/core/utils.ts +230 -0
  408. package/src/dom/animations.ts +341 -0
  409. package/src/dom/dom_driver.ts +91 -0
  410. package/src/dom/highlight_overlay.ts +204 -0
  411. package/src/dom/native_setter.ts +40 -0
  412. package/src/dom/ring_color.ts +206 -0
  413. package/src/dom/utils.ts +5 -0
  414. package/src/index.ts +220 -0
  415. package/src/skills/fill_template.ts +31 -0
  416. package/src/skills/parse_skills.ts +31 -0
  417. package/src/skills/skill.ts +32 -0
  418. package/src/skills/skill_catalog.ts +172 -0
  419. package/src/skills/skill_name_from.ts +19 -0
  420. package/src/tools/chat_surface_tools.ts +183 -0
  421. package/src/tools/client_tool_registry.ts +80 -0
  422. package/src/tools/is_destructive.ts +17 -0
  423. package/src/tools/is_navigates.ts +12 -0
  424. package/src/tools/page_action_tools.ts +123 -0
  425. package/src/tools/page_map.ts +26 -0
  426. package/src/tools/page_state.ts +57 -0
  427. package/src/tools/parse_tool_catalog.ts +41 -0
  428. package/src/tools/route_map.ts +132 -0
  429. package/src/tools/tool_catalog.ts +334 -0
  430. package/src/tools/tool_dispatch.ts +453 -0
  431. package/src/ui/charts/chart_block.ts +519 -0
  432. package/src/ui/charts/chart_spec_from.ts +125 -0
  433. package/src/ui/charts/chart_styles.ts +63 -0
  434. package/src/ui/charts/chart_tool.ts +72 -0
  435. package/src/ui/composer/attachment_styles.ts +71 -0
  436. package/src/ui/composer/attachment_tray.ts +295 -0
  437. package/src/ui/composer/auto_grow.ts +12 -0
  438. package/src/ui/composer/build_composer.ts +114 -0
  439. package/src/ui/composer/composer_attachments.ts +324 -0
  440. package/src/ui/composer/composer_styles.ts +177 -0
  441. package/src/ui/composer/composer_voice.ts +93 -0
  442. package/src/ui/composer/skill_styles.ts +120 -0
  443. package/src/ui/composer/skills_menu.ts +188 -0
  444. package/src/ui/composer/voice_input.ts +282 -0
  445. package/src/ui/excerpts/attach_copy_buttons.ts +73 -0
  446. package/src/ui/excerpts/copy_payload.ts +156 -0
  447. package/src/ui/excerpts/page_quote_offer.ts +203 -0
  448. package/src/ui/excerpts/place_offer.ts +24 -0
  449. package/src/ui/excerpts/quote_selection.ts +345 -0
  450. package/src/ui/excerpts/transcript_quote_offer.ts +175 -0
  451. package/src/ui/fill_ui_string.ts +41 -0
  452. package/src/ui/history/checkpoint_menu.ts +300 -0
  453. package/src/ui/history/conversation_history.ts +1169 -0
  454. package/src/ui/history/history_styles.ts +492 -0
  455. package/src/ui/history/relative_time.ts +50 -0
  456. package/src/ui/history/thread_drawer.ts +471 -0
  457. package/src/ui/history/utils.ts +11 -0
  458. package/src/ui/interrupts/approval_card.ts +202 -0
  459. package/src/ui/interrupts/approval_styles.ts +52 -0
  460. package/src/ui/interrupts/confirmation_card.ts +138 -0
  461. package/src/ui/interrupts/confirmation_styles.ts +114 -0
  462. package/src/ui/interrupts/pending_decision.ts +33 -0
  463. package/src/ui/interrupts/question_card.ts +213 -0
  464. package/src/ui/interrupts/question_styles.ts +73 -0
  465. package/src/ui/place/place.ts +16 -0
  466. package/src/ui/place/place_chip.ts +146 -0
  467. package/src/ui/place/place_from_route.ts +27 -0
  468. package/src/ui/place/place_styles.ts +108 -0
  469. package/src/ui/placement/clamp_launcher.ts +38 -0
  470. package/src/ui/placement/clamp_panel.ts +30 -0
  471. package/src/ui/placement/collapse_styles.ts +472 -0
  472. package/src/ui/placement/dock_styles.ts +116 -0
  473. package/src/ui/placement/embedded_styles.ts +14 -0
  474. package/src/ui/placement/launcher_drag.ts +206 -0
  475. package/src/ui/placement/launcher_placement.ts +133 -0
  476. package/src/ui/placement/launcher_styles.ts +189 -0
  477. package/src/ui/placement/page_styles.ts +47 -0
  478. package/src/ui/placement/panel_drag.ts +138 -0
  479. package/src/ui/placement/panel_placement.ts +1975 -0
  480. package/src/ui/placement/place_widget.ts +64 -0
  481. package/src/ui/placement/preset_styles.ts +49 -0
  482. package/src/ui/placement/resize_handle.ts +229 -0
  483. package/src/ui/placement/resize_styles.ts +201 -0
  484. package/src/ui/placement/sidebar_styles.ts +113 -0
  485. package/src/ui/placement/small_viewport_styles.ts +70 -0
  486. package/src/ui/placement/surface_for.ts +183 -0
  487. package/src/ui/progress/prettify_tool_name.ts +16 -0
  488. package/src/ui/progress/run_announcer.ts +67 -0
  489. package/src/ui/progress/run_notice.ts +61 -0
  490. package/src/ui/progress/run_notice_styles.ts +52 -0
  491. package/src/ui/progress/subagent_panel.ts +227 -0
  492. package/src/ui/progress/subagent_progress.ts +200 -0
  493. package/src/ui/progress/subagent_update.ts +89 -0
  494. package/src/ui/progress/thoughts_block.ts +165 -0
  495. package/src/ui/progress/thoughts_styles.ts +74 -0
  496. package/src/ui/progress/tool_call_card.ts +429 -0
  497. package/src/ui/progress/tool_group_styles.ts +313 -0
  498. package/src/ui/progress/tool_step_group.ts +317 -0
  499. package/src/ui/progress/tool_step_styles.ts +486 -0
  500. package/src/ui/shell/adopt_styles.ts +34 -0
  501. package/src/ui/shell/build_header.ts +121 -0
  502. package/src/ui/shell/build_launcher.ts +79 -0
  503. package/src/ui/shell/collapse_glyph.ts +42 -0
  504. package/src/ui/shell/frame_styles.ts +147 -0
  505. package/src/ui/shell/glyph_slot.ts +12 -0
  506. package/src/ui/shell/header_button.ts +22 -0
  507. package/src/ui/shell/icon_element.ts +33 -0
  508. package/src/ui/shell/is_unread_badge_enabled.ts +11 -0
  509. package/src/ui/shell/read_launcher_icon_url.ts +9 -0
  510. package/src/ui/shell/theme_styles.ts +11 -0
  511. package/src/ui/shell/token_styles.ts +480 -0
  512. package/src/ui/styles.ts +110 -0
  513. package/src/ui/themes/classic_styles.ts +32 -0
  514. package/src/ui/themes/classic_tokens.ts +51 -0
  515. package/src/ui/themes/code_tokens.ts +21 -0
  516. package/src/ui/themes/ledger_styles.ts +169 -0
  517. package/src/ui/themes/ledger_tokens.ts +33 -0
  518. package/src/ui/themes/modern_styles.ts +80 -0
  519. package/src/ui/themes/quiet_styles.ts +118 -0
  520. package/src/ui/themes/scheme_styles.ts +26 -0
  521. package/src/ui/themes/soft_styles.ts +149 -0
  522. package/src/ui/themes/soft_tokens.ts +29 -0
  523. package/src/ui/themes/theme_hosts.ts +14 -0
  524. package/src/ui/transcript/answer_actions.ts +165 -0
  525. package/src/ui/transcript/answer_stream.ts +123 -0
  526. package/src/ui/transcript/attachment_chip_styles.ts +116 -0
  527. package/src/ui/transcript/attachment_chips.ts +82 -0
  528. package/src/ui/transcript/build_transcript.ts +67 -0
  529. package/src/ui/transcript/greeting_styles.ts +171 -0
  530. package/src/ui/transcript/groups_tool_steps.ts +16 -0
  531. package/src/ui/transcript/message_action_styles.ts +155 -0
  532. package/src/ui/transcript/message_actions.ts +234 -0
  533. package/src/ui/transcript/message_styles.ts +262 -0
  534. package/src/ui/transcript/render_markdown.ts +181 -0
  535. package/src/ui/transcript/render_or_warn.ts +22 -0
  536. package/src/ui/transcript/reveal_words.ts +41 -0
  537. package/src/ui/transcript/starter_chips.ts +42 -0
  538. package/src/ui/transcript/stick_to_bottom.ts +126 -0
  539. package/src/ui/transcript/suggestion_chips.ts +73 -0
  540. package/src/ui/transcript/transcript.ts +555 -0
  541. package/src/ui/transcript/transcript_styles.ts +153 -0
  542. package/src/ui/ui_strings.ts +550 -0
  543. package/src/version.ts +1 -0
@@ -0,0 +1,1028 @@
1
+ import {
2
+ type AbstractAgent,
3
+ type AgentSubscriber,
4
+ buildResumeArray,
5
+ type RunAgentParameters,
6
+ randomUUID,
7
+ } from "@ag-ui/client";
8
+ import {
9
+ type Context,
10
+ contentToText,
11
+ type Interrupt,
12
+ type Message,
13
+ type ResumeEntry,
14
+ type Tool,
15
+ } from "@ag-ui/core";
16
+ import { MAX_TOOL_ROUNDS, TOOL_OUTCOME } from "../constants.js";
17
+ import type { AttachmentRef } from "./attachment.js";
18
+ import type { ToolOutcome } from "./tool_outcome.js";
19
+ import { answerUnansweredCalls } from "./utils.js";
20
+
21
+ /** A tool call surfaced to the host by {@link AgUiClient}. */
22
+ export interface AgUiToolCall {
23
+ readonly id: string;
24
+ readonly name: string;
25
+ readonly args: Record<string, unknown>;
26
+ }
27
+
28
+ /**
29
+ * Which message an answer surfaced by {@link AgUiClient} is, as the stream
30
+ * named it.
31
+ */
32
+ export interface AgUiMessageIds {
33
+ /** The id the message streamed under, on its `TEXT_MESSAGE_START`/`END`. */
34
+ readonly messageId: string;
35
+ /**
36
+ * The run that streamed it, as that run's `RUN_STARTED` declared it.
37
+ *
38
+ * Absent when no `RUN_STARTED` was seen. `@ag-ui/client` 1.0 rejects a
39
+ * stream whose first event is anything else, so over a real agent this is
40
+ * always present; nothing else names the run the server recorded, so
41
+ * nothing is substituted when it is not.
42
+ *
43
+ * Text a sub-agent streams through the parent run carries the parent's run
44
+ * id: that is the stream it arrived on, and the delegation's
45
+ * `subagentRunId` names the child's work rather than a run the answer is
46
+ * recorded under.
47
+ */
48
+ readonly runId?: string;
49
+ }
50
+
51
+ /** The result of executing a frontend tool, destined for a tool-result message. */
52
+ export interface ToolExecution {
53
+ /** String content for the AG-UI tool-result message. */
54
+ content: string;
55
+ /** Present when the handler failed; surfaced for logging. */
56
+ error?: string;
57
+ /**
58
+ * How the call ended, in the same vocabulary a server states on
59
+ * `TOOL_CALL_RESULT`. Omit for a success -- absent *is* success, here for the
60
+ * same reason it is on the wire.
61
+ *
62
+ * Distinct from {@link error}, which is a *message* and only exists for the
63
+ * one failure shape that produces one. A refusal has no error and is still
64
+ * not a success, and that is the case this field exists for.
65
+ */
66
+ outcome?: ToolOutcome;
67
+ /**
68
+ * When `true`, a navigating tool triggered a page reload. The loop stops
69
+ * without appending a result — the result is supplied after the next mount
70
+ * (see the resume path). Mutually exclusive with a usable `content`.
71
+ */
72
+ halt?: boolean;
73
+ }
74
+
75
+ /**
76
+ * Executes a frontend tool call.
77
+ *
78
+ * Returns the {@link ToolExecution} to post back to the agent, or `null` when
79
+ * there is nothing to post: the call is not a frontend tool the host owns (a
80
+ * server-side tool the server already executed, which the client must not
81
+ * re-run), or its run was stopped or handed over while the call waited. A
82
+ * stopped call is left unanswered here, like the calls after it in the round,
83
+ * and the next request answers it as not finished.
84
+ */
85
+ export type ExecuteTool = (call: AgUiToolCall) => Promise<ToolExecution | null>;
86
+
87
+ /**
88
+ * One user decision for a server-side-tool approval interrupt. Structurally
89
+ * matches `@ag-ui/client`'s (non-exported) `ResumeResponse`, the payload
90
+ * {@link buildResumeArray} turns into a `ResumeEntry`: `resolved` approves (with
91
+ * an optional `payload`, e.g. `{ approved: true }`), `cancelled` denies.
92
+ */
93
+ export type InterruptResponse = { status: "resolved"; payload?: unknown } | { status: "cancelled" };
94
+
95
+ /**
96
+ * Resolves the approval interrupts a run finished on, keyed by interrupt id.
97
+ *
98
+ * A gated server-side tool defers instead of executing, so the run finishes on
99
+ * an interrupt outcome; the host collects one decision per interrupt and the
100
+ * loop resumes with the answers. Omit for agents that never gate server-side
101
+ * tools — an unresolved interrupt then ends the loop.
102
+ */
103
+ export type ResolveInterrupts = (
104
+ interrupts: readonly Interrupt[],
105
+ ) => Promise<Record<string, InterruptResponse>>;
106
+
107
+ /**
108
+ * Callbacks the {@link AgUiClient} invokes as a run progresses. The host
109
+ * (the `<ag-ui-chat>` element) implements these to render streaming text and
110
+ * tool activity into the chat.
111
+ */
112
+ export interface AgUiClientHandlers {
113
+ onRunStart(): void;
114
+ /** Fired on every streamed token; `buffer` is the full text so far. */
115
+ onTextDelta(buffer: string): void;
116
+ /**
117
+ * Fired when the assistant message completes; `buffer` is the final text,
118
+ * and `message` names it as the stream did.
119
+ */
120
+ onTextEnd(buffer: string, message: AgUiMessageIds): void;
121
+ /** Fired when the agent finishes calling a tool (server- or frontend-side). */
122
+ onToolCall(call: AgUiToolCall): void;
123
+ /**
124
+ * Fired when a server-side tool's result streams back (AG-UI's
125
+ * `TOOL_CALL_RESULT`). Frontend tools don't emit this — the client supplies
126
+ * their result itself — so this is the channel for server-executed output.
127
+ *
128
+ * `outcome` is the `outcome` key of the event's `metadata`, forwarded raw.
129
+ * It is `unknown` rather than {@link ToolOutcome} because metadata is open by
130
+ * key: the protocol declares the object and validates nothing inside it, so
131
+ * neither can this signature honestly claim to. Read it with
132
+ * `toolStatusFromOutcome`, which treats `undefined` and anything unrecognised
133
+ * as a success.
134
+ *
135
+ * Only the metadata is read. A server written against `@ag-ui/client` 0.x
136
+ * states the outcome at the top level of the event, and 1.0 strips that key
137
+ * before any subscriber runs, so from such a server this is `undefined` and
138
+ * the card settles as done.
139
+ */
140
+ onToolResult(toolCallId: string, content: string, outcome?: unknown): void;
141
+ /**
142
+ * Fired for AG-UI activity events — ambient notices about what the *run* did,
143
+ * as opposed to work the agent asked for. `django-ag-ui` emits one with
144
+ * `activityType: "compaction"` when it condensed the history.
145
+ */
146
+ onActivity(activityType: string, content: unknown, messageId: string): void;
147
+ /**
148
+ * An activity's content changed in place — a snapshot re-sent under the same
149
+ * `messageId` with `replace`, or an `ACTIVITY_DELTA` whose JSON patch
150
+ * `@ag-ui/client` has already applied.
151
+ *
152
+ * Reported after the client has updated its own message, so `content` is the
153
+ * result rather than the instruction. That is the whole reason this is a
154
+ * separate callback: the raw delta event fires *before* the patch lands, and
155
+ * a subscriber acting on it would redraw from stale content.
156
+ */
157
+ onActivityChanged(messageId: string, activityType: string, content: unknown): void;
158
+ /** Fired when a reasoning model starts emitting its chain-of-thought. */
159
+ onReasoningStart(): void;
160
+ /**
161
+ * Fired on every reasoning token, and once more when the block ends.
162
+ *
163
+ * ``buffer`` is the text accumulated *before* the token that triggered the
164
+ * call, which is what the protocol client passes -- so the stream trails by
165
+ * one delta and the final call, at the end of the block, is what completes
166
+ * it. Render the buffer wholesale rather than appending it.
167
+ */
168
+ onReasoningDelta(buffer: string): void;
169
+ /** Fired when the reasoning block ends (before the answer text streams). */
170
+ onReasoningEnd(): void;
171
+ onRunEnd(): void;
172
+ /**
173
+ * The server replaced the whole conversation with `MESSAGES_SNAPSHOT`.
174
+ *
175
+ * `@ag-ui/client` applies the event before any subscriber sees it, so by the
176
+ * time this fires `agent.messages` **is** the server's list -- and the run
177
+ * loop persists `agent.messages`. The replacement therefore reaches the
178
+ * conversation store whatever the host does; this hook exists so the host can
179
+ * stop that being invisible.
180
+ */
181
+ onMessagesSnapshot(messages: readonly Message[]): void;
182
+ /**
183
+ * The agent sent a `CUSTOM` event.
184
+ *
185
+ * Forwarded whole and uninterpreted: `name` is an open string the protocol
186
+ * does not enumerate, so a client that decided which names were legal would
187
+ * be the thing the open field exists to avoid.
188
+ */
189
+ onCustomEvent(name: string, value: unknown): void;
190
+ /**
191
+ * A delegated sub-agent started, inside the run.
192
+ *
193
+ * The protocol's own event, unlike the steps that follow it on
194
+ * {@link SUBAGENT_CUSTOM_NAME}. `parentToolCallId` links the delegation to the
195
+ * `delegate_task` call that spawned it -- the field AG-UI provides for the
196
+ * "agents as tools" shape -- and is `null` when the server did not send one,
197
+ * which a host reads as a delegation it has nothing to attach to.
198
+ *
199
+ * `subagentRunId` names the child run and is the only id the two closing
200
+ * events carry, so a host that wants to close what it opened has to remember
201
+ * the pairing.
202
+ */
203
+ onSubAgentStarted(subagentRunId: string, name: string, parentToolCallId: string | null): void;
204
+ /** The delegation named by `subagentRunId` completed. */
205
+ onSubAgentFinished(subagentRunId: string): void;
206
+ /**
207
+ * The delegation named by `subagentRunId` failed.
208
+ *
209
+ * `message` is server text and required by the protocol, but it is not an
210
+ * exception's words: `django-ag-ui` sends only which sub-agent failed, on the
211
+ * same reasoning that redacts a `RUN_ERROR`. Render it as text, never as
212
+ * markup.
213
+ */
214
+ onSubAgentError(subagentRunId: string, message: string): void;
215
+ onError(message: string): void;
216
+ /**
217
+ * Fired when the user cancelled the run ({@link AgUiClient.cancel}) — the
218
+ * deliberate-stop sibling of `onError`. Partial assistant text already
219
+ * streamed stays valid, so the host keeps the bubble and shows a stopped
220
+ * affordance rather than an error. `onSettled` still follows.
221
+ */
222
+ onCancelled(): void;
223
+ /**
224
+ * Fired exactly once when the interaction settles, however the run loop ended
225
+ * — server-only round, rounds exhausted, cancellation, or error. The terminal
226
+ * guarantee that the UI returns to rest.
227
+ */
228
+ onSettled(): void;
229
+ }
230
+
231
+ /**
232
+ * Provider of the per-run frontend tool catalog and context. Both are read
233
+ * fresh on every {@link AgUiClient.send} so the catalog reflects the current
234
+ * page state.
235
+ */
236
+ export interface AgUiRunInputs {
237
+ getTools?: () => Tool[];
238
+ getContext?: () => Context[];
239
+ }
240
+
241
+ export interface AgUiClientConfig extends AgUiRunInputs {
242
+ /** The AG-UI agent to drive. Injected so it can be faked in tests. */
243
+ agent: AbstractAgent;
244
+ handlers: AgUiClientHandlers;
245
+ /** Executes frontend tool calls. Omit for server-only tool sets. */
246
+ executeTool?: ExecuteTool;
247
+ /**
248
+ * Resolves server-side-tool approval interrupts. Omit when no server-side
249
+ * tool is gated for approval — an interrupt then ends the loop unanswered.
250
+ */
251
+ resolveInterrupts?: ResolveInterrupts;
252
+ /**
253
+ * Invoked with the latest history whenever it changes, so the host can
254
+ * persist it for durability across page reloads. Omit to keep the
255
+ * conversation in-memory only.
256
+ */
257
+ onPersist?: (messages: readonly Message[]) => void;
258
+ /**
259
+ * Called whenever AG-UI shared state changes — a streamed `STATE_SNAPSHOT` /
260
+ * `STATE_DELTA`, or {@link AgUiClient.setState}. `@ag-ui/client` owns applying
261
+ * those events; this only forwards the result.
262
+ */
263
+ onStateChanged?: (state: Readonly<Record<string, unknown>>) => void;
264
+ /**
265
+ * Error text for {@link AgUiClientHandlers.onError} when a run's stream closes
266
+ * without a terminal AG-UI event. Defaults to `"Connection lost"`; the host
267
+ * passes its localized string.
268
+ */
269
+ connectionLostMessage?: string;
270
+ /**
271
+ * Result content for a tool call the conversation moved past without one: a
272
+ * Stop while the stream was arriving, a round that ended on `RUN_ERROR`, a
273
+ * call no {@link executeTool} answered, or history restored with a call still
274
+ * open. Sent to the agent, so it says only that the call did not finish --
275
+ * nobody refused it. Defaults to an English sentence; the host passes its
276
+ * localized string.
277
+ */
278
+ unfinishedMessage?: string;
279
+ /**
280
+ * Result content for a server-side approval that Stop closed while it was
281
+ * open, which the host's {@link resolveInterrupts} answers as declined.
282
+ * Defaults to `"User declined the action."`; the host passes its localized
283
+ * string.
284
+ */
285
+ declinedMessage?: string;
286
+ /**
287
+ * Upper bound on frontend tool-call to re-run rounds within one
288
+ * {@link AgUiClient.send}. Defaults to {@link MAX_TOOL_ROUNDS}.
289
+ *
290
+ * The default suits a chat whose tools answer questions. A page-driving
291
+ * deployment reaches it legitimately -- filling a form field by field is one
292
+ * round each -- and the symptom is not an error but an answer that stops
293
+ * mid-task, which reads as the model giving up. Raise it where a turn is
294
+ * expected to take many small steps.
295
+ *
296
+ * Read once, when the client is built. The bound is a property of the
297
+ * deployment rather than of a run, so re-reading it per round would only make
298
+ * a mid-run change possible, and a mid-run change to how long the run may
299
+ * last is not a thing a host has any way to reason about.
300
+ */
301
+ maxToolRounds?: number;
302
+ }
303
+
304
+ /**
305
+ * Raised when a run's stream closes cleanly at the transport level but never
306
+ * emits a terminal AG-UI event, so the run neither finished nor errored. Routed
307
+ * to {@link AgUiClientHandlers.onError} (it is not an abort), turning a silent
308
+ * "stuck pending" into a visible "connection lost".
309
+ */
310
+ export class ConnectionLostError extends Error {
311
+ constructor(message: string) {
312
+ super(message);
313
+ this.name = "ConnectionLostError";
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Thin orchestration layer over an AG-UI {@link AbstractAgent}.
319
+ *
320
+ * Translates AG-UI's subscriber callbacks into the host's
321
+ * {@link AgUiClientHandlers}, and appends the user message + current frontend
322
+ * tool catalog + context to each run.
323
+ */
324
+ export class AgUiClient {
325
+ readonly #agent: AbstractAgent;
326
+ readonly #handlers: AgUiClientHandlers;
327
+ readonly #getTools: () => Tool[];
328
+ readonly #getContext: () => Context[];
329
+ readonly #executeTool: ExecuteTool | null;
330
+ readonly #resolveInterrupts: ResolveInterrupts | null;
331
+ readonly #onPersist: (messages: readonly Message[]) => void;
332
+ /**
333
+ * Message ids the server has already closed, so a reuse can be reported. Per
334
+ * client rather than per run — the merge happens across runs, which a per-run
335
+ * set would miss entirely.
336
+ */
337
+ readonly #closedMessageIds = new Set<string>();
338
+ readonly #connectionLostMessage: string;
339
+ readonly #unfinishedMessage: string;
340
+ readonly #declinedMessage: string;
341
+ readonly #maxToolRounds: number;
342
+ // Set by cancel(); reset as send() or resume() begins, before either runs
343
+ // any host code. Checked by the loop so a cancel between frontend-tool
344
+ // rounds doesn't start another round, and again just before each request,
345
+ // so a cancel from host code the round itself runs is not sent anyway.
346
+ #cancelled = false;
347
+
348
+ constructor(config: AgUiClientConfig) {
349
+ this.#agent = config.agent;
350
+ this.#handlers = config.handlers;
351
+ this.#getTools = config.getTools ?? (() => []);
352
+ this.#getContext = config.getContext ?? (() => []);
353
+ this.#executeTool = config.executeTool ?? null;
354
+ this.#resolveInterrupts = config.resolveInterrupts ?? null;
355
+ this.#onPersist = config.onPersist ?? (() => {});
356
+ this.#connectionLostMessage = config.connectionLostMessage ?? "Connection lost";
357
+ this.#unfinishedMessage =
358
+ config.unfinishedMessage ??
359
+ "Not finished: the run ended or moved on before this tool call returned a result.";
360
+ this.#declinedMessage = config.declinedMessage ?? "User declined the action.";
361
+ // Validated here rather than at each caller, so the element's attribute and
362
+ // a direct consumer get the same answer. A bound below one -- or a NaN from
363
+ // an unparseable attribute -- is not a smaller budget but a send that runs
364
+ // the agent zero times, which would look exactly like a broken endpoint.
365
+ const rounds = config.maxToolRounds ?? MAX_TOOL_ROUNDS;
366
+ this.#maxToolRounds = rounds >= 1 ? Math.floor(rounds) : MAX_TOOL_ROUNDS;
367
+ this.#moveSeededKeysIntoMetadata();
368
+ const onStateChanged = config.onStateChanged;
369
+ if (onStateChanged !== undefined) {
370
+ // The agent applies STATE_SNAPSHOT / STATE_DELTA itself; subscribing is
371
+ // how the result is learned rather than re-derived from the event stream.
372
+ // Lives for the agent's lifetime, which is this client's.
373
+ this.#agent.subscribe({
374
+ onStateChanged: ({ state }) => {
375
+ onStateChanged(state as Record<string, unknown>);
376
+ },
377
+ });
378
+ }
379
+ }
380
+
381
+ /** The current AG-UI shared state, as the agent last applied it. */
382
+ get state(): Readonly<Record<string, unknown>> {
383
+ return this.#agent.state as Record<string, unknown>;
384
+ }
385
+
386
+ /** Replace the shared state; the next run sends it as `RunAgentInput.state`. */
387
+ setState(state: Readonly<Record<string, unknown>>): void {
388
+ this.#agent.setState({ ...state });
389
+ }
390
+
391
+ /** Whether a run is currently in flight. */
392
+ get running(): boolean {
393
+ return this.#agent.isRunning;
394
+ }
395
+
396
+ /** The current conversation history (for persistence / rehydration). */
397
+ get messages(): readonly Message[] {
398
+ return this.#agent.messages;
399
+ }
400
+
401
+ /**
402
+ * Append a user message and run the agent to completion.
403
+ *
404
+ * When the agent calls frontend tools, this executes them and re-runs the
405
+ * agent with the results, looping until the agent stops calling frontend
406
+ * tools (bounded by {@link AgUiClientConfig.maxToolRounds}, which defaults to
407
+ * {@link MAX_TOOL_ROUNDS}).
408
+ *
409
+ * `attachments` ride in the user message's `metadata`, under
410
+ * `attachments`, which is how the server learns which files the agent can
411
+ * read and how the default store round-trips them for history replay; see
412
+ * {@link messageAttachments}.
413
+ */
414
+ async send(content: string, attachments: readonly AttachmentRef[] = []): Promise<void> {
415
+ // A new interaction, so the Stop of the one before it is forgotten here,
416
+ // ahead of the save below: that save is the host's store, and a Stop it
417
+ // makes (a new chat started from the save) has to stop this turn.
418
+ this.#cancelled = false;
419
+ // In `metadata` because it is the one field on a message `@ag-ui/client`
420
+ // declares open by key. 1.0 strips every undeclared key from the input it
421
+ // sends, so refs at the top level of the message are removed with a
422
+ // console warning and the server is never told a file was attached. No metadata at all
423
+ // without refs: an empty object would put a field on every plain message that says nothing.
424
+ const message: Message = {
425
+ id: randomUUID(),
426
+ role: "user",
427
+ content,
428
+ ...(attachments.length > 0 ? { metadata: { attachments } } : {}),
429
+ };
430
+ this.#agent.addMessage(message);
431
+ this.#persist();
432
+ await this.#run();
433
+ }
434
+
435
+ /**
436
+ * Drop everything after the most recent user message, so the same question
437
+ * can be asked again.
438
+ *
439
+ * Returns the retained history, or `null` when there is nothing to retry (no
440
+ * user message has been sent yet). The shortened history is saved before it
441
+ * replaces the current one, so a save that throws changes nothing and the
442
+ * throw propagates.
443
+ *
444
+ * **Truncates only** -- the caller re-renders from the returned list and then
445
+ * calls {@link resume}, because the transcript belongs to the element and a
446
+ * client that reached into it would own two things. Running here instead
447
+ * would stream the new answer in underneath the old one.
448
+ *
449
+ * Re-running answers the question the agent was last asked, rather than
450
+ * telling it its answer was wrong, which is what makes the result a
451
+ * *different* answer instead of a conversation about the previous one.
452
+ *
453
+ * **A retried turn re-runs its tools.** For a page-driving agent that is not
454
+ * neutral: the previous attempt already clicked what it clicked, and this
455
+ * does not undo it.
456
+ */
457
+ truncateToLastUser(): readonly Message[] | null {
458
+ const messages = [...this.#agent.messages];
459
+ // Forward, keeping the last match, rather than a reverse scan with an
460
+ // index lookup: `noUncheckedIndexedAccess` makes the latter reach for an
461
+ // optional chain whose null arm cannot happen and cannot be covered.
462
+ let lastUser = -1;
463
+ for (const [index, message] of messages.entries()) {
464
+ if (message.role === "user") {
465
+ lastUser = index;
466
+ }
467
+ }
468
+ if (lastUser === -1) {
469
+ return null;
470
+ }
471
+ const kept = messages.slice(0, lastUser + 1);
472
+ // Saved before the history is shortened, so a store that throws leaves it
473
+ // as it was and the throw reaches the caller, as a refused save does from
474
+ // send(), rather than leaving this client without an answer the caller's
475
+ // transcript still shows.
476
+ // Handed over directly rather than through #persist, which saves the list
477
+ // the agent holds, because the agent does not hold this one yet.
478
+ this.#onPersist(kept);
479
+ this.#agent.setMessages(kept);
480
+ return kept;
481
+ }
482
+
483
+ /**
484
+ * Resume the run loop after a navigating tool's result was supplied
485
+ * post-reload (via {@link addToolResult}). Unlike {@link send}, adds no user
486
+ * message; it continues the conversation already in history.
487
+ */
488
+ async resume(): Promise<void> {
489
+ // A new interaction, as in send(); no host code runs between here and the loop.
490
+ this.#cancelled = false;
491
+ await this.#run();
492
+ }
493
+
494
+ /** Append a frontend tool result to history (used by the resume path). */
495
+ addToolResult(toolCallId: string, content: string): void {
496
+ this.#agent.addMessage({ id: randomUUID(), role: "tool", content, toolCallId });
497
+ this.#persist();
498
+ }
499
+
500
+ /**
501
+ * Cancel the in-flight run (AG-UI has no server cancel route — aborting the
502
+ * streaming request is the protocol's cancel; the server observes the
503
+ * disconnect). Safe to call with no run in flight. Partial text already
504
+ * streamed stays in history; {@link AgUiClientHandlers.onCancelled} fires
505
+ * instead of `onError`, and `onSettled` still follows.
506
+ *
507
+ * Also stops a {@link send} or {@link resume} whose request has not been made
508
+ * yet, when host code it runs on the way calls this: the save of the turn,
509
+ * the tool and context providers, the save answering a call left open. No
510
+ * request is made then, and `onCancelled` and `onSettled` fire as for any
511
+ * other Stop. A cancel with nothing in flight is forgotten when the next
512
+ * send or resume begins.
513
+ */
514
+ cancel(): void {
515
+ this.#cancelled = true;
516
+ this.#agent.abortRun();
517
+ }
518
+
519
+ async #run(): Promise<void> {
520
+ try {
521
+ await this.#runLoop();
522
+ // `@ag-ui/client` filters abort errors inside `runAgent` (it resolves
523
+ // rather than rejects on a cancelled fetch), so a cancelled run usually
524
+ // ends here, not in the catch.
525
+ if (this.#cancelled) {
526
+ this.#onCancelled();
527
+ }
528
+ } catch (error) {
529
+ if (this.#cancelled || isAbortError(error)) {
530
+ this.#onCancelled();
531
+ } else {
532
+ this.#handlers.onError(error instanceof Error ? error.message : String(error));
533
+ }
534
+ } finally {
535
+ this.#handlers.onSettled();
536
+ }
537
+ }
538
+
539
+ #onCancelled(): void {
540
+ // Persist so the truncated exchange, partial assistant text included,
541
+ // survives a reload.
542
+ this.#persist();
543
+ this.#handlers.onCancelled();
544
+ }
545
+
546
+ /**
547
+ * Move the keys a history stored by an earlier release carries at the top
548
+ * level of its messages into their `metadata`, where this release writes them.
549
+ *
550
+ * Earlier releases wrote a tool message's `outcome` and a user message's
551
+ * `attachments` beside the protocol's own fields. `@ag-ui/client` 1.0 strips
552
+ * every undeclared key from the input it sends, so a restored conversation
553
+ * sent as it was stored would lose both on its first request: the server
554
+ * would no longer be told about any file attached before the upgrade, and
555
+ * each strip would print a warning. Moved rather than dropped, so the next
556
+ * save writes the conversation in the shape this release reads first and a
557
+ * reload after it still replays a declined card as declined.
558
+ *
559
+ * Merged into the metadata a message already has, with a key already there
560
+ * winning: a message carrying both was last written by the newer writer.
561
+ * `ag_ui_chat_restored_outcome_wire.test.ts` reads the result off the body of
562
+ * a real request. The check before `setMessages` only spares a copy when
563
+ * nothing moves, which is every restore of a store this release wrote;
564
+ * nothing observable depends on it.
565
+ */
566
+ #moveSeededKeysIntoMetadata(): void {
567
+ const seeded = this.#agent.messages;
568
+ const moved = seeded.map(withTopLevelKeysInMetadata);
569
+ if (moved.some((message, index) => message !== seeded[index])) {
570
+ this.#agent.setMessages(moved);
571
+ }
572
+ }
573
+
574
+ /**
575
+ * Give every tool call in history a result, before a request carries it.
576
+ *
577
+ * Called ahead of every `runAgent`, which makes it the one place the rule is
578
+ * kept for every way a call can be left open -- a live run's Stop or
579
+ * `RUN_ERROR`, a name no tool here owns, a server that sent no result -- and
580
+ * for a client seeded with a restored history just the same. The answer says
581
+ * the call did not finish rather than that anyone declined it: a person
582
+ * declined only where they were asked, and those answers are already here by
583
+ * the time this runs (see {@link #answerStoppedApprovals}).
584
+ *
585
+ * `resuming` names the calls the request is itself answering: a resumed
586
+ * approval runs or denies them server-side, and a result here would answer
587
+ * them twice.
588
+ *
589
+ * The outcome rides in the result's `metadata`, where the client folds a
590
+ * server's own, so the next request carries it and a reload replays it. A
591
+ * server that does not read it ignores it: `metadata` is a declared field.
592
+ */
593
+ #answerUnansweredCalls(resuming: ReadonlySet<string>): void {
594
+ const messages = this.#agent.messages;
595
+ const answered = answerUnansweredCalls(
596
+ messages,
597
+ (toolCallId) => ({
598
+ id: randomUUID(),
599
+ role: "tool",
600
+ content: this.#unfinishedMessage,
601
+ toolCallId,
602
+ metadata: { outcome: TOOL_OUTCOME.INTERRUPTED },
603
+ }),
604
+ resuming,
605
+ );
606
+ if (answered !== messages) {
607
+ this.#agent.setMessages([...answered]);
608
+ this.#persist();
609
+ }
610
+ }
611
+
612
+ /**
613
+ * Answer, as declined, every approval that Stop closed as declined.
614
+ *
615
+ * Stop cancels the resume that would have told the server, so the decline the
616
+ * approval recorded reaches no one unless history carries it. Only calls the
617
+ * host's resolver answered `cancelled` are declined: a person who approved a
618
+ * call and then pressed Stop did not decline it, and
619
+ * {@link #answerUnansweredCalls} answers that one on the next request as not
620
+ * finished.
621
+ *
622
+ * The status check is held by "does not call an approved call declined, since
623
+ * nobody declined it" in `ag_ui_chat_unanswered_tool_calls.test.ts`; the
624
+ * `toolCallId` check is held by the type checker, since a result needs an id
625
+ * to answer.
626
+ */
627
+ #answerStoppedApprovals(
628
+ interrupts: readonly Interrupt[],
629
+ responses: Readonly<Record<string, InterruptResponse>>,
630
+ ): void {
631
+ for (const interrupt of interrupts) {
632
+ const toolCallId = interrupt.toolCallId;
633
+ if (toolCallId !== undefined && responses[interrupt.id]?.status === "cancelled") {
634
+ this.#agent.addMessage({
635
+ id: randomUUID(),
636
+ role: "tool",
637
+ content: this.#declinedMessage,
638
+ toolCallId,
639
+ metadata: { outcome: TOOL_OUTCOME.DENIED },
640
+ });
641
+ }
642
+ }
643
+ this.#persist();
644
+ }
645
+
646
+ /**
647
+ * Hand the transcript to the host's store.
648
+ *
649
+ * The list as the agent holds it, with nothing added: how each tool call
650
+ * ended rides in its result's `metadata`, which the client folds in from a
651
+ * server's `TOOL_CALL_RESULT` and this class writes for the results it makes
652
+ * itself, so the store sees exactly what the next request sends.
653
+ */
654
+ #persist(): void {
655
+ this.#onPersist(this.#agent.messages);
656
+ }
657
+
658
+ async #runLoop(): Promise<void> {
659
+ // Carries resolved approval answers into the next run when a round finished
660
+ // on a server-side-tool interrupt. Distinct from the public resume(), which
661
+ // continues an unfinished frontend-tool round after a page load; this stays
662
+ // inside one #run().
663
+ let resume: ResumeEntry[] | undefined;
664
+ let resuming: ReadonlySet<string> = new Set();
665
+ for (let round = 0; round < this.#maxToolRounds; round += 1) {
666
+ // A cancel during the previous round's tool execution lands here: the
667
+ // running handler completed, but no further round starts. The check
668
+ // before the request below would stop the round as well; this one keeps
669
+ // a stopped loop from running the providers and the store for a round
670
+ // it will not send.
671
+ if (this.#cancelled) {
672
+ return;
673
+ }
674
+ const pending: AgUiToolCall[] = [];
675
+ const runState: RunState = { terminal: false, errored: false, interrupts: [] };
676
+ const params: RunAgentParameters = {
677
+ tools: this.#getTools(),
678
+ context: this.#getContext(),
679
+ };
680
+ if (resume !== undefined) {
681
+ params.resume = resume;
682
+ }
683
+ this.#answerUnansweredCalls(resuming);
684
+ // Everything above since the check at the top is host code -- the tool
685
+ // and context providers, and the store saving the answers just made -- and
686
+ // any of it can be what stopped the run, by starting a new chat or
687
+ // changing the principal. `@ag-ui/client` makes a fresh abort controller
688
+ // for each run, so the abort `cancel()` made in that time reached
689
+ // nothing, and only this check keeps the request from going out.
690
+ if (this.#cancelled) {
691
+ return;
692
+ }
693
+ await this.#agent.runAgent(params, this.#buildSubscriber(pending, runState));
694
+ resume = undefined;
695
+ resuming = new Set();
696
+ this.#persist();
697
+ // Cancelled mid-stream: don't execute the tool calls collected before the
698
+ // abort.
699
+ if (this.#cancelled) {
700
+ return;
701
+ }
702
+ // The stream resolved without RUN_FINISHED / RUN_ERROR, so the transport
703
+ // dropped mid-run. Surface it as an error (via #run) rather than resting
704
+ // silently with a stuck pending indicator.
705
+ if (!runState.terminal) {
706
+ throw new ConnectionLostError(this.#connectionLostMessage);
707
+ }
708
+ // RUN_ERROR is terminal and the agent already reported it via onError.
709
+ // Executing the calls collected before it, or starting another round,
710
+ // would run into a broken context and raise a confusing second error.
711
+ // Pending tool cards are swept at onSettled.
712
+ if (runState.errored) {
713
+ return;
714
+ }
715
+ // A gated server-side tool deferred, so the run finished on an interrupt
716
+ // outcome: resolve each interrupt, then re-enter carrying the answers so
717
+ // the follow-up run executes or denies the tool. Must precede the
718
+ // frontend-tool sweep below — a server-side tool isn't ours to run.
719
+ if (runState.interrupts.length > 0) {
720
+ if (this.#resolveInterrupts === null) {
721
+ return;
722
+ }
723
+ const responses = await this.#resolveInterrupts(runState.interrupts);
724
+ if (this.#cancelled) {
725
+ this.#answerStoppedApprovals(runState.interrupts, responses);
726
+ return;
727
+ }
728
+ resume = buildResumeArray(runState.interrupts, responses);
729
+ resuming = new Set(runState.interrupts.flatMap(({ toolCallId }) => toolCallId ?? []));
730
+ continue;
731
+ }
732
+ if (this.#executeTool === null || pending.length === 0) {
733
+ return;
734
+ }
735
+ let executed = false;
736
+ for (const call of pending) {
737
+ // A cancel while an earlier call in this round ran lands here. That
738
+ // handler could not be aborted and its result is kept, but the calls
739
+ // after it have not started, and a person who pressed Stop during the
740
+ // first page action did not ask for the second. They are answered as not
741
+ // finished before the next request.
742
+ if (this.#cancelled) {
743
+ return;
744
+ }
745
+ const result = await this.#executeTool(call);
746
+ if (result === null) {
747
+ continue;
748
+ }
749
+ if (result.halt === true) {
750
+ // A navigating tool reloaded the page; the result arrives on the
751
+ // next mount. Stop here rather than re-running into a dead context.
752
+ return;
753
+ }
754
+ // A frontend tool's refusal or failure is stated by no server, because
755
+ // no server ran the call, so the client writes it where the client
756
+ // folds a server's: the result's `metadata`. Written with the message,
757
+ // so the very first save of it already carries how it ended, and a
758
+ // reload reads the card off it. A success writes no metadata at all,
759
+ // as a server's success states none.
760
+ this.#agent.addMessage({
761
+ id: randomUUID(),
762
+ role: "tool",
763
+ content: result.content,
764
+ toolCallId: call.id,
765
+ ...(result.outcome === undefined ? {} : { metadata: { outcome: result.outcome } }),
766
+ });
767
+ this.#persist();
768
+ executed = true;
769
+ }
770
+ if (!executed) {
771
+ return;
772
+ }
773
+ }
774
+ }
775
+
776
+ #buildSubscriber(pending: AgUiToolCall[], runState: RunState): AgentSubscriber {
777
+ const h = this.#handlers;
778
+ const closed = this.#closedMessageIds;
779
+ // Read at event time, not captured now: the flag flips mid-run, and the
780
+ // subscriber is built before the run that a later `cancel()` stops.
781
+ const cancelled = (): boolean => this.#cancelled;
782
+ // Charts whose patch has been dispatched but not yet applied. Scoped to the
783
+ // subscriber, so it cannot outlive the run that created it.
784
+ const pendingDeltas = new Set<string>();
785
+ // The run this round's stream declared. Per subscriber, so per round: a
786
+ // turn that calls a tool is two runs, and its answer belongs to the second.
787
+ //
788
+ // Taken from `RUN_STARTED` rather than the `runId` the request carried.
789
+ // The client mints that one, and a server is free to record the run under
790
+ // the id it streams back; the event is the server's own statement of
791
+ // which run this is, so it is the id a host can find again.
792
+ let runId: string | undefined;
793
+ return {
794
+ onRunInitialized() {
795
+ h.onRunStart();
796
+ },
797
+ onRunStartedEvent({ event }) {
798
+ runId = event.runId;
799
+ },
800
+ onTextMessageStartEvent({ event }) {
801
+ // A reused message id merges two answers into one persisted transcript
802
+ // entry. The protocol has no rule to enforce and refusing the event
803
+ // would be worse than the merge, so warn and continue — but do not stay
804
+ // silent, since the corruption outlives the session.
805
+ if (closed.has(event.messageId)) {
806
+ console.warn(
807
+ `<ag-ui-chat>: the server reused message id "${event.messageId}", which was ` +
808
+ "already closed. Its content will be appended to that earlier message rather " +
809
+ "than starting a new one, and the merged result is what gets persisted. " +
810
+ "Issue a fresh id per message.",
811
+ );
812
+ }
813
+ },
814
+ onTextMessageContentEvent({ textMessageBuffer }) {
815
+ h.onTextDelta(textMessageBuffer);
816
+ },
817
+ onTextMessageEndEvent({ event, textMessageBuffer }) {
818
+ closed.add(event.messageId);
819
+ h.onTextEnd(textMessageBuffer, {
820
+ messageId: event.messageId,
821
+ ...(runId === undefined ? {} : { runId }),
822
+ });
823
+ },
824
+ onToolCallEndEvent({ event, toolCallName, toolCallArgs }) {
825
+ const call: AgUiToolCall = {
826
+ id: event.toolCallId,
827
+ name: toolCallName,
828
+ args: toolCallArgs,
829
+ };
830
+ pending.push(call);
831
+ h.onToolCall(call);
832
+ },
833
+ onToolCallResultEvent({ event }) {
834
+ // In the event's `metadata`, the one place on it `@ag-ui/client` 1.0
835
+ // declares open by key. Its enforcement stage strips every undeclared
836
+ // key before a subscriber runs, so an `outcome` stated at the top level
837
+ // of the event never gets here.
838
+ //
839
+ // Nothing is recorded for the transcript: after this returns, the
840
+ // client builds the tool message and folds the event's metadata onto
841
+ // it, so the outcome is already on the message a save writes, word for
842
+ // word as the server said it. A reload reads it back through the same
843
+ // mapping as the live path, which is what keeps the two agreeing.
844
+ const outcome: unknown = event.metadata?.["outcome"];
845
+ // Content may arrive as parts since the protocol's 1.0, so a tool can
846
+ // return an image beside its text. A card shows text, so the text parts
847
+ // are joined and the rest left to the host store, which keeps the
848
+ // message whole; a plain string passes through unchanged.
849
+ h.onToolResult(event.toolCallId, contentToText(event.content), outcome);
850
+ },
851
+ onActivitySnapshotEvent({ event, messages }) {
852
+ // A snapshot for an id already in the list is a replacement, not a new
853
+ // activity: the client has swapped its content in place, and a second
854
+ // append would leave the superseded one on screen.
855
+ const known = messages.some(
856
+ (message) => message.id === event.messageId && message.role === "activity",
857
+ );
858
+ if (known) {
859
+ h.onActivityChanged(event.messageId, event.activityType, event.content);
860
+ return;
861
+ }
862
+ h.onActivity(event.activityType, event.content, event.messageId);
863
+ },
864
+ // Deliberately does *not* read the message here. `@ag-ui/client`
865
+ // dispatches this subscriber **before** applying the patch, so the
866
+ // message still holds its previous content: redrawing from it would leave
867
+ // the chart one revision behind for the life of the run, and disagreeing
868
+ // with what a reload shows. Note which chart moved and read the result on
869
+ // the change that follows.
870
+ onActivityDeltaEvent({ event }) {
871
+ pendingDeltas.add(event.messageId);
872
+ },
873
+ onCustomEvent({ event }) {
874
+ h.onCustomEvent(event.name, event.value);
875
+ },
876
+ // The delegation lifecycle. Forwarded rather than interpreted here, for
877
+ // the reason every other subscriber in this block is thin: this file
878
+ // adapts the protocol to the handler surface, and what a delegation looks
879
+ // like on screen is the element's business.
880
+ //
881
+ // `parentToolCallId` is optional on the wire and normalised to `null` so
882
+ // the handler has one absent-value to check rather than two.
883
+ onSubagentStartedEvent({ event }) {
884
+ h.onSubAgentStarted(event.subagentRunId, event.name, event.parentToolCallId ?? null);
885
+ },
886
+ onSubagentFinishedEvent({ event }) {
887
+ h.onSubAgentFinished(event.subagentRunId);
888
+ },
889
+ onSubagentErrorEvent({ event }) {
890
+ h.onSubAgentError(event.subagentRunId, event.message);
891
+ },
892
+ onMessagesSnapshotEvent({ event }) {
893
+ h.onMessagesSnapshot(event.messages as readonly Message[]);
894
+ },
895
+ // Emitted after the client has written the patched messages, which is the
896
+ // first moment the result exists. Only the ids marked above are looked at,
897
+ // so an ordinary text delta does not walk the transcript.
898
+ onMessagesChanged({ messages }) {
899
+ for (const id of pendingDeltas) {
900
+ const message = messages.find((entry) => entry.id === id);
901
+ if (message !== undefined && message.role === "activity") {
902
+ h.onActivityChanged(id, message.activityType, message.content);
903
+ }
904
+ }
905
+ pendingDeltas.clear();
906
+ },
907
+ // THINKING_* left the protocol in its 1.0, but `@ag-ui/client` still
908
+ // converts a server's THINKING_* events onto these REASONING_* callbacks,
909
+ // with a console warning, for a deprecation window it sets. Nothing in the
910
+ // family emits THINKING_*: pydantic-ai has sent REASONING_* at every
911
+ // protocol version django-ag-ui admits.
912
+ onReasoningStartEvent() {
913
+ h.onReasoningStart();
914
+ },
915
+ onReasoningMessageContentEvent({ reasoningMessageBuffer }) {
916
+ h.onReasoningDelta(reasoningMessageBuffer);
917
+ },
918
+ // The delta callback reports the buffer as it stood *before* the announced
919
+ // delta was appended, so on its own it always trails the stream by one and
920
+ // renders nothing at all for a block that arrives as a single delta. The
921
+ // answer text is spared that because its own end event carries the whole
922
+ // message; this is the reasoning counterpart, and it has to be
923
+ // REASONING_MESSAGE_END rather than REASONING_END, because only the former
924
+ // carries a buffer.
925
+ onReasoningMessageEndEvent({ reasoningMessageBuffer }) {
926
+ h.onReasoningDelta(reasoningMessageBuffer);
927
+ },
928
+ onReasoningEndEvent() {
929
+ h.onReasoningEnd();
930
+ },
931
+ onRunFinishedEvent(params) {
932
+ // RUN_FINISHED is terminal for both a normal finish and an interrupt.
933
+ // Capture the interrupts here rather than reading the agent's
934
+ // `pendingInterrupts` afterwards, to stay independent of that field's
935
+ // cross-run clearing semantics.
936
+ runState.terminal = true;
937
+ if (params.outcome === "interrupt") {
938
+ runState.interrupts = params.interrupts;
939
+ }
940
+ },
941
+ onRunErrorEvent({ event }) {
942
+ runState.terminal = true;
943
+ runState.errored = true;
944
+ // Cancelling aborts the response mid-read, and the browser's own words
945
+ // for that can arrive here as a RUN_ERROR — Chrome's is
946
+ // "BodyStreamBuffer was aborted". The run is over either way, but a
947
+ // deliberate stop is not a failure, and reporting it would raise a
948
+ // warning bubble above the stopped note saying the same thing twice.
949
+ // The promise route in `#run` reports the cancellation.
950
+ if (cancelled()) {
951
+ return;
952
+ }
953
+ h.onError(event.message);
954
+ },
955
+ onRunFinalized() {
956
+ runState.terminal = true;
957
+ h.onRunEnd();
958
+ },
959
+ };
960
+ }
961
+ }
962
+
963
+ /** Per-run mutable flags the subscriber writes and {@link AgUiClient} reads. */
964
+ interface RunState {
965
+ terminal: boolean;
966
+ errored: boolean;
967
+ /** Approval interrupts a run finished on (empty for a normal finish). */
968
+ interrupts: Interrupt[];
969
+ }
970
+
971
+ /**
972
+ * Whether a rejection came from aborting the run's fetch. Belt-and-suspenders
973
+ * with the `#cancelled` flag: some `@ag-ui/client` versions re-throw the
974
+ * `AbortError` instead of filtering it.
975
+ *
976
+ * Aborting a fetch whose body is mid-read does not always surface as an
977
+ * `AbortError`. Chrome raises `TypeError: BodyStreamBuffer was aborted`, which
978
+ * is the same event wearing a different name, so a message naming the abort is
979
+ * read as one too. Narrow on purpose: only a `TypeError`, and only when it says
980
+ * so — a genuine type error carries no such word, and misreading one as a
981
+ * cancellation would hide a real failure behind a stopped note.
982
+ */
983
+ function isAbortError(error: unknown): boolean {
984
+ if (!(error instanceof Error)) {
985
+ return false;
986
+ }
987
+ return (
988
+ error.name === "AbortError" || (error instanceof TypeError && /abort/i.test(error.message))
989
+ );
990
+ }
991
+
992
+ /**
993
+ * `message` with the keys an earlier release wrote at its top level -- a tool
994
+ * message's `outcome`, a user message's `attachments` -- moved into its
995
+ * `metadata`, or `message` itself when it carries neither.
996
+ *
997
+ * Both keys move whatever the role: no message the protocol defines declares
998
+ * either, so a top-level one can only be this component's own. A key already in
999
+ * the metadata wins over the one being moved, and a `metadata` that is not an
1000
+ * object is replaced rather than spread -- a store is not trusted to hold the
1001
+ * shape anything here wrote, and a spread string would scatter into numbered
1002
+ * keys.
1003
+ *
1004
+ * Held by "carries history stored by an earlier release into metadata" in
1005
+ * `agui_client.test.ts`, which seeds a message with only `attachments`, one
1006
+ * with only `outcome`, one whose metadata already has the key, and one whose
1007
+ * metadata is a string: deleting either half of the early return, or the
1008
+ * object check, fails it.
1009
+ */
1010
+ function withTopLevelKeysInMetadata(message: Message): Message {
1011
+ const { outcome, attachments, ...wire } = message as Message & {
1012
+ outcome?: unknown;
1013
+ attachments?: unknown;
1014
+ };
1015
+ if (outcome === undefined && attachments === undefined) {
1016
+ return message;
1017
+ }
1018
+ const existing = (wire as { metadata?: unknown }).metadata;
1019
+ return {
1020
+ ...wire,
1021
+ metadata: {
1022
+ ...(outcome === undefined ? {} : { outcome }),
1023
+ ...(attachments === undefined ? {} : { attachments }),
1024
+ // `null` passes the check and spreads as nothing, which is what it means.
1025
+ ...(typeof existing === "object" ? existing : {}),
1026
+ },
1027
+ } as Message;
1028
+ }