@copilotkit/vue 2.0.0-rc.2 → 2.0.0-rc.21

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 (335) hide show
  1. package/PARITY.md +3 -3
  2. package/dist/hooks/use-copilot-action.d.ts.map +1 -1
  3. package/dist/hooks/use-frontend-tool.d.ts +1 -1
  4. package/dist/hooks/use-frontend-tool.d.ts.map +1 -1
  5. package/dist/hooks/zod-parameters.d.ts +14 -0
  6. package/dist/hooks/zod-parameters.d.ts.map +1 -0
  7. package/dist/index.cjs +1 -73
  8. package/dist/index.cjs.map +1 -1
  9. package/dist/index.mjs +365 -16920
  10. package/dist/index.mjs.map +1 -1
  11. package/dist/use-render-activity-message-BAhbpSDh.js +16200 -0
  12. package/dist/use-render-activity-message-BAhbpSDh.js.map +1 -0
  13. package/dist/use-render-activity-message-DJ6q1qHV.cjs +114 -0
  14. package/dist/use-render-activity-message-DJ6q1qHV.cjs.map +1 -0
  15. package/dist/v2/components/A2UIMessageRenderer.d.ts +14 -0
  16. package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -1
  17. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +10 -3
  18. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -1
  19. package/dist/v2/components/CopilotKitInspector.vue.d.ts +5 -1
  20. package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -1
  21. package/dist/v2/components/InlineFeatureWarning.vue.d.ts.map +1 -1
  22. package/dist/v2/components/LicenseWarningBanner.vue.d.ts +2 -2
  23. package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -1
  24. package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -1
  25. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +13 -59
  26. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -1
  27. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +0 -5
  28. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -1
  29. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +10 -15
  30. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -1
  31. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts +14 -130
  32. package/dist/v2/components/a2ui/A2UIRecoveryStates.d.ts.map +1 -1
  33. package/dist/v2/components/a2ui/catalog.d.ts.map +1 -1
  34. package/dist/v2/components/a2ui/utils.d.ts +3 -15
  35. package/dist/v2/components/a2ui/utils.d.ts.map +1 -1
  36. package/dist/v2/components/a2ui.d.ts +9 -8
  37. package/dist/v2/components/a2ui.d.ts.map +1 -1
  38. package/dist/v2/components/chat/CopilotChat.vue.d.ts +2 -2
  39. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -1
  40. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -1
  41. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -1
  42. package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -1
  43. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +4 -8
  44. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -1
  45. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts +1 -1
  46. package/dist/v2/components/chat/CopilotChatFeedbackCommentPopover.vue.d.ts.map +1 -1
  47. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +6 -270
  48. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -1
  49. package/dist/v2/components/chat/CopilotChatInspectorButton.vue.d.ts.map +1 -1
  50. package/dist/v2/components/chat/CopilotChatLightbox.vue.d.ts.map +1 -1
  51. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +1 -1
  52. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -1
  53. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -1
  54. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts +1 -1
  55. package/dist/v2/components/chat/CopilotChatToolCallGroup.vue.d.ts.map +1 -1
  56. package/dist/v2/components/chat/CopilotChatToolCallItem.vue.d.ts.map +1 -1
  57. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +1 -1
  58. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -1
  59. package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -1
  60. package/dist/v2/components/chat/CopilotChatView.vue.d.ts +3 -3
  61. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -1
  62. package/dist/v2/components/chat/CopilotChatWorkSummary.vue.d.ts.map +1 -1
  63. package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -1
  64. package/dist/v2/components/chat/CopilotPopup.vue.d.ts +8 -3
  65. package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -1
  66. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +5 -3
  67. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -1
  68. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +5 -3
  69. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -1
  70. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +8 -3
  71. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -1
  72. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +6 -4
  73. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -1
  74. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +6 -4
  75. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -1
  76. package/dist/v2/components/chat/CopilotThreadsDrawer.vue.d.ts.map +1 -1
  77. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts +1 -0
  78. package/dist/v2/components/chat/StreamingMarkdownDefault.vue.d.ts.map +1 -1
  79. package/dist/v2/components/chat/audioRecorder.d.ts +3 -4
  80. package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -1
  81. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts +3 -3
  82. package/dist/v2/components/chat/copilotChatToolCallItemRender.d.ts.map +1 -1
  83. package/dist/v2/components/chat/forwarded-slots.d.ts +12 -0
  84. package/dist/v2/components/chat/forwarded-slots.d.ts.map +1 -0
  85. package/dist/v2/components/chat/index.d.ts +34 -31
  86. package/dist/v2/components/chat/index.d.ts.map +1 -1
  87. package/dist/v2/components/chat/inspector-shortcuts.d.ts +2 -1
  88. package/dist/v2/components/chat/inspector-shortcuts.d.ts.map +1 -1
  89. package/dist/v2/components/chat/message-feedback.d.ts +18 -49
  90. package/dist/v2/components/chat/message-feedback.d.ts.map +1 -1
  91. package/dist/v2/components/chat/tool-call-display-context.d.ts +4 -19
  92. package/dist/v2/components/chat/tool-call-display-context.d.ts.map +1 -1
  93. package/dist/v2/components/chat/tool-call-group-parts.d.ts +7 -20
  94. package/dist/v2/components/chat/tool-call-group-parts.d.ts.map +1 -1
  95. package/dist/v2/components/chat/turn-work.d.ts +0 -2
  96. package/dist/v2/components/chat/turn-work.d.ts.map +1 -1
  97. package/dist/v2/components/chat/types.d.ts +9 -4
  98. package/dist/v2/components/chat/types.d.ts.map +1 -1
  99. package/dist/v2/components/chat/use-chat-labels.d.ts +8 -0
  100. package/dist/v2/components/chat/use-chat-labels.d.ts.map +1 -0
  101. package/dist/v2/components/chat/use-copy-feedback.d.ts +11 -0
  102. package/dist/v2/components/chat/use-copy-feedback.d.ts.map +1 -0
  103. package/dist/v2/components/chat/use-lightbox.d.ts.map +1 -1
  104. package/dist/v2/components/icons/index.d.ts +968 -1
  105. package/dist/v2/components/icons/index.d.ts.map +1 -1
  106. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts +1 -1
  107. package/dist/v2/components/ui/CopilotDropdownMenuContent.vue.d.ts.map +1 -1
  108. package/dist/v2/hooks/use-agent-context.d.ts +2 -3
  109. package/dist/v2/hooks/use-agent-context.d.ts.map +1 -1
  110. package/dist/v2/hooks/use-agent.d.ts +4 -1
  111. package/dist/v2/hooks/use-agent.d.ts.map +1 -1
  112. package/dist/v2/hooks/use-attachments.d.ts +7 -0
  113. package/dist/v2/hooks/use-attachments.d.ts.map +1 -1
  114. package/dist/v2/hooks/use-capabilities.d.ts.map +1 -1
  115. package/dist/v2/hooks/use-component.d.ts +1 -0
  116. package/dist/v2/hooks/use-component.d.ts.map +1 -1
  117. package/dist/v2/hooks/use-configure-suggestions.d.ts +4 -9
  118. package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -1
  119. package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -1
  120. package/dist/v2/hooks/use-frontend-tool.d.ts +5 -0
  121. package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -1
  122. package/dist/v2/hooks/use-human-in-the-loop.d.ts +2 -1
  123. package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -1
  124. package/dist/v2/hooks/use-interrupt.d.ts +0 -1
  125. package/dist/v2/hooks/use-interrupt.d.ts.map +1 -1
  126. package/dist/v2/hooks/use-keyboard-height.d.ts +1 -1
  127. package/dist/v2/hooks/use-keyboard-height.d.ts.map +1 -1
  128. package/dist/v2/hooks/use-pin-to-send.d.ts +4 -12
  129. package/dist/v2/hooks/use-pin-to-send.d.ts.map +1 -1
  130. package/dist/v2/hooks/use-render-activity-message.d.ts +13 -9
  131. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -1
  132. package/dist/v2/hooks/use-render-custom-messages.d.ts +5 -7
  133. package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -1
  134. package/dist/v2/hooks/use-suggestions.d.ts.map +1 -1
  135. package/dist/v2/hooks/use-threads.d.ts +0 -8
  136. package/dist/v2/hooks/use-threads.d.ts.map +1 -1
  137. package/dist/v2/index.cjs +1 -1
  138. package/dist/v2/index.mjs +197 -200
  139. package/dist/v2/index.mjs.map +1 -1
  140. package/dist/v2/lib/processPartialHtml.d.ts +1 -2
  141. package/dist/v2/lib/processPartialHtml.d.ts.map +1 -1
  142. package/dist/v2/lib/shallow-stable.d.ts.map +1 -1
  143. package/dist/v2/lib/vue-core.d.ts +12 -12
  144. package/dist/v2/lib/vue-core.d.ts.map +1 -1
  145. package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -1
  146. package/dist/v2/providers/CopilotKitProvider.types.d.ts +9 -129
  147. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -1
  148. package/dist/v2/providers/CopilotKitProvider.vue.d.ts +9 -10
  149. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -1
  150. package/dist/v2/providers/chat-configuration-store-key.d.ts +8 -0
  151. package/dist/v2/providers/chat-configuration-store-key.d.ts.map +1 -0
  152. package/dist/v2/providers/markdown-renderer.d.ts +4 -2
  153. package/dist/v2/providers/markdown-renderer.d.ts.map +1 -1
  154. package/dist/v2/providers/types.d.ts +3 -46
  155. package/dist/v2/providers/types.d.ts.map +1 -1
  156. package/dist/v2/providers/use-inspector-thread-override.d.ts +5 -0
  157. package/dist/v2/providers/use-inspector-thread-override.d.ts.map +1 -1
  158. package/dist/v2/types/human-in-the-loop.d.ts +8 -29
  159. package/dist/v2/types/human-in-the-loop.d.ts.map +1 -1
  160. package/dist/v2/types/interrupt.d.ts +1 -22
  161. package/dist/v2/types/interrupt.d.ts.map +1 -1
  162. package/dist/v2/types/vue-activity-message-renderer.d.ts +16 -15
  163. package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -1
  164. package/dist/v2/types/vue-custom-message-renderer.d.ts +7 -16
  165. package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -1
  166. package/dist/v2/types/vue-tool-call-renderer.d.ts +16 -35
  167. package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -1
  168. package/package.json +10 -11
  169. package/src/__tests__/build-entries.test.ts +27 -0
  170. package/src/hooks/__tests__/zod-parameters.test.ts +520 -0
  171. package/src/hooks/use-copilot-action.ts +2 -1
  172. package/src/hooks/use-frontend-tool.ts +3 -6
  173. package/src/hooks/zod-parameters.ts +162 -0
  174. package/src/v2/__tests__/exports.test.ts +10 -0
  175. package/src/v2/__tests__/mocks/web-inspector.ts +6 -0
  176. package/src/v2/__tests__/utils/test-helpers.ts +34 -4
  177. package/src/v2/components/A2UIMessageRenderer.ts +98 -68
  178. package/src/v2/components/A2UISurfaceActivityRenderer.vue +68 -103
  179. package/src/v2/components/CopilotKitInspector.vue +104 -51
  180. package/src/v2/components/InlineFeatureWarning.vue +20 -21
  181. package/src/v2/components/LicenseWarningBanner.vue +27 -135
  182. package/src/v2/components/MCPAppsActivityRenderer.ts +75 -201
  183. package/src/v2/components/OpenGenerativeUIRenderer.ts +122 -446
  184. package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +171 -8
  185. package/src/v2/components/__tests__/CopilotKitInspector.test.ts +42 -0
  186. package/src/v2/components/__tests__/LicenseWarningBanner.test.ts +60 -0
  187. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +64 -8
  188. package/src/v2/components/__tests__/OpenGenerativeUIToolRenderer.test.ts +38 -0
  189. package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +2 -7
  190. package/src/v2/components/a2ui/A2UICatalogContext.ts +27 -185
  191. package/src/v2/components/a2ui/A2UIRecoveryStates.ts +127 -327
  192. package/src/v2/components/a2ui/catalog.ts +3 -3
  193. package/src/v2/components/a2ui/utils.ts +13 -65
  194. package/src/v2/components/a2ui.ts +15 -22
  195. package/src/v2/components/chat/CopilotChat.vue +147 -356
  196. package/src/v2/components/chat/CopilotChatAssistantMessage.vue +19 -43
  197. package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +36 -55
  198. package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +14 -6
  199. package/src/v2/components/chat/CopilotChatAudioRecorder.vue +19 -246
  200. package/src/v2/components/chat/CopilotChatFeedbackCommentPopover.vue +2 -2
  201. package/src/v2/components/chat/CopilotChatInput.vue +108 -436
  202. package/src/v2/components/chat/CopilotChatInspectorButton.vue +45 -108
  203. package/src/v2/components/chat/CopilotChatLightbox.vue +10 -31
  204. package/src/v2/components/chat/CopilotChatMessageView.vue +82 -210
  205. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +7 -15
  206. package/src/v2/components/chat/CopilotChatToolCallGroup.vue +63 -71
  207. package/src/v2/components/chat/CopilotChatToolCallItem.vue +8 -6
  208. package/src/v2/components/chat/CopilotChatToolCallsView.vue +31 -32
  209. package/src/v2/components/chat/CopilotChatUserMessage.vue +24 -112
  210. package/src/v2/components/chat/CopilotChatView.vue +33 -28
  211. package/src/v2/components/chat/CopilotChatWorkSummary.vue +26 -36
  212. package/src/v2/components/chat/CopilotModalHeader.vue +11 -23
  213. package/src/v2/components/chat/CopilotPopup.vue +23 -25
  214. package/src/v2/components/chat/CopilotPopupView.vue +10 -39
  215. package/src/v2/components/chat/CopilotPopupViewInternal.vue +35 -131
  216. package/src/v2/components/chat/CopilotSidebar.vue +23 -25
  217. package/src/v2/components/chat/CopilotSidebarView.vue +10 -39
  218. package/src/v2/components/chat/CopilotSidebarViewInternal.vue +38 -59
  219. package/src/v2/components/chat/CopilotThreadsDrawer.vue +37 -102
  220. package/src/v2/components/chat/StreamingMarkdownDefault.vue +15 -28
  221. package/src/v2/components/chat/__tests__/CopilotChat.labels.test.ts +19 -4
  222. package/src/v2/components/chat/__tests__/CopilotChat.test.ts +12 -10
  223. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +26 -9
  224. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.learningFeedback.test.ts +5 -5
  225. package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +28 -0
  226. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +89 -1
  227. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +247 -403
  228. package/src/v2/components/chat/__tests__/CopilotChatMessageView.toolCallDisplay.test.ts +1 -99
  229. package/src/v2/components/chat/__tests__/CopilotPopup.test.ts +90 -1
  230. package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +19 -0
  231. package/src/v2/components/chat/__tests__/CopilotSidebar.test.ts +90 -1
  232. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.ts +32 -8
  233. package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +2 -1
  234. package/src/v2/components/chat/__tests__/StreamingMarkdownDefault.test.ts +54 -0
  235. package/src/v2/components/chat/audioRecorder.ts +4 -7
  236. package/src/v2/components/chat/copilotChatToolCallItemRender.ts +24 -89
  237. package/src/v2/components/chat/forwarded-slots.ts +17 -0
  238. package/src/v2/components/chat/index.ts +0 -1
  239. package/src/v2/components/chat/inspector-shortcuts.ts +10 -16
  240. package/src/v2/components/chat/message-feedback.ts +62 -197
  241. package/src/v2/components/chat/tool-call-display-context.ts +6 -22
  242. package/src/v2/components/chat/tool-call-group-parts.ts +47 -108
  243. package/src/v2/components/chat/turn-work.ts +0 -9
  244. package/src/v2/components/chat/types.ts +12 -4
  245. package/src/v2/components/chat/use-chat-labels.ts +16 -0
  246. package/src/v2/components/chat/use-copy-feedback.ts +21 -0
  247. package/src/v2/components/chat/use-lightbox.ts +15 -33
  248. package/src/v2/components/icons/__tests__/icons.test.ts +72 -68
  249. package/src/v2/components/icons/index.ts +109 -27
  250. package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +5 -3
  251. package/src/v2/hooks/__tests__/use-agent-stability.test.ts +25 -4
  252. package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +5 -1
  253. package/src/v2/hooks/__tests__/use-agent.test.ts +6 -3
  254. package/src/v2/hooks/__tests__/use-component.test.ts +21 -0
  255. package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +30 -1
  256. package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +10 -10
  257. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +66 -1
  258. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +6 -1
  259. package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +187 -12
  260. package/src/v2/hooks/__tests__/use-interrupt.test.ts +97 -0
  261. package/src/v2/hooks/__tests__/use-render-activity-message.test.ts +106 -0
  262. package/src/v2/hooks/__tests__/use-render-tool.test.ts +3 -6
  263. package/src/v2/hooks/__tests__/use-threads.test.ts +48 -6
  264. package/src/v2/hooks/__tests__/zod-regression.test.ts +5 -4
  265. package/src/v2/hooks/use-agent-context.ts +4 -10
  266. package/src/v2/hooks/use-agent.ts +86 -290
  267. package/src/v2/hooks/use-attachments.ts +48 -168
  268. package/src/v2/hooks/use-capabilities.ts +2 -7
  269. package/src/v2/hooks/use-component.ts +7 -4
  270. package/src/v2/hooks/use-configure-suggestions.ts +28 -218
  271. package/src/v2/hooks/use-default-render-tool.ts +34 -94
  272. package/src/v2/hooks/use-frontend-tool.ts +10 -10
  273. package/src/v2/hooks/use-human-in-the-loop.ts +11 -80
  274. package/src/v2/hooks/use-interrupt.ts +29 -308
  275. package/src/v2/hooks/use-keyboard-height.ts +17 -40
  276. package/src/v2/hooks/use-pin-to-send.ts +15 -99
  277. package/src/v2/hooks/use-render-activity-message.ts +31 -55
  278. package/src/v2/hooks/use-render-custom-messages.ts +24 -88
  279. package/src/v2/hooks/use-suggestions.ts +9 -33
  280. package/src/v2/hooks/use-threads.ts +43 -273
  281. package/src/v2/lib/__tests__/shallow-stable.test.ts +54 -0
  282. package/src/v2/lib/__tests__/vue-core-renderers.test.ts +42 -0
  283. package/src/v2/lib/processPartialHtml.ts +6 -21
  284. package/src/v2/lib/shallow-stable.ts +2 -16
  285. package/src/v2/lib/vue-core.ts +69 -78
  286. package/src/v2/providers/CopilotChatConfigurationProvider.vue +48 -186
  287. package/src/v2/providers/CopilotKitProvider.types.ts +12 -132
  288. package/src/v2/providers/CopilotKitProvider.vue +237 -510
  289. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +22 -2
  290. package/src/v2/providers/__tests__/CopilotKitProvider.a2ui.test.ts +20 -2
  291. package/src/v2/providers/__tests__/CopilotKitProvider.config.test.ts +124 -0
  292. package/src/v2/providers/__tests__/CopilotKitProvider.headersAtSendTime.test.ts +3 -3
  293. package/src/v2/providers/__tests__/CopilotKitProvider.learning.test.ts +3 -1
  294. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +4 -1
  295. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +53 -26
  296. package/src/v2/providers/chat-configuration-store-key.ts +9 -0
  297. package/src/v2/providers/markdown-renderer.ts +8 -2
  298. package/src/v2/providers/types.ts +3 -49
  299. package/src/v2/providers/use-inspector-thread-override.ts +22 -80
  300. package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +28 -33
  301. package/src/v2/types/defineToolCallRenderer.ts +5 -4
  302. package/src/v2/types/human-in-the-loop.ts +8 -32
  303. package/src/v2/types/interrupt.ts +7 -31
  304. package/src/v2/types/vue-activity-message-renderer.ts +24 -15
  305. package/src/v2/types/vue-custom-message-renderer.ts +12 -16
  306. package/src/v2/types/vue-tool-call-renderer.ts +23 -38
  307. package/tsconfig.json +0 -1
  308. package/vite.config.ts +8 -5
  309. package/vitest.config.ts +12 -7
  310. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts +0 -9
  311. package/dist/v2/components/chat/BasicMarkdown.vue.d.ts.map +0 -1
  312. package/dist/v2/components/chat/normalize-auto-scroll.d.ts +0 -3
  313. package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +0 -1
  314. package/dist/v2/components/chat/tool-call-layout.d.ts +0 -115
  315. package/dist/v2/components/chat/tool-call-layout.d.ts.map +0 -1
  316. package/dist/v2/lib/is-mobile-viewport.d.ts +0 -15
  317. package/dist/v2/lib/is-mobile-viewport.d.ts.map +0 -1
  318. package/dist/v2/lib/message-feedback.d.ts +0 -33
  319. package/dist/v2/lib/message-feedback.d.ts.map +0 -1
  320. package/dist/v2/lib/transcription-client.d.ts +0 -19
  321. package/dist/v2/lib/transcription-client.d.ts.map +0 -1
  322. package/src/v2/components/__tests__/A2UICatalogContext.test.ts +0 -63
  323. package/src/v2/components/a2ui/__tests__/A2UIRecoveryStates.test.ts +0 -175
  324. package/src/v2/components/chat/BasicMarkdown.vue +0 -160
  325. package/src/v2/components/chat/__tests__/BasicMarkdown.test.ts +0 -69
  326. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +0 -39
  327. package/src/v2/components/chat/normalize-auto-scroll.ts +0 -17
  328. package/src/v2/components/chat/tool-call-layout.ts +0 -304
  329. package/src/v2/lib/__tests__/is-mobile-viewport.test.ts +0 -48
  330. package/src/v2/lib/__tests__/message-feedback.test.ts +0 -152
  331. package/src/v2/lib/__tests__/processPartialHtml.test.ts +0 -84
  332. package/src/v2/lib/__tests__/transcription-client.test.ts +0 -67
  333. package/src/v2/lib/is-mobile-viewport.ts +0 -23
  334. package/src/v2/lib/message-feedback.ts +0 -120
  335. package/src/v2/lib/transcription-client.ts +0 -151
@@ -1,7 +1,7 @@
1
1
  import { computed, onScopeDispose, shallowRef, watch } from "vue";
2
2
  import type { ComputedRef, Ref } from "vue";
3
- import { buildResumeArray, isInterruptExpired } from "@ag-ui/client";
4
- import type { Interrupt, RunAgentResult } from "@ag-ui/client";
3
+ import type { Interrupt } from "@ag-ui/client";
4
+ import { ɵInterruptController } from "@copilotkit/core";
5
5
  import { useCopilotKit } from "../providers/useCopilotKit";
6
6
  import { useAgent } from "./use-agent";
7
7
  import type {
@@ -19,21 +19,6 @@ export type {
19
19
  Interrupt,
20
20
  };
21
21
 
22
- const INTERRUPT_EVENT_NAME = "on_interrupt";
23
-
24
- /** Internal accumulator response shape consumed by buildResumeArray. */
25
- type ResumeResponse =
26
- | { status: "resolved"; payload?: unknown }
27
- | { status: "cancelled" };
28
-
29
- /**
30
- * Normalized pending interrupt. `legacy` carries the custom-event payload;
31
- * `standard` carries the AG-UI `outcome:"interrupt"` interrupts array.
32
- */
33
- type PendingInterrupt =
34
- | { kind: "legacy"; event: InterruptEvent }
35
- | { kind: "standard"; interrupts: Interrupt[] };
36
-
37
22
  type InterruptHandlerFn<TValue, TResult> = (
38
23
  props: InterruptHandlerProps<TValue>,
39
24
  ) => TResult | PromiseLike<TResult>;
@@ -75,38 +60,6 @@ export interface UseInterruptResult<TValue = unknown, TResult = never> {
75
60
  > | null>;
76
61
  }
77
62
 
78
- export function isPromiseLike<TValue>(
79
- value: TValue | PromiseLike<TValue>,
80
- ): value is PromiseLike<TValue> {
81
- return (
82
- (typeof value === "object" || typeof value === "function") &&
83
- value !== null &&
84
- typeof Reflect.get(value, "then") === "function"
85
- );
86
- }
87
-
88
- function normalizeAsyncResult<TValue>(
89
- value: PromiseLike<TValue>,
90
- ): Promise<TValue> {
91
- if (value instanceof Promise) {
92
- return value;
93
- }
94
-
95
- return new Promise<TValue>((resolve, reject) => {
96
- try {
97
- value.then(resolve, reject);
98
- } catch (error) {
99
- reject(error);
100
- }
101
- });
102
- }
103
-
104
- /** Derive the legacy-compatible `event` for any pending interrupt. */
105
- function toLegacyEvent(pending: PendingInterrupt): InterruptEvent {
106
- if (pending.kind === "legacy") return pending.event;
107
- return { name: INTERRUPT_EVENT_NAME, value: pending.interrupts[0] };
108
- }
109
-
110
63
  /**
111
64
  * Vue composable for handling agent interrupts with optional filtering,
112
65
  * preprocessing, and resume behavior.
@@ -131,262 +84,33 @@ export function useInterrupt<TValue = unknown, TResult = never>(
131
84
  ): UseInterruptResult<TValue, TResult> {
132
85
  const { copilotkit } = useCopilotKit();
133
86
  const { agent } = useAgent({ agentId: config.agentId });
134
- const pending = shallowRef<PendingInterrupt | null>(null);
135
- const result = shallowRef<InterruptResult<TValue, TResult>>(null);
136
-
137
- // Accumulated per-interrupt responses for the current standard interrupt set.
138
- const responses: Record<string, ResumeResponse> = {};
139
-
140
- watch(
141
- agent,
142
- (resolvedAgent, _previousAgent, onCleanup) => {
143
- if (!resolvedAgent) {
144
- pending.value = null;
145
- result.value = null;
146
- return;
147
- }
148
87
 
149
- let localLegacy: InterruptEvent<TValue> | null = null;
150
- let localStandard: Interrupt[] | null = null;
151
-
152
- const subscription = resolvedAgent.subscribe({
153
- onCustomEvent: ({ event }) => {
154
- if (event.name === INTERRUPT_EVENT_NAME) {
155
- localLegacy = {
156
- name: event.name,
157
- value: event.value as TValue,
158
- };
159
- }
160
- },
161
- onRunFinishedEvent: (params) => {
162
- if (params.outcome === "interrupt") {
163
- localStandard = params.interrupts;
164
- }
165
- },
166
- onRunStartedEvent: () => {
167
- localLegacy = null;
168
- localStandard = null;
169
- // Reset accumulated responses for the new run.
170
- for (const k of Object.keys(responses)) {
171
- delete responses[k];
172
- }
173
- pending.value = null;
174
- },
175
- onRunFinalized: () => {
176
- // Standard wins if both somehow appear for one run.
177
- if (localStandard && localStandard.length > 0) {
178
- pending.value = { kind: "standard", interrupts: localStandard };
179
- } else if (localLegacy) {
180
- pending.value = { kind: "legacy", event: localLegacy };
181
- }
182
- localLegacy = null;
183
- localStandard = null;
184
- },
185
- onRunFailed: () => {
186
- localLegacy = null;
187
- localStandard = null;
188
- for (const k of Object.keys(responses)) {
189
- delete responses[k];
190
- }
191
- pending.value = null;
192
- },
193
- });
194
-
195
- onCleanup(() => subscription.unsubscribe());
88
+ const controller = new ɵInterruptController<
89
+ TValue,
90
+ InterruptResult<TValue, TResult>
91
+ >(
92
+ { runAgent: (params) => copilotkit.value.runAgent(params) },
93
+ {
94
+ enabled: (event) => config.enabled?.(event) ?? true,
95
+ handler: (props) => (config.handler ? config.handler(props) : null),
196
96
  },
197
- { immediate: true },
198
97
  );
199
-
200
- /** Submit the accumulated standard responses once all open interrupts are addressed. */
201
- const submitStandardIfComplete = async (
202
- interrupts: Interrupt[],
203
- ): Promise<RunAgentResult | void> => {
204
- const allAddressed = interrupts.every((i) => responses[i.id]);
205
- if (!allAddressed) return;
206
-
207
- const expired = interrupts.find((i) => isInterruptExpired(i));
208
- if (expired) {
209
- console.error(
210
- `[CopilotKit] useInterrupt: interrupt ${expired.id} expired at ${expired.expiresAt}; not resuming.`,
211
- );
212
- for (const k of Object.keys(responses)) {
213
- delete responses[k];
214
- }
215
- pending.value = null;
216
- return;
217
- }
218
-
219
- const resume = buildResumeArray(interrupts, responses);
220
- for (const k of Object.keys(responses)) {
221
- delete responses[k];
222
- }
223
- const resolvedAgent = agent.value;
224
- if (!resolvedAgent) return;
225
- try {
226
- return await copilotkit.value.runAgent({ agent: resolvedAgent, resume });
227
- } catch (err) {
228
- console.error(
229
- "[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing",
230
- err,
231
- );
232
- pending.value = null;
233
- throw err;
234
- }
235
- };
236
-
237
- const resolve: InterruptResolveFn = async (payload?, interruptId?) => {
238
- const current = pending.value;
239
- if (!current) return;
240
-
241
- const resolvedAgent = agent.value;
242
- if (!resolvedAgent) return;
243
-
244
- if (current.kind === "legacy") {
245
- const interruptEventValue = current.event.value;
246
- try {
247
- return await copilotkit.value.runAgent({
248
- agent: resolvedAgent,
249
- forwardedProps: {
250
- command: {
251
- resume: payload,
252
- interruptEvent: interruptEventValue,
253
- },
254
- },
255
- });
256
- } catch (err) {
257
- console.error(
258
- "[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing",
259
- err,
260
- );
261
- pending.value = null;
262
- throw err;
263
- }
264
- }
265
-
266
- if (current.interrupts.length > 1 && interruptId === undefined) {
267
- console.warn(
268
- `[CopilotKit] useInterrupt: resolve()/cancel() called without an interruptId while ${current.interrupts.length} interrupts are open; defaulting to the first. Pass an interruptId to address a specific interrupt.`,
269
- );
270
- }
271
- const id = interruptId ?? current.interrupts[0]?.id;
272
- if (!id) return;
273
- responses[id] = { status: "resolved", payload };
274
- return submitStandardIfComplete(current.interrupts);
275
- };
276
-
277
- const cancel: InterruptCancelFn = async (interruptId?) => {
278
- const current = pending.value;
279
- if (!current) return;
280
-
281
- if (current.kind === "legacy") {
282
- // Legacy interrupts have no cancel semantics; dismiss without resuming.
283
- console.warn(
284
- "[CopilotKit] useInterrupt: cancel() is not supported for legacy on_interrupt interrupts; dismissing.",
285
- );
286
- pending.value = null;
287
- return;
288
- }
289
-
290
- if (current.interrupts.length > 1 && interruptId === undefined) {
291
- console.warn(
292
- `[CopilotKit] useInterrupt: resolve()/cancel() called without an interruptId while ${current.interrupts.length} interrupts are open; defaulting to the first. Pass an interruptId to address a specific interrupt.`,
293
- );
294
- }
295
- const id = interruptId ?? current.interrupts[0]?.id;
296
- if (!id) return;
297
- responses[id] = { status: "cancelled" };
298
- return submitStandardIfComplete(current.interrupts);
299
- };
300
-
301
- // Keep resolveInterrupt and cancelInterrupt as aliases for back-compat.
302
- const resolveInterrupt: InterruptResolveFn = resolve;
303
- const cancelInterrupt: InterruptCancelFn = cancel;
98
+ const snapshot = shallowRef(controller.getSnapshot());
99
+ controller.subscribe(() => {
100
+ snapshot.value = controller.getSnapshot();
101
+ });
304
102
 
305
103
  watch(
306
- pending,
307
- (currentPending, _previous, onCleanup) => {
308
- if (!currentPending) {
309
- result.value = null;
310
- return;
311
- }
312
- const legacyEvent = toLegacyEvent(
313
- currentPending,
314
- ) as InterruptEvent<TValue>;
315
- if (config.enabled && !config.enabled(legacyEvent)) {
316
- result.value = null;
317
- return;
318
- }
319
- const handler = config.handler;
320
- if (!handler) {
321
- result.value = null;
322
- return;
323
- }
324
-
325
- let cancelled = false;
326
- const maybePromise = handler({
327
- event: legacyEvent,
328
- interrupt:
329
- currentPending.kind === "standard"
330
- ? (currentPending.interrupts[0] ?? null)
331
- : null,
332
- interrupts:
333
- currentPending.kind === "standard" ? currentPending.interrupts : [],
334
- resolve,
335
- cancel,
336
- });
337
-
338
- if (isPromiseLike(maybePromise)) {
339
- normalizeAsyncResult(maybePromise)
340
- .then((resolved) => {
341
- if (!cancelled) {
342
- result.value = resolved;
343
- }
344
- })
345
- .catch((err) => {
346
- if (!cancelled) {
347
- console.error("[CopilotKit] useInterrupt handler failed:", err);
348
- result.value = null;
349
- }
350
- });
351
- } else {
352
- result.value = maybePromise;
353
- }
354
-
355
- onCleanup(() => {
356
- cancelled = true;
357
- });
104
+ agent,
105
+ (resolvedAgent) => {
106
+ if (resolvedAgent) controller.connect(resolvedAgent);
107
+ else controller.disconnect();
358
108
  },
359
109
  { immediate: true },
360
110
  );
361
111
 
362
- // Compute the legacy-compat "interrupt" ref for existing consumers (InterruptEvent shape).
363
- const interrupt = computed<InterruptEvent<TValue> | null>(() => {
364
- if (!pending.value) return null;
365
- return toLegacyEvent(pending.value) as InterruptEvent<TValue>;
366
- });
367
-
368
- const slotProps = computed<InterruptRenderProps<
369
- TValue,
370
- InterruptResult<TValue, TResult>
371
- > | null>(() => {
372
- const currentPending = pending.value;
373
- if (!currentPending) return null;
374
- const legacyEvent = toLegacyEvent(currentPending) as InterruptEvent<TValue>;
375
- if (config.enabled && !config.enabled(legacyEvent)) return null;
376
-
377
- return {
378
- event: legacyEvent,
379
- interrupt:
380
- currentPending.kind === "standard"
381
- ? (currentPending.interrupts[0] ?? null)
382
- : null,
383
- interrupts:
384
- currentPending.kind === "standard" ? currentPending.interrupts : [],
385
- result: result.value,
386
- resolve,
387
- cancel,
388
- };
389
- });
112
+ const { resolve, cancel } = controller;
113
+ const slotProps = computed(() => snapshot.value.renderProps);
390
114
 
391
115
  watch(
392
116
  [() => copilotkit.value, slotProps, () => config.renderInChat !== false],
@@ -395,10 +119,7 @@ export function useInterrupt<TValue = unknown, TResult = never>(
395
119
  return;
396
120
  }
397
121
 
398
- core.setInterruptState(
399
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
400
- nextSlotProps as InterruptRenderProps<any, any> | null,
401
- );
122
+ core.setInterruptState(nextSlotProps);
402
123
  const publishedState = nextSlotProps;
403
124
 
404
125
  onCleanup(() => {
@@ -420,17 +141,17 @@ export function useInterrupt<TValue = unknown, TResult = never>(
420
141
  }
421
142
  });
422
143
 
144
+ onScopeDispose(() => controller.dispose());
145
+
423
146
  return {
424
- interrupt: interrupt as unknown as Ref<InterruptEvent<TValue> | null>,
425
- result: result as Ref<InterruptResult<TValue, TResult>>,
147
+ interrupt: computed(() => slotProps.value?.event ?? null),
148
+ result: computed(() => snapshot.value.result),
426
149
  hasInterrupt: computed(() => slotProps.value !== null),
427
150
  resolve,
428
- resolveInterrupt,
151
+ // Aliases kept for back-compat.
152
+ resolveInterrupt: resolve,
429
153
  cancel,
430
- cancelInterrupt,
431
- slotProps: slotProps as ComputedRef<InterruptRenderProps<
432
- TValue,
433
- InterruptResult<TValue, TResult>
434
- > | null>,
154
+ cancelInterrupt: cancel,
155
+ slotProps,
435
156
  };
436
157
  }
@@ -1,4 +1,6 @@
1
- import { onBeforeUnmount, onMounted, readonly, ref, type Ref } from "vue";
1
+ import { onBeforeUnmount, onMounted, readonly, ref } from "vue";
2
+ import type { Ref } from "vue";
3
+ import { ɵinitialKeyboardState, ɵwatchKeyboardState } from "@copilotkit/core";
2
4
 
3
5
  /**
4
6
  * Vue-idiomatic result of {@link useKeyboardHeight}. Fields mirror the React
@@ -32,50 +34,25 @@ export interface KeyboardState {
32
34
  * ```
33
35
  */
34
36
  export function useKeyboardHeight(): KeyboardState {
35
- const initialHeight = typeof window !== "undefined" ? window.innerHeight : 0;
36
- const isKeyboardOpen = ref(false);
37
- const keyboardHeight = ref(0);
38
- const availableHeight = ref(initialHeight);
39
- const viewportHeight = ref(initialHeight);
40
-
41
- const updateKeyboardState = () => {
42
- if (typeof window === "undefined") return;
43
- const visualViewport = window.visualViewport;
44
- if (!visualViewport) return;
45
-
46
- const layoutHeight = window.innerHeight;
47
- const visualHeight = visualViewport.height;
48
-
49
- // Keyboard height = layout viewport minus visual viewport, clamped to 0.
50
- const nextKeyboardHeight = Math.max(0, layoutHeight - visualHeight);
51
- // Only treat the keyboard as open past the 150px threshold, matching
52
- // the React implementation's mobile heuristic.
53
- const nextIsOpen = nextKeyboardHeight > 150;
54
-
55
- keyboardHeight.value = nextKeyboardHeight;
56
- isKeyboardOpen.value = nextIsOpen;
57
- availableHeight.value = visualHeight;
58
- viewportHeight.value = layoutHeight;
59
- };
60
-
61
- let visualViewport: VisualViewport | null = null;
37
+ const initial = ɵinitialKeyboardState();
38
+ const isKeyboardOpen = ref(initial.isKeyboardOpen);
39
+ const keyboardHeight = ref(initial.keyboardHeight);
40
+ const availableHeight = ref(initial.availableHeight);
41
+ const viewportHeight = ref(initial.viewportHeight);
42
+ let stopWatching: (() => void) | undefined;
62
43
 
63
44
  onMounted(() => {
64
- if (typeof window === "undefined") return;
65
- visualViewport = window.visualViewport ?? null;
66
- if (!visualViewport) return;
67
-
68
- updateKeyboardState();
69
-
70
- visualViewport.addEventListener("resize", updateKeyboardState);
71
- visualViewport.addEventListener("scroll", updateKeyboardState);
45
+ stopWatching = ɵwatchKeyboardState((state) => {
46
+ keyboardHeight.value = state.keyboardHeight;
47
+ isKeyboardOpen.value = state.isKeyboardOpen;
48
+ availableHeight.value = state.availableHeight;
49
+ viewportHeight.value = state.viewportHeight;
50
+ });
72
51
  });
73
52
 
74
53
  onBeforeUnmount(() => {
75
- if (!visualViewport) return;
76
- visualViewport.removeEventListener("resize", updateKeyboardState);
77
- visualViewport.removeEventListener("scroll", updateKeyboardState);
78
- visualViewport = null;
54
+ stopWatching?.();
55
+ stopWatching = undefined;
79
56
  });
80
57
 
81
58
  return {
@@ -1,5 +1,6 @@
1
1
  import { inject, watch } from "vue";
2
2
  import type { Ref } from "vue";
3
+ import { ɵpinMessageToTop } from "@copilotkit/core";
3
4
  import {
4
5
  LastUserMessageKey,
5
6
  createDefaultLastUserMessageRef,
@@ -19,18 +20,10 @@ export interface UsePinToSendOptions {
19
20
  * pinned near the top of the viewport while the assistant streams a
20
21
  * response beneath it.
21
22
  *
22
- * Implementation parity with React:
23
- * - Reads the latest user message via the `LastUserMessageContext`
24
- * equivalent (`LastUserMessageKey` + `Ref<LastUserMessageState>`).
25
- * - On each `sendNonce` increment, sizes the spacer to
26
- * `viewportHeight - bubbleHeight - topOffset` and scrolls so the bubble
27
- * sits `topOffset` from the viewport top (padding above the bubble is
28
- * pushed off-screen).
29
- * - Installs a `ResizeObserver` on `contentRef` that adjusts the spacer in
30
- * both directions as content below the anchored message changes, keeping
31
- * the bubble pinned.
32
- * - Cancels the scheduled `requestAnimationFrame` and disconnects the
33
- * `ResizeObserver` on subsequent re-runs and on scope dispose.
23
+ * Reads the latest user message via `LastUserMessageKey` and, on each
24
+ * `sendNonce` increment, runs core's `ɵpinMessageToTop`, which sizes the
25
+ * spacer, scrolls, and follows the reply's growth. The previous pin stops on
26
+ * the next run and on scope dispose.
34
27
  */
35
28
  export function usePinToSend({
36
29
  scrollRef,
@@ -45,17 +38,6 @@ export function usePinToSend({
45
38
  );
46
39
 
47
40
  let lastNonce = -1;
48
- let raf: number | null = null;
49
- let ro: ResizeObserver | null = null;
50
-
51
- function teardown() {
52
- if (raf !== null && typeof cancelAnimationFrame === "function") {
53
- cancelAnimationFrame(raf);
54
- }
55
- raf = null;
56
- ro?.disconnect();
57
- ro = null;
58
- }
59
41
 
60
42
  watch(
61
43
  [
@@ -67,90 +49,24 @@ export function usePinToSend({
67
49
  () => topOffset,
68
50
  ],
69
51
  ([id, sendNonce, scrollEl, contentEl, spacerEl], _old, onCleanup) => {
70
- // Always tear down the previous run's RAF / ResizeObserver before
71
- // evaluating any of the early-exit guards below. Mirrors React
72
- // `useEffect` cleanup semantics: the prior run's cleanup runs before
73
- // the next callback executes regardless of why the deps changed.
74
- // Without this, a reactive `autoScroll` mode change (e.g. switching
75
- // away from `"pin-to-send"`) or a `ref` swap that doesn't bump
76
- // `sendNonce` would leak the stale content-element observer because
77
- // the nonce guard skips the body. Registering via `onCleanup` also
78
- // covers watcher stop on scope dispose, so an explicit
79
- // `onScopeDispose(teardown)` is no longer required.
80
- onCleanup(teardown);
81
-
82
52
  if (sendNonce === lastNonce) return;
83
53
  lastNonce = sendNonce;
84
54
 
85
55
  if (!id) return;
86
56
  if (!scrollEl || !contentEl || !spacerEl) return;
87
57
 
88
- const escaped =
89
- typeof CSS !== "undefined" && typeof CSS.escape === "function"
90
- ? CSS.escape(id)
91
- : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
92
- const targetEl = contentEl.querySelector<HTMLElement>(
93
- `[data-message-id="${escaped}"]`,
58
+ // Registered through `onCleanup`, so the previous pin stops before the
59
+ // next run and when the watcher stops on scope dispose.
60
+ onCleanup(
61
+ ɵpinMessageToTop({
62
+ scrollEl,
63
+ contentEl,
64
+ spacerEl,
65
+ messageId: id,
66
+ topOffset,
67
+ }),
94
68
  );
95
- if (!targetEl) return;
96
-
97
- // The target message's element has a top padding (e.g. `pt-10`) that
98
- // creates breathing room above the visible bubble. When we "anchor at
99
- // the top", we mean anchor the *bubble*, not the element's padded box.
100
- // So we scroll past the padding (it goes above the viewport, hiding
101
- // whatever was above the element too — including the previous message's
102
- // trailing copy button).
103
- const viewportHeight = scrollEl.clientHeight;
104
- const userMessageHeight = targetEl.getBoundingClientRect().height;
105
- const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
106
- const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
107
- const spacerHeight = Math.max(
108
- 0,
109
- viewportHeight - bubbleHeight - topOffset,
110
- );
111
-
112
- spacerEl.style.height = `${spacerHeight}px`;
113
-
114
- if (typeof requestAnimationFrame === "function") {
115
- raf = requestAnimationFrame(() => {
116
- // Scroll so the BUBBLE is `topOffset` from the viewport top — the
117
- // padding above the bubble ends up scrolled off-screen.
118
- const targetTop =
119
- computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
120
- scrollEl.scrollTo({
121
- top: Math.max(0, targetTop),
122
- behavior: "smooth",
123
- });
124
- });
125
- }
126
-
127
- // As content below the anchored user message changes, adjust the spacer
128
- // by the same amount so total scrollable space below the bubble stays
129
- // constant and the bubble stays pinned.
130
- if (typeof ResizeObserver !== "undefined") {
131
- ro = new ResizeObserver(() => {
132
- if (!contentEl || !spacerEl || !scrollEl) return;
133
- const contentHeight = contentEl.getBoundingClientRect().height;
134
- const targetOffsetWithinContent = computeOffsetTop(
135
- targetEl,
136
- contentEl,
137
- );
138
- const consumedBelow =
139
- contentHeight - targetOffsetWithinContent - userMessageHeight;
140
- const remaining = Math.max(0, spacerHeight - consumedBelow);
141
- spacerEl.style.height = `${remaining}px`;
142
- });
143
- ro.observe(contentEl);
144
- }
145
69
  },
146
70
  { immediate: true, flush: "post" },
147
71
  );
148
72
  }
149
-
150
- // Compute the offset of el relative to stopAt, accounting for stopAt's current scrollTop.
151
- // Uses getBoundingClientRect so it works regardless of CSS positioning (including position:static).
152
- function computeOffsetTop(el: HTMLElement, stopAt: HTMLElement): number {
153
- const elRect = el.getBoundingClientRect();
154
- const stopRect = stopAt.getBoundingClientRect();
155
- return elRect.top - stopRect.top + stopAt.scrollTop;
156
- }