@copilotkit/vue 1.57.1

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 (437) hide show
  1. package/AGENTS.md +50 -0
  2. package/CHANGELOG.md +13 -0
  3. package/PARITY.md +434 -0
  4. package/README.md +396 -0
  5. package/dist/components/copilot-provider/CopilotKit.vue.d.ts +20 -0
  6. package/dist/components/copilot-provider/CopilotKit.vue.d.ts.map +1 -0
  7. package/dist/components/copilot-provider/index.d.ts +3 -0
  8. package/dist/components/copilot-provider/index.d.ts.map +1 -0
  9. package/dist/components/copilot-provider/types.d.ts +22 -0
  10. package/dist/components/copilot-provider/types.d.ts.map +1 -0
  11. package/dist/hooks/index.d.ts +7 -0
  12. package/dist/hooks/index.d.ts.map +1 -0
  13. package/dist/hooks/use-copilot-action.d.ts +27 -0
  14. package/dist/hooks/use-copilot-action.d.ts.map +1 -0
  15. package/dist/hooks/use-copilot-readable.d.ts +20 -0
  16. package/dist/hooks/use-copilot-readable.d.ts.map +1 -0
  17. package/dist/hooks/use-frontend-tool.d.ts +21 -0
  18. package/dist/hooks/use-frontend-tool.d.ts.map +1 -0
  19. package/dist/index.cjs +2 -0
  20. package/dist/index.cjs.map +1 -0
  21. package/dist/index.d.cts +10 -0
  22. package/dist/index.d.mts +10 -0
  23. package/dist/index.d.ts +10 -0
  24. package/dist/index.d.ts.map +1 -0
  25. package/dist/index.mjs +252 -0
  26. package/dist/index.mjs.map +1 -0
  27. package/dist/styles.css +2 -0
  28. package/dist/use-render-activity-message-BRL1Rpl-.cjs +85 -0
  29. package/dist/use-render-activity-message-BRL1Rpl-.cjs.map +1 -0
  30. package/dist/use-render-activity-message-CqtxiFSs.js +8927 -0
  31. package/dist/use-render-activity-message-CqtxiFSs.js.map +1 -0
  32. package/dist/v2/components/A2UIMessageRenderer.d.ts +9 -0
  33. package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -0
  34. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +16 -0
  35. package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -0
  36. package/dist/v2/components/CopilotKitInspector.vue.d.ts +7 -0
  37. package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -0
  38. package/dist/v2/components/InlineFeatureWarning.vue.d.ts +6 -0
  39. package/dist/v2/components/InlineFeatureWarning.vue.d.ts.map +1 -0
  40. package/dist/v2/components/LicenseWarningBanner.vue.d.ts +18 -0
  41. package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -0
  42. package/dist/v2/components/MCPAppsActivityRenderer.d.ts +88 -0
  43. package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -0
  44. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +154 -0
  45. package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -0
  46. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +19 -0
  47. package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -0
  48. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +16 -0
  49. package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -0
  50. package/dist/v2/components/a2ui/VueSurface.d.ts +62 -0
  51. package/dist/v2/components/a2ui/VueSurface.d.ts.map +1 -0
  52. package/dist/v2/components/a2ui/adapter.d.ts +38 -0
  53. package/dist/v2/components/a2ui/adapter.d.ts.map +1 -0
  54. package/dist/v2/components/a2ui/catalog.d.ts +29 -0
  55. package/dist/v2/components/a2ui/catalog.d.ts.map +1 -0
  56. package/dist/v2/components/a2ui/index.d.ts +5 -0
  57. package/dist/v2/components/a2ui/index.d.ts.map +1 -0
  58. package/dist/v2/components/a2ui/utils.d.ts +18 -0
  59. package/dist/v2/components/a2ui/utils.d.ts.map +1 -0
  60. package/dist/v2/components/a2ui.d.ts +12 -0
  61. package/dist/v2/components/a2ui.d.ts.map +1 -0
  62. package/dist/v2/components/chat/CopilotChat.vue.d.ts +50 -0
  63. package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -0
  64. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +164 -0
  65. package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -0
  66. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts +12 -0
  67. package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -0
  68. package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts +7 -0
  69. package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -0
  70. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +12 -0
  71. package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -0
  72. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +290 -0
  73. package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -0
  74. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +72 -0
  75. package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -0
  76. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts +65 -0
  77. package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -0
  78. package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts +27 -0
  79. package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts.map +1 -0
  80. package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts +26 -0
  81. package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts.map +1 -0
  82. package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts +17 -0
  83. package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts.map +1 -0
  84. package/dist/v2/components/chat/CopilotChatToggleButtonCloseIcon.d.ts +5 -0
  85. package/dist/v2/components/chat/CopilotChatToggleButtonCloseIcon.d.ts.map +1 -0
  86. package/dist/v2/components/chat/CopilotChatToggleButtonOpenIcon.d.ts +5 -0
  87. package/dist/v2/components/chat/CopilotChatToggleButtonOpenIcon.d.ts.map +1 -0
  88. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +21 -0
  89. package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -0
  90. package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts +34 -0
  91. package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -0
  92. package/dist/v2/components/chat/CopilotChatView.vue.d.ts +106 -0
  93. package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -0
  94. package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts +15 -0
  95. package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -0
  96. package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts +5 -0
  97. package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts.map +1 -0
  98. package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts +5 -0
  99. package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts.map +1 -0
  100. package/dist/v2/components/chat/CopilotPopup.vue.d.ts +50 -0
  101. package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -0
  102. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +55 -0
  103. package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -0
  104. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +55 -0
  105. package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -0
  106. package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts +28 -0
  107. package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -0
  108. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +48 -0
  109. package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -0
  110. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +62 -0
  111. package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -0
  112. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +53 -0
  113. package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -0
  114. package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts +28 -0
  115. package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -0
  116. package/dist/v2/components/chat/audioRecorder.d.ts +11 -0
  117. package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -0
  118. package/dist/v2/components/chat/index.d.ts +682 -0
  119. package/dist/v2/components/chat/index.d.ts.map +1 -0
  120. package/dist/v2/components/chat/last-user-message-context.d.ts +29 -0
  121. package/dist/v2/components/chat/last-user-message-context.d.ts.map +1 -0
  122. package/dist/v2/components/chat/normalize-auto-scroll.d.ts +3 -0
  123. package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +1 -0
  124. package/dist/v2/components/chat/types.d.ts +380 -0
  125. package/dist/v2/components/chat/types.d.ts.map +1 -0
  126. package/dist/v2/components/icons/index.d.ts +2 -0
  127. package/dist/v2/components/icons/index.d.ts.map +1 -0
  128. package/dist/v2/components/index.d.ts +8 -0
  129. package/dist/v2/components/index.d.ts.map +1 -0
  130. package/dist/v2/hooks/index.d.ts +24 -0
  131. package/dist/v2/hooks/index.d.ts.map +1 -0
  132. package/dist/v2/hooks/use-agent-context.d.ts +24 -0
  133. package/dist/v2/hooks/use-agent-context.d.ts.map +1 -0
  134. package/dist/v2/hooks/use-agent.d.ts +53 -0
  135. package/dist/v2/hooks/use-agent.d.ts.map +1 -0
  136. package/dist/v2/hooks/use-attachments.d.ts +21 -0
  137. package/dist/v2/hooks/use-attachments.d.ts.map +1 -0
  138. package/dist/v2/hooks/use-capabilities.d.ts +16 -0
  139. package/dist/v2/hooks/use-capabilities.d.ts.map +1 -0
  140. package/dist/v2/hooks/use-component.d.ts +13 -0
  141. package/dist/v2/hooks/use-component.d.ts.map +1 -0
  142. package/dist/v2/hooks/use-configure-suggestions.d.ts +24 -0
  143. package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -0
  144. package/dist/v2/hooks/use-default-render-tool.d.ts +14 -0
  145. package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -0
  146. package/dist/v2/hooks/use-frontend-tool.d.ts +19 -0
  147. package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -0
  148. package/dist/v2/hooks/use-human-in-the-loop.d.ts +19 -0
  149. package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -0
  150. package/dist/v2/hooks/use-interrupt.d.ts +36 -0
  151. package/dist/v2/hooks/use-interrupt.d.ts.map +1 -0
  152. package/dist/v2/hooks/use-katex-styles.d.ts +22 -0
  153. package/dist/v2/hooks/use-katex-styles.d.ts.map +1 -0
  154. package/dist/v2/hooks/use-keyboard-height.d.ts +33 -0
  155. package/dist/v2/hooks/use-keyboard-height.d.ts.map +1 -0
  156. package/dist/v2/hooks/use-pin-to-send.d.ts +28 -0
  157. package/dist/v2/hooks/use-pin-to-send.d.ts.map +1 -0
  158. package/dist/v2/hooks/use-render-activity-message.d.ts +21 -0
  159. package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -0
  160. package/dist/v2/hooks/use-render-custom-messages.d.ts +27 -0
  161. package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -0
  162. package/dist/v2/hooks/use-render-tool.d.ts +36 -0
  163. package/dist/v2/hooks/use-render-tool.d.ts.map +1 -0
  164. package/dist/v2/hooks/use-suggestions.d.ts +26 -0
  165. package/dist/v2/hooks/use-suggestions.d.ts.map +1 -0
  166. package/dist/v2/hooks/use-threads.d.ts +42 -0
  167. package/dist/v2/hooks/use-threads.d.ts.map +1 -0
  168. package/dist/v2/index.cjs +2 -0
  169. package/dist/v2/index.cjs.map +1 -0
  170. package/dist/v2/index.d.cts +9 -0
  171. package/dist/v2/index.d.mts +9 -0
  172. package/dist/v2/index.d.ts +9 -0
  173. package/dist/v2/index.d.ts.map +1 -0
  174. package/dist/v2/index.mjs +75 -0
  175. package/dist/v2/index.mjs.map +1 -0
  176. package/dist/v2/lib/processPartialHtml.d.ts +3 -0
  177. package/dist/v2/lib/processPartialHtml.d.ts.map +1 -0
  178. package/dist/v2/lib/shallow-stable.d.ts +7 -0
  179. package/dist/v2/lib/shallow-stable.d.ts.map +1 -0
  180. package/dist/v2/lib/transcription-client.d.ts +19 -0
  181. package/dist/v2/lib/transcription-client.d.ts.map +1 -0
  182. package/dist/v2/lib/vue-core.d.ts +47 -0
  183. package/dist/v2/lib/vue-core.d.ts.map +1 -0
  184. package/dist/v2/providers/CopilotChatConfigurationProvider.types.d.ts +15 -0
  185. package/dist/v2/providers/CopilotChatConfigurationProvider.types.d.ts.map +1 -0
  186. package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts +17 -0
  187. package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -0
  188. package/dist/v2/providers/CopilotKitProvider.types.d.ts +61 -0
  189. package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -0
  190. package/dist/v2/providers/CopilotKitProvider.vue.d.ts +37 -0
  191. package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -0
  192. package/dist/v2/providers/SandboxFunctionsContext.d.ts +4 -0
  193. package/dist/v2/providers/SandboxFunctionsContext.d.ts.map +1 -0
  194. package/dist/v2/providers/index.d.ts +13 -0
  195. package/dist/v2/providers/index.d.ts.map +1 -0
  196. package/dist/v2/providers/keys.d.ts +17 -0
  197. package/dist/v2/providers/keys.d.ts.map +1 -0
  198. package/dist/v2/providers/license-context.d.ts +7 -0
  199. package/dist/v2/providers/license-context.d.ts.map +1 -0
  200. package/dist/v2/providers/types.d.ts +38 -0
  201. package/dist/v2/providers/types.d.ts.map +1 -0
  202. package/dist/v2/providers/useCopilotChatConfiguration.d.ts +4 -0
  203. package/dist/v2/providers/useCopilotChatConfiguration.d.ts.map +1 -0
  204. package/dist/v2/providers/useCopilotKit.d.ts +2 -0
  205. package/dist/v2/providers/useCopilotKit.d.ts.map +1 -0
  206. package/dist/v2/providers/useLicenseContext.d.ts +14 -0
  207. package/dist/v2/providers/useLicenseContext.d.ts.map +1 -0
  208. package/dist/v2/types/a2ui.d.ts +5 -0
  209. package/dist/v2/types/a2ui.d.ts.map +1 -0
  210. package/dist/v2/types/defineToolCallRenderer.d.ts +15 -0
  211. package/dist/v2/types/defineToolCallRenderer.d.ts.map +1 -0
  212. package/dist/v2/types/frontend-tool.d.ts +6 -0
  213. package/dist/v2/types/frontend-tool.d.ts.map +1 -0
  214. package/dist/v2/types/human-in-the-loop.d.ts +29 -0
  215. package/dist/v2/types/human-in-the-loop.d.ts.map +1 -0
  216. package/dist/v2/types/index.d.ts +10 -0
  217. package/dist/v2/types/index.d.ts.map +1 -0
  218. package/dist/v2/types/interrupt.d.ts +14 -0
  219. package/dist/v2/types/interrupt.d.ts.map +1 -0
  220. package/dist/v2/types/sandbox-function.d.ts +8 -0
  221. package/dist/v2/types/sandbox-function.d.ts.map +1 -0
  222. package/dist/v2/types/vue-activity-message-renderer.d.ts +18 -0
  223. package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -0
  224. package/dist/v2/types/vue-custom-message-renderer.d.ts +19 -0
  225. package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -0
  226. package/dist/v2/types/vue-tool-call-renderer.d.ts +37 -0
  227. package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -0
  228. package/env.d.ts +7 -0
  229. package/eslint.config.mjs +42 -0
  230. package/package.json +130 -0
  231. package/scripts/scope-preflight.mjs +100 -0
  232. package/src/components/copilot-provider/CopilotKit.vue +18 -0
  233. package/src/components/copilot-provider/index.ts +2 -0
  234. package/src/components/copilot-provider/types.ts +24 -0
  235. package/src/hooks/index.ts +9 -0
  236. package/src/hooks/use-copilot-action.ts +168 -0
  237. package/src/hooks/use-copilot-readable.ts +75 -0
  238. package/src/hooks/use-frontend-tool.ts +76 -0
  239. package/src/index.ts +12 -0
  240. package/src/styles/globals.css +314 -0
  241. package/src/v2/__tests__/exports.test.ts +35 -0
  242. package/src/v2/__tests__/mocks/web-inspector.ts +5 -0
  243. package/src/v2/__tests__/setup.ts +141 -0
  244. package/src/v2/__tests__/utils/agents.ts +391 -0
  245. package/src/v2/__tests__/utils/mount.ts +83 -0
  246. package/src/v2/__tests__/utils/test-helpers.ts +712 -0
  247. package/src/v2/components/A2UIMessageRenderer.ts +125 -0
  248. package/src/v2/components/A2UISurfaceActivityRenderer.vue +186 -0
  249. package/src/v2/components/CopilotKitInspector.vue +42 -0
  250. package/src/v2/components/InlineFeatureWarning.vue +35 -0
  251. package/src/v2/components/LicenseWarningBanner.vue +196 -0
  252. package/src/v2/components/MCPAppsActivityRenderer.ts +778 -0
  253. package/src/v2/components/OpenGenerativeUIRenderer.ts +550 -0
  254. package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +271 -0
  255. package/src/v2/components/__tests__/CopilotKitInspector.test.ts +57 -0
  256. package/src/v2/components/__tests__/MCPAppsActivityRenderer.e2e.test.ts +851 -0
  257. package/src/v2/components/__tests__/MCPAppsActivityRenderer.test.ts +237 -0
  258. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +516 -0
  259. package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +295 -0
  260. package/src/v2/components/a2ui/A2UICatalogContext.ts +190 -0
  261. package/src/v2/components/a2ui/VueSurface.ts +144 -0
  262. package/src/v2/components/a2ui/adapter.ts +156 -0
  263. package/src/v2/components/a2ui/catalog.ts +858 -0
  264. package/src/v2/components/a2ui/index.ts +7 -0
  265. package/src/v2/components/a2ui/utils.ts +67 -0
  266. package/src/v2/components/a2ui.ts +30 -0
  267. package/src/v2/components/chat/CopilotChat.vue +777 -0
  268. package/src/v2/components/chat/CopilotChatAssistantMessage.vue +891 -0
  269. package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +411 -0
  270. package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +87 -0
  271. package/src/v2/components/chat/CopilotChatAudioRecorder.vue +269 -0
  272. package/src/v2/components/chat/CopilotChatInput.vue +1271 -0
  273. package/src/v2/components/chat/CopilotChatMessageView.vue +476 -0
  274. package/src/v2/components/chat/CopilotChatReasoningMessage.vue +247 -0
  275. package/src/v2/components/chat/CopilotChatSuggestionPill.vue +56 -0
  276. package/src/v2/components/chat/CopilotChatSuggestionView.vue +93 -0
  277. package/src/v2/components/chat/CopilotChatToggleButton.vue +145 -0
  278. package/src/v2/components/chat/CopilotChatToggleButtonCloseIcon.ts +17 -0
  279. package/src/v2/components/chat/CopilotChatToggleButtonOpenIcon.ts +18 -0
  280. package/src/v2/components/chat/CopilotChatToolCallsView.vue +161 -0
  281. package/src/v2/components/chat/CopilotChatUserMessage.vue +322 -0
  282. package/src/v2/components/chat/CopilotChatView.vue +740 -0
  283. package/src/v2/components/chat/CopilotModalHeader.vue +73 -0
  284. package/src/v2/components/chat/CopilotModalHeaderCloseButton.ts +38 -0
  285. package/src/v2/components/chat/CopilotModalHeaderTitle.ts +22 -0
  286. package/src/v2/components/chat/CopilotPopup.vue +182 -0
  287. package/src/v2/components/chat/CopilotPopupView.vue +168 -0
  288. package/src/v2/components/chat/CopilotPopupViewInternal.vue +453 -0
  289. package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +140 -0
  290. package/src/v2/components/chat/CopilotSidebar.vue +178 -0
  291. package/src/v2/components/chat/CopilotSidebarView.vue +172 -0
  292. package/src/v2/components/chat/CopilotSidebarViewInternal.vue +366 -0
  293. package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +142 -0
  294. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.ts +237 -0
  295. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +1240 -0
  296. package/src/v2/components/chat/__tests__/CopilotChat.licenseWarning.test.ts +138 -0
  297. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.ts +85 -0
  298. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.ts +141 -0
  299. package/src/v2/components/chat/__tests__/CopilotChat.test.ts +652 -0
  300. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +683 -0
  301. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.ts +768 -0
  302. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +1108 -0
  303. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.ts +87 -0
  304. package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +277 -0
  305. package/src/v2/components/chat/__tests__/CopilotChatAttachmentRenderer.test.ts +124 -0
  306. package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.ts +230 -0
  307. package/src/v2/components/chat/__tests__/CopilotChatInput.bottomAnchored.test.ts +83 -0
  308. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.ts +1139 -0
  309. package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +1051 -0
  310. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.ts +141 -0
  311. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +494 -0
  312. package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.ts +181 -0
  313. package/src/v2/components/chat/__tests__/CopilotChatReasoningMessage.test.ts +73 -0
  314. package/src/v2/components/chat/__tests__/CopilotChatSuggestionPill.test.ts +73 -0
  315. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.ts +674 -0
  316. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.test.ts +91 -0
  317. package/src/v2/components/chat/__tests__/CopilotChatToggleButton.test.ts +93 -0
  318. package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.test.ts +382 -0
  319. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.ts +1019 -0
  320. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.ts +516 -0
  321. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.ts +701 -0
  322. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.test.ts +337 -0
  323. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.ts +135 -0
  324. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.ts +278 -0
  325. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.ts +1082 -0
  326. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.ts +166 -0
  327. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.ts +1145 -0
  328. package/src/v2/components/chat/__tests__/CopilotChatView.test.ts +374 -0
  329. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.ts +636 -0
  330. package/src/v2/components/chat/__tests__/CopilotModalHeader.test.ts +112 -0
  331. package/src/v2/components/chat/__tests__/CopilotPopup.test.ts +58 -0
  332. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.ts +725 -0
  333. package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +112 -0
  334. package/src/v2/components/chat/__tests__/CopilotSidebar.test.ts +58 -0
  335. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.ts +603 -0
  336. package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +214 -0
  337. package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.ts +394 -0
  338. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.ts +82 -0
  339. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +39 -0
  340. package/src/v2/components/chat/audioRecorder.ts +15 -0
  341. package/src/v2/components/chat/index.ts +52 -0
  342. package/src/v2/components/chat/last-user-message-context.ts +39 -0
  343. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  344. package/src/v2/components/chat/types.ts +481 -0
  345. package/src/v2/components/icons/__tests__/icons.test.ts +86 -0
  346. package/src/v2/components/icons/index.ts +22 -0
  347. package/src/v2/components/index.ts +7 -0
  348. package/src/v2/hooks/__tests__/standard-schema-types.test.ts +149 -0
  349. package/src/v2/hooks/__tests__/standard-schema.test.ts +315 -0
  350. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.ts +144 -0
  351. package/src/v2/hooks/__tests__/use-agent-context.test.ts +271 -0
  352. package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +64 -0
  353. package/src/v2/hooks/__tests__/use-agent-stability.test.ts +268 -0
  354. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.ts +433 -0
  355. package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +747 -0
  356. package/src/v2/hooks/__tests__/use-agent.e2e.test.ts +187 -0
  357. package/src/v2/hooks/__tests__/use-agent.test.ts +126 -0
  358. package/src/v2/hooks/__tests__/use-attachments.test.ts +181 -0
  359. package/src/v2/hooks/__tests__/use-component.test.ts +145 -0
  360. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.ts +527 -0
  361. package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +399 -0
  362. package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +214 -0
  363. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.ts +220 -0
  364. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +2320 -0
  365. package/src/v2/hooks/__tests__/use-frontend-tool.test.ts +648 -0
  366. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +1379 -0
  367. package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +282 -0
  368. package/src/v2/hooks/__tests__/use-interrupt.test.ts +345 -0
  369. package/src/v2/hooks/__tests__/use-katex-styles.test.ts +69 -0
  370. package/src/v2/hooks/__tests__/use-keyboard-height.test.ts +199 -0
  371. package/src/v2/hooks/__tests__/use-pin-to-send.test.ts +363 -0
  372. package/src/v2/hooks/__tests__/use-render-tool.test.ts +329 -0
  373. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.ts +397 -0
  374. package/src/v2/hooks/__tests__/use-suggestions.test.ts +198 -0
  375. package/src/v2/hooks/__tests__/use-threads.test.ts +1041 -0
  376. package/src/v2/hooks/__tests__/zod-regression.test.ts +339 -0
  377. package/src/v2/hooks/index.ts +29 -0
  378. package/src/v2/hooks/use-agent-context.ts +55 -0
  379. package/src/v2/hooks/use-agent.ts +345 -0
  380. package/src/v2/hooks/use-attachments.ts +261 -0
  381. package/src/v2/hooks/use-capabilities.ts +30 -0
  382. package/src/v2/hooks/use-component.ts +46 -0
  383. package/src/v2/hooks/use-configure-suggestions.ts +252 -0
  384. package/src/v2/hooks/use-default-render-tool.ts +130 -0
  385. package/src/v2/hooks/use-frontend-tool.ts +68 -0
  386. package/src/v2/hooks/use-human-in-the-loop.ts +90 -0
  387. package/src/v2/hooks/use-interrupt.ts +257 -0
  388. package/src/v2/hooks/use-katex-styles.ts +44 -0
  389. package/src/v2/hooks/use-keyboard-height.ts +87 -0
  390. package/src/v2/hooks/use-pin-to-send.ts +160 -0
  391. package/src/v2/hooks/use-render-activity-message.ts +92 -0
  392. package/src/v2/hooks/use-render-custom-messages.ts +129 -0
  393. package/src/v2/hooks/use-render-tool.ts +128 -0
  394. package/src/v2/hooks/use-suggestions.ts +98 -0
  395. package/src/v2/hooks/use-threads.ts +208 -0
  396. package/src/v2/index.ts +11 -0
  397. package/src/v2/lib/__tests__/processPartialHtml.test.ts +84 -0
  398. package/src/v2/lib/__tests__/transcription-client.test.ts +65 -0
  399. package/src/v2/lib/processPartialHtml.ts +21 -0
  400. package/src/v2/lib/shallow-stable.ts +54 -0
  401. package/src/v2/lib/transcription-client.ts +151 -0
  402. package/src/v2/lib/vue-core.ts +161 -0
  403. package/src/v2/providers/CopilotChatConfigurationProvider.types.ts +15 -0
  404. package/src/v2/providers/CopilotChatConfigurationProvider.vue +95 -0
  405. package/src/v2/providers/CopilotKitProvider.types.ts +66 -0
  406. package/src/v2/providers/CopilotKitProvider.vue +653 -0
  407. package/src/v2/providers/SandboxFunctionsContext.ts +11 -0
  408. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +309 -0
  409. package/src/v2/providers/__tests__/CopilotKitProvider.debug.test.ts +295 -0
  410. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.ts +110 -0
  411. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.ts +67 -0
  412. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.ts +901 -0
  413. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +141 -0
  414. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.ts +871 -0
  415. package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +603 -0
  416. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.ts +104 -0
  417. package/src/v2/providers/index.ts +21 -0
  418. package/src/v2/providers/keys.ts +25 -0
  419. package/src/v2/providers/license-context.ts +16 -0
  420. package/src/v2/providers/types.ts +40 -0
  421. package/src/v2/providers/useCopilotChatConfiguration.ts +11 -0
  422. package/src/v2/providers/useCopilotKit.ts +11 -0
  423. package/src/v2/providers/useLicenseContext.ts +21 -0
  424. package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +157 -0
  425. package/src/v2/types/a2ui.ts +5 -0
  426. package/src/v2/types/defineToolCallRenderer.ts +32 -0
  427. package/src/v2/types/frontend-tool.ts +8 -0
  428. package/src/v2/types/human-in-the-loop.ts +38 -0
  429. package/src/v2/types/index.ts +9 -0
  430. package/src/v2/types/interrupt.ts +15 -0
  431. package/src/v2/types/sandbox-function.ts +8 -0
  432. package/src/v2/types/vue-activity-message-renderer.ts +22 -0
  433. package/src/v2/types/vue-custom-message-renderer.ts +24 -0
  434. package/src/v2/types/vue-tool-call-renderer.ts +44 -0
  435. package/tsconfig.json +27 -0
  436. package/vite.config.ts +49 -0
  437. package/vitest.config.ts +23 -0
@@ -0,0 +1,1379 @@
1
+ import { defineComponent, ref, watch } from "vue";
2
+ import type { PropType } from "vue";
3
+ import { screen, fireEvent, waitFor, cleanup } from "@testing-library/vue";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import { z } from "zod";
6
+ import type { AssistantMessage, Message } from "@ag-ui/core";
7
+ import { ToolCallStatus } from "@copilotkit/core";
8
+ import CopilotChat from "../../components/chat/CopilotChat.vue";
9
+ import CopilotChatToolCallsView from "../../components/chat/CopilotChatToolCallsView.vue";
10
+ import { useHumanInTheLoop } from "../use-human-in-the-loop";
11
+ import {
12
+ MockStepwiseAgent,
13
+ MockReconnectableAgent,
14
+ renderWithCopilotKit,
15
+ runStartedEvent,
16
+ runFinishedEvent,
17
+ toolCallChunkEvent,
18
+ testId,
19
+ } from "../../__tests__/utils/test-helpers";
20
+
21
+ afterEach(() => {
22
+ cleanup();
23
+ });
24
+
25
+ async function submitMessage(value: string) {
26
+ const input = await screen.findByRole("textbox");
27
+ await fireEvent.update(input, value);
28
+ await fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
29
+ await waitFor(() => {
30
+ expect(screen.getByText(value)).toBeDefined();
31
+ });
32
+ }
33
+
34
+ /**
35
+ * Wait for any in-flight connect cycle to finish. When a
36
+ * MockReconnectableAgent is used, AbstractAgent.connectAgent sets
37
+ * isRunning = true for the duration of the connect Observable. The chat
38
+ * input treats Enter as "stop" (not "submit") while isRunning is true, so
39
+ * we must wait for the connect to settle before submitting a message.
40
+ */
41
+ async function waitForConnectCycleToSettle() {
42
+ // connect() returns from([]).pipe(delay(10)) → ~10ms Observable lifetime
43
+ // plus requestAnimationFrame (setTimeout 16ms in jsdom) in the CopilotChat
44
+ // connect watch finally block.
45
+ await new Promise((r) => setTimeout(r, 50));
46
+ // Let Vue flush any reactive updates triggered by the cycle completing.
47
+ await waitFor(() => {});
48
+ }
49
+
50
+ function createChatHost(registrar: ReturnType<typeof defineComponent>) {
51
+ return defineComponent({
52
+ components: {
53
+ RegisteredComponent: registrar,
54
+ CopilotChat,
55
+ },
56
+ template: `
57
+ <div>
58
+ <RegisteredComponent />
59
+ <div style="height: 400px;">
60
+ <CopilotChat :welcome-screen="false" />
61
+ </div>
62
+ </div>
63
+ `,
64
+ });
65
+ }
66
+
67
+ describe("useHumanInTheLoop E2E - HITL Tool Rendering", () => {
68
+ describe("HITL Renderer with Status Transitions", () => {
69
+ it("should show InProgress → Complete transitions for HITL tool", async () => {
70
+ const agent = new MockStepwiseAgent();
71
+ const statusHistory: ToolCallStatus[] = [];
72
+
73
+ const HITLRenderer = defineComponent({
74
+ props: {
75
+ name: { type: String, required: true },
76
+ description: { type: String, required: true },
77
+ status: { type: String as PropType<ToolCallStatus>, required: true },
78
+ args: {
79
+ type: Object as PropType<{ action?: string; reason?: string }>,
80
+ required: true,
81
+ },
82
+ result: { type: String, required: false },
83
+ respond: {
84
+ type: Function as PropType<
85
+ ((result: unknown) => Promise<void>) | undefined
86
+ >,
87
+ required: false,
88
+ },
89
+ },
90
+ setup(props) {
91
+ watch(
92
+ () => props.status,
93
+ (status) => {
94
+ if (statusHistory[statusHistory.length - 1] !== status) {
95
+ statusHistory.push(status);
96
+ }
97
+ },
98
+ { immediate: true, flush: "post" },
99
+ );
100
+ return {};
101
+ },
102
+ template: `
103
+ <div data-testid="hitl-tool">
104
+ <div data-testid="hitl-name">{{ name }}</div>
105
+ <div data-testid="hitl-description">{{ description }}</div>
106
+ <div data-testid="hitl-status">{{ status }}</div>
107
+ <div data-testid="hitl-action">{{ args.action ?? "" }}</div>
108
+ <div data-testid="hitl-reason">{{ args.reason ?? "" }}</div>
109
+ <button
110
+ v-if="respond"
111
+ data-testid="hitl-approve"
112
+ @click="respond(JSON.stringify({ approved: true }))"
113
+ >
114
+ Approve
115
+ </button>
116
+ <div v-if="result" data-testid="hitl-result">{{ result }}</div>
117
+ </div>
118
+ `,
119
+ });
120
+
121
+ const HITLComponent = defineComponent({
122
+ setup() {
123
+ const hitlTool = {
124
+ name: "approvalTool",
125
+ description: "Requires human approval",
126
+ parameters: z.object({
127
+ action: z.string(),
128
+ reason: z.string(),
129
+ }),
130
+ render: HITLRenderer,
131
+ };
132
+ useHumanInTheLoop(hitlTool);
133
+ return {};
134
+ },
135
+ template: `<div />`,
136
+ });
137
+
138
+ renderWithCopilotKit({
139
+ agent,
140
+ children: createChatHost(HITLComponent),
141
+ });
142
+
143
+ await submitMessage("Request approval");
144
+
145
+ const messageId = testId("msg");
146
+ const toolCallId = testId("tc");
147
+
148
+ await agent.emit(runStartedEvent());
149
+ await agent.emit(
150
+ toolCallChunkEvent({
151
+ toolCallId,
152
+ toolCallName: "approvalTool",
153
+ parentMessageId: messageId,
154
+ delta: JSON.stringify({ action: "delete", reason: "cleanup" }),
155
+ }),
156
+ );
157
+
158
+ await waitFor(() => {
159
+ expect(screen.getByTestId("hitl-status").textContent).toBe(
160
+ ToolCallStatus.InProgress,
161
+ );
162
+ expect(screen.getByTestId("hitl-action").textContent).toBe("delete");
163
+ expect(screen.getByTestId("hitl-reason").textContent).toBe("cleanup");
164
+ });
165
+
166
+ await agent.emit(runFinishedEvent());
167
+ await agent.complete();
168
+
169
+ const approveButton = await screen.findByTestId("hitl-approve");
170
+ expect(screen.getByTestId("hitl-status").textContent).toBe(
171
+ ToolCallStatus.Executing,
172
+ );
173
+
174
+ await fireEvent.click(approveButton);
175
+
176
+ await waitFor(() => {
177
+ expect(screen.getByTestId("hitl-status").textContent).toBe(
178
+ ToolCallStatus.Complete,
179
+ );
180
+ expect(screen.getByTestId("hitl-result").textContent).toContain(
181
+ "approved",
182
+ );
183
+ expect(statusHistory).toEqual([
184
+ ToolCallStatus.InProgress,
185
+ ToolCallStatus.Executing,
186
+ ToolCallStatus.Complete,
187
+ ]);
188
+ });
189
+ });
190
+ });
191
+
192
+ describe("HITL with Interactive Respond", () => {
193
+ it("should handle interactive respond callback during Executing state", async () => {
194
+ const agent = new MockStepwiseAgent();
195
+ const respondSelections: string[] = [];
196
+
197
+ const InteractiveRenderer = defineComponent({
198
+ props: {
199
+ name: { type: String, required: true },
200
+ status: { type: String as PropType<ToolCallStatus>, required: true },
201
+ args: {
202
+ type: Object as PropType<{ question?: string; options?: string[] }>,
203
+ required: true,
204
+ },
205
+ result: { type: String, required: false },
206
+ respond: {
207
+ type: Function as PropType<
208
+ ((result: unknown) => Promise<void>) | undefined
209
+ >,
210
+ required: false,
211
+ },
212
+ },
213
+ setup(props) {
214
+ const respondYes = () => {
215
+ respondSelections.push("yes");
216
+ if (props.respond) {
217
+ void props.respond(JSON.stringify({ answer: "yes" }));
218
+ }
219
+ };
220
+
221
+ const respondNo = () => {
222
+ respondSelections.push("no");
223
+ if (props.respond) {
224
+ void props.respond(JSON.stringify({ answer: "no" }));
225
+ }
226
+ };
227
+
228
+ return { ToolCallStatus, respondYes, respondNo };
229
+ },
230
+ template: `
231
+ <div data-testid="interactive-hitl">
232
+ <div data-testid="interactive-name">{{ name }}</div>
233
+ <div data-testid="interactive-status">{{ status }}</div>
234
+ <div data-testid="interactive-question">
235
+ {{ args.question ?? "" }}
236
+ </div>
237
+ <div data-testid="interactive-options">
238
+ {{ args.options?.join(", ") ?? "" }}
239
+ </div>
240
+
241
+ <div
242
+ v-if="status === ToolCallStatus.Executing && respond"
243
+ data-testid="respond-section"
244
+ >
245
+ <button
246
+ data-testid="respond-yes"
247
+ @click="respondYes"
248
+ >
249
+ Respond Yes
250
+ </button>
251
+ <button
252
+ data-testid="respond-no"
253
+ @click="respondNo"
254
+ >
255
+ Respond No
256
+ </button>
257
+ </div>
258
+
259
+ <div v-if="result" data-testid="interactive-result">{{ result }}</div>
260
+ </div>
261
+ `,
262
+ });
263
+
264
+ const InteractiveHITLComponent = defineComponent({
265
+ setup() {
266
+ const hitlTool = {
267
+ name: "interactiveTool",
268
+ description: "Interactive human-in-the-loop tool",
269
+ parameters: z.object({
270
+ question: z.string(),
271
+ options: z.array(z.string()),
272
+ }),
273
+ render: InteractiveRenderer,
274
+ };
275
+
276
+ useHumanInTheLoop(hitlTool);
277
+ return { respondSelections };
278
+ },
279
+ template: `<div />`,
280
+ });
281
+
282
+ renderWithCopilotKit({
283
+ agent,
284
+ children: createChatHost(InteractiveHITLComponent),
285
+ });
286
+
287
+ await submitMessage("Interactive question");
288
+
289
+ const messageId = testId("msg");
290
+ const toolCallId = testId("tc");
291
+
292
+ await agent.emit(runStartedEvent());
293
+ await agent.emit(
294
+ toolCallChunkEvent({
295
+ toolCallId,
296
+ toolCallName: "interactiveTool",
297
+ parentMessageId: messageId,
298
+ delta: JSON.stringify({
299
+ question: "Proceed with operation?",
300
+ options: ["yes", "no"],
301
+ }),
302
+ }),
303
+ );
304
+
305
+ await waitFor(() => {
306
+ expect(
307
+ screen.getByTestId("interactive-question").textContent,
308
+ ).toContain("Proceed with operation?");
309
+ expect(screen.getByTestId("interactive-options").textContent).toContain(
310
+ "yes",
311
+ );
312
+ expect(screen.getByTestId("interactive-options").textContent).toContain(
313
+ "no",
314
+ );
315
+ });
316
+
317
+ await agent.emit(runFinishedEvent());
318
+ await agent.complete();
319
+
320
+ await waitFor(() => {
321
+ expect(screen.getByTestId("interactive-status").textContent).toBe(
322
+ ToolCallStatus.Executing,
323
+ );
324
+ expect(screen.getByTestId("respond-section")).toBeDefined();
325
+ });
326
+
327
+ await fireEvent.click(screen.getByTestId("respond-yes"));
328
+
329
+ await waitFor(() => {
330
+ expect(screen.getByTestId("interactive-status").textContent).toBe(
331
+ ToolCallStatus.Complete,
332
+ );
333
+ expect(screen.getByTestId("interactive-result").textContent).toContain(
334
+ "yes",
335
+ );
336
+ });
337
+
338
+ expect(respondSelections).toEqual(["yes"]);
339
+ });
340
+ });
341
+
342
+ describe("Multiple HITL Tools", () => {
343
+ it("should handle multiple HITL tools registered simultaneously", async () => {
344
+ const agent = new MockStepwiseAgent();
345
+
346
+ const ReviewRenderer = defineComponent({
347
+ props: {
348
+ name: { type: String, required: true },
349
+ description: { type: String, required: true },
350
+ status: { type: String as PropType<ToolCallStatus>, required: true },
351
+ args: {
352
+ type: Object as PropType<{ changes?: string[] }>,
353
+ required: true,
354
+ },
355
+ },
356
+ template: `
357
+ <div data-testid="review-tool">
358
+ {{ name }} - {{ description }} | Status: {{ status }} | Changes:
359
+ {{ args.changes?.length ?? 0 }}
360
+ </div>
361
+ `,
362
+ });
363
+
364
+ const ConfirmRenderer = defineComponent({
365
+ props: {
366
+ name: { type: String, required: true },
367
+ description: { type: String, required: true },
368
+ status: { type: String as PropType<ToolCallStatus>, required: true },
369
+ args: {
370
+ type: Object as PropType<{ action?: string }>,
371
+ required: true,
372
+ },
373
+ },
374
+ template: `
375
+ <div data-testid="confirm-tool">
376
+ {{ name }} - {{ description }} | Status: {{ status }} | Action:
377
+ {{ args.action ?? "" }}
378
+ </div>
379
+ `,
380
+ });
381
+
382
+ const MultipleHITLComponent = defineComponent({
383
+ setup() {
384
+ const reviewTool = {
385
+ name: "reviewTool",
386
+ description: "Review changes",
387
+ parameters: z.object({ changes: z.array(z.string()) }),
388
+ render: ReviewRenderer,
389
+ };
390
+
391
+ const confirmTool = {
392
+ name: "confirmTool",
393
+ description: "Confirm action",
394
+ parameters: z.object({ action: z.string() }),
395
+ render: ConfirmRenderer,
396
+ };
397
+
398
+ useHumanInTheLoop(reviewTool);
399
+ useHumanInTheLoop(confirmTool);
400
+ return {};
401
+ },
402
+ template: `<div />`,
403
+ });
404
+
405
+ renderWithCopilotKit({
406
+ agent,
407
+ children: createChatHost(MultipleHITLComponent),
408
+ });
409
+
410
+ await submitMessage("Multiple HITL");
411
+
412
+ const messageId = testId("msg");
413
+ const toolCallId1 = testId("tc1");
414
+ const toolCallId2 = testId("tc2");
415
+
416
+ await agent.emit(runStartedEvent());
417
+ await agent.emit(
418
+ toolCallChunkEvent({
419
+ toolCallId: toolCallId1,
420
+ toolCallName: "reviewTool",
421
+ parentMessageId: messageId,
422
+ delta: JSON.stringify({ changes: ["file1.ts", "file2.ts"] }),
423
+ }),
424
+ );
425
+ await agent.emit(
426
+ toolCallChunkEvent({
427
+ toolCallId: toolCallId2,
428
+ toolCallName: "confirmTool",
429
+ parentMessageId: messageId,
430
+ delta: JSON.stringify({ action: "deploy" }),
431
+ }),
432
+ );
433
+
434
+ await waitFor(() => {
435
+ const reviewTool = screen.getByTestId("review-tool");
436
+ const confirmTool = screen.getByTestId("confirm-tool");
437
+ expect(reviewTool.textContent).toContain("Changes: 2");
438
+ expect(confirmTool.textContent).toContain("Action: deploy");
439
+ expect(reviewTool.textContent).toContain(ToolCallStatus.InProgress);
440
+ expect(confirmTool.textContent).toContain(ToolCallStatus.InProgress);
441
+ });
442
+
443
+ await agent.emit(runFinishedEvent());
444
+ await agent.complete();
445
+ });
446
+ });
447
+
448
+ describe("Multiple Hook Instances", () => {
449
+ it("should isolate state across two useHumanInTheLoop registrations", async () => {
450
+ const agent = new MockStepwiseAgent();
451
+
452
+ const PrimaryRenderer = defineComponent({
453
+ props: {
454
+ status: { type: String as PropType<ToolCallStatus>, required: true },
455
+ args: {
456
+ type: Object as PropType<{ action?: string }>,
457
+ required: true,
458
+ },
459
+ result: { type: String, required: false },
460
+ respond: {
461
+ type: Function as PropType<
462
+ ((result: unknown) => Promise<void>) | undefined
463
+ >,
464
+ required: false,
465
+ },
466
+ },
467
+ template: `
468
+ <div data-testid="primary-tool">
469
+ <div data-testid="primary-status">{{ status }}</div>
470
+ <div data-testid="primary-action">{{ args.action ?? "" }}</div>
471
+ <button
472
+ v-if="respond"
473
+ data-testid="primary-respond"
474
+ @click="respond(JSON.stringify({ approved: true }))"
475
+ >
476
+ Respond Primary
477
+ </button>
478
+ <div v-if="result" data-testid="primary-result">{{ result }}</div>
479
+ </div>
480
+ `,
481
+ });
482
+
483
+ const SecondaryRenderer = defineComponent({
484
+ props: {
485
+ status: { type: String as PropType<ToolCallStatus>, required: true },
486
+ args: {
487
+ type: Object as PropType<{ detail?: string }>,
488
+ required: true,
489
+ },
490
+ result: { type: String, required: false },
491
+ respond: {
492
+ type: Function as PropType<
493
+ ((result: unknown) => Promise<void>) | undefined
494
+ >,
495
+ required: false,
496
+ },
497
+ },
498
+ template: `
499
+ <div data-testid="secondary-tool">
500
+ <div data-testid="secondary-status">{{ status }}</div>
501
+ <div data-testid="secondary-detail">{{ args.detail ?? "" }}</div>
502
+ <button
503
+ v-if="respond"
504
+ data-testid="secondary-respond"
505
+ @click="respond(JSON.stringify({ confirmed: true }))"
506
+ >
507
+ Respond Secondary
508
+ </button>
509
+ <div v-if="result" data-testid="secondary-result">{{ result }}</div>
510
+ </div>
511
+ `,
512
+ });
513
+
514
+ const DualHookComponent = defineComponent({
515
+ setup() {
516
+ const primaryTool = {
517
+ name: "primaryTool",
518
+ description: "Primary approval tool",
519
+ parameters: z.object({ action: z.string() }),
520
+ render: PrimaryRenderer,
521
+ };
522
+
523
+ const secondaryTool = {
524
+ name: "secondaryTool",
525
+ description: "Secondary approval tool",
526
+ parameters: z.object({ detail: z.string() }),
527
+ render: SecondaryRenderer,
528
+ };
529
+
530
+ useHumanInTheLoop(primaryTool);
531
+ useHumanInTheLoop(secondaryTool);
532
+ return {};
533
+ },
534
+ template: `<div />`,
535
+ });
536
+
537
+ renderWithCopilotKit({
538
+ agent,
539
+ children: createChatHost(DualHookComponent),
540
+ });
541
+
542
+ await submitMessage("Dual hook instance");
543
+
544
+ const messageId = testId("msg");
545
+ const primaryToolCallId = testId("tc-primary");
546
+ const secondaryToolCallId = testId("tc-secondary");
547
+
548
+ await agent.emit(runStartedEvent());
549
+ await agent.emit(
550
+ toolCallChunkEvent({
551
+ toolCallId: primaryToolCallId,
552
+ toolCallName: "primaryTool",
553
+ parentMessageId: messageId,
554
+ delta: JSON.stringify({ action: "archive" }),
555
+ }),
556
+ );
557
+ await agent.emit(
558
+ toolCallChunkEvent({
559
+ toolCallId: secondaryToolCallId,
560
+ toolCallName: "secondaryTool",
561
+ parentMessageId: messageId,
562
+ delta: JSON.stringify({ detail: "requires confirmation" }),
563
+ }),
564
+ );
565
+
566
+ await waitFor(() => {
567
+ expect(screen.getByTestId("primary-status").textContent).toBe(
568
+ ToolCallStatus.InProgress,
569
+ );
570
+ expect(screen.getByTestId("primary-action").textContent).toBe(
571
+ "archive",
572
+ );
573
+ expect(screen.getByTestId("secondary-status").textContent).toBe(
574
+ ToolCallStatus.InProgress,
575
+ );
576
+ expect(screen.getByTestId("secondary-detail").textContent).toBe(
577
+ "requires confirmation",
578
+ );
579
+ });
580
+
581
+ await agent.emit(runFinishedEvent());
582
+ await agent.complete();
583
+
584
+ const primaryRespondButton = await screen.findByTestId("primary-respond");
585
+
586
+ expect(screen.getByTestId("primary-status").textContent).toBe(
587
+ ToolCallStatus.Executing,
588
+ );
589
+ expect(screen.getByTestId("secondary-status").textContent).toBe(
590
+ ToolCallStatus.InProgress,
591
+ );
592
+ expect(screen.queryByTestId("secondary-respond")).toBeNull();
593
+
594
+ await fireEvent.click(primaryRespondButton);
595
+
596
+ await waitFor(() => {
597
+ expect(screen.getByTestId("primary-status").textContent).toBe(
598
+ ToolCallStatus.Complete,
599
+ );
600
+ expect(screen.getByTestId("primary-result").textContent).toContain(
601
+ "approved",
602
+ );
603
+ expect(screen.getByTestId("secondary-status").textContent).toBe(
604
+ ToolCallStatus.Executing,
605
+ );
606
+ expect(screen.queryByTestId("secondary-result")).toBeNull();
607
+ });
608
+
609
+ const secondaryRespondButton =
610
+ await screen.findByTestId("secondary-respond");
611
+
612
+ await fireEvent.click(secondaryRespondButton);
613
+
614
+ await waitFor(() => {
615
+ expect(screen.getByTestId("secondary-status").textContent).toBe(
616
+ ToolCallStatus.Complete,
617
+ );
618
+ expect(screen.getByTestId("secondary-result").textContent).toContain(
619
+ "confirmed",
620
+ );
621
+ });
622
+ });
623
+ });
624
+
625
+ describe("HITL Tool with Dynamic Registration", () => {
626
+ it("should support dynamic registration and unregistration of HITL tools", async () => {
627
+ const agent = new MockStepwiseAgent();
628
+
629
+ const DynamicRenderer = defineComponent({
630
+ props: {
631
+ name: { type: String, required: true },
632
+ description: { type: String, required: true },
633
+ args: { type: Object as PropType<{ data?: string }>, required: true },
634
+ },
635
+ template: `
636
+ <div data-testid="dynamic-hitl">
637
+ {{ name }}: {{ description }} | Data: {{ args.data ?? "" }}
638
+ </div>
639
+ `,
640
+ });
641
+
642
+ const DynamicHITLComponent = defineComponent({
643
+ setup() {
644
+ const dynamicHitl = {
645
+ name: "dynamicHitl",
646
+ description: "Dynamically registered HITL",
647
+ parameters: z.object({ data: z.string() }),
648
+ render: DynamicRenderer,
649
+ };
650
+
651
+ useHumanInTheLoop(dynamicHitl);
652
+ return {};
653
+ },
654
+ template: `<div data-testid="hitl-enabled">HITL Enabled</div>`,
655
+ });
656
+
657
+ const TestWrapper = defineComponent({
658
+ components: { DynamicHITLComponent, CopilotChat },
659
+ setup() {
660
+ const enabled = ref(false);
661
+ const toggle = () => {
662
+ enabled.value = !enabled.value;
663
+ };
664
+ return { enabled, toggle };
665
+ },
666
+ template: `
667
+ <div>
668
+ <button data-testid="toggle-hitl" @click="toggle">
669
+ Toggle HITL
670
+ </button>
671
+ <DynamicHITLComponent v-if="enabled" />
672
+ <div style="height: 400px;">
673
+ <CopilotChat :welcome-screen="false" />
674
+ </div>
675
+ </div>
676
+ `,
677
+ });
678
+
679
+ renderWithCopilotKit({
680
+ agent,
681
+ children: TestWrapper,
682
+ });
683
+
684
+ expect(screen.queryByTestId("hitl-enabled")).toBeNull();
685
+
686
+ const toggleButton = screen.getByTestId("toggle-hitl");
687
+ await fireEvent.click(toggleButton);
688
+
689
+ await waitFor(() => {
690
+ expect(screen.getByTestId("hitl-enabled")).toBeDefined();
691
+ });
692
+
693
+ await submitMessage("Test dynamic HITL");
694
+
695
+ const messageId = testId("msg");
696
+ const toolCallId = testId("tc");
697
+
698
+ await agent.emit(runStartedEvent());
699
+ await agent.emit(
700
+ toolCallChunkEvent({
701
+ toolCallId,
702
+ toolCallName: "dynamicHitl",
703
+ parentMessageId: messageId,
704
+ delta: JSON.stringify({ data: "test data" }),
705
+ }),
706
+ );
707
+
708
+ await waitFor(() => {
709
+ const dynamicHitl = screen.getByTestId("dynamic-hitl");
710
+ expect(dynamicHitl.textContent).toContain("dynamicHitl");
711
+ expect(dynamicHitl.textContent).toContain("test data");
712
+ });
713
+
714
+ await agent.emit(runFinishedEvent());
715
+
716
+ await fireEvent.click(toggleButton);
717
+
718
+ await waitFor(() => {
719
+ expect(screen.queryByTestId("hitl-enabled")).toBeNull();
720
+ });
721
+
722
+ await submitMessage("Test after disable");
723
+
724
+ const messageId2 = testId("msg2");
725
+ const toolCallId2 = testId("tc2");
726
+
727
+ await agent.emit(runStartedEvent());
728
+ await agent.emit(
729
+ toolCallChunkEvent({
730
+ toolCallId: toolCallId2,
731
+ toolCallName: "dynamicHitl",
732
+ parentMessageId: messageId2,
733
+ delta: JSON.stringify({ data: "should not render" }),
734
+ }),
735
+ );
736
+
737
+ await waitFor(
738
+ () => {
739
+ const dynamicRenders = screen.queryAllByTestId("dynamic-hitl");
740
+ expect(dynamicRenders.length).toBe(0);
741
+ expect(screen.queryByText(/should not render/)).toBeNull();
742
+ },
743
+ { timeout: 200 },
744
+ );
745
+
746
+ await agent.emit(runFinishedEvent());
747
+ await agent.complete();
748
+ });
749
+ });
750
+
751
+ describe("useHumanInTheLoop dependencies", () => {
752
+ it("updates HITL renderer when optional deps change", async () => {
753
+ const DependencyDrivenHITLComponent = defineComponent({
754
+ components: { CopilotChatToolCallsView },
755
+ setup() {
756
+ const version = ref(0);
757
+
758
+ const hitlTool = {
759
+ name: "dependencyHitlTool",
760
+ description: "Dependency-driven HITL tool",
761
+ parameters: z.object({ message: z.string() }),
762
+ render: defineComponent({
763
+ props: {
764
+ args: {
765
+ type: Object as PropType<{ message?: string }>,
766
+ required: true,
767
+ },
768
+ },
769
+ setup(props) {
770
+ return { props, version };
771
+ },
772
+ template: `
773
+ <div data-testid="dependency-hitl-render">
774
+ {{ props.args.message }} (v{{ version }})
775
+ </div>
776
+ `,
777
+ }),
778
+ };
779
+
780
+ useHumanInTheLoop(hitlTool, [version]);
781
+
782
+ const toolCallId = testId("hitl_dep_tc");
783
+ const assistantMessage: AssistantMessage = {
784
+ id: testId("hitl_dep_a"),
785
+ role: "assistant",
786
+ content: "",
787
+ toolCalls: [
788
+ {
789
+ id: toolCallId,
790
+ type: "function",
791
+ function: {
792
+ name: "dependencyHitlTool",
793
+ arguments: JSON.stringify({ message: "hello" }),
794
+ },
795
+ } as any,
796
+ ],
797
+ } as any;
798
+ const messages: Message[] = [];
799
+
800
+ const bumpVersion = () => {
801
+ version.value += 1;
802
+ };
803
+
804
+ return {
805
+ assistantMessage,
806
+ messages,
807
+ bumpVersion,
808
+ };
809
+ },
810
+ template: `
811
+ <div>
812
+ <button
813
+ data-testid="hitl-bump-version"
814
+ type="button"
815
+ @click="bumpVersion"
816
+ >
817
+ Bump
818
+ </button>
819
+ <CopilotChatToolCallsView
820
+ :message="assistantMessage"
821
+ :messages="messages"
822
+ />
823
+ </div>
824
+ `,
825
+ });
826
+
827
+ renderWithCopilotKit({
828
+ children: DependencyDrivenHITLComponent,
829
+ });
830
+
831
+ await waitFor(() => {
832
+ const el = screen.getByTestId("dependency-hitl-render");
833
+ expect(el).toBeDefined();
834
+ expect(el.textContent).toContain("hello");
835
+ expect(el.textContent).toContain("(v0)");
836
+ });
837
+
838
+ await fireEvent.click(screen.getByTestId("hitl-bump-version"));
839
+
840
+ await waitFor(() => {
841
+ const el = screen.getByTestId("dependency-hitl-render");
842
+ expect(el.textContent).toContain("(v1)");
843
+ });
844
+ });
845
+ });
846
+ });
847
+
848
+ describe("HITL Thread Reconnection Bug", () => {
849
+ it("should show executing status when reconnecting to thread with pending HITL", async () => {
850
+ const agent = new MockReconnectableAgent();
851
+
852
+ const HITLRenderer = defineComponent({
853
+ props: {
854
+ status: { type: String as PropType<ToolCallStatus>, required: true },
855
+ args: { type: Object as PropType<{ action?: string }>, required: true },
856
+ respond: {
857
+ type: Function as PropType<
858
+ ((result: unknown) => Promise<void>) | undefined
859
+ >,
860
+ required: false,
861
+ },
862
+ },
863
+ template: `
864
+ <div data-testid="hitl-tool">
865
+ <div data-testid="hitl-status">{{ status }}</div>
866
+ <div data-testid="hitl-action">{{ args.action ?? "no-action" }}</div>
867
+ <button v-if="respond" data-testid="hitl-respond">Respond</button>
868
+ </div>
869
+ `,
870
+ });
871
+
872
+ const HITLComponent = defineComponent({
873
+ setup() {
874
+ const hitlTool = {
875
+ name: "approvalTool",
876
+ description: "Requires human approval",
877
+ parameters: z.object({ action: z.string() }),
878
+ render: HITLRenderer,
879
+ };
880
+ useHumanInTheLoop(hitlTool);
881
+ return {};
882
+ },
883
+ template: `<div />`,
884
+ });
885
+
886
+ const { unmount } = renderWithCopilotKit({
887
+ agent,
888
+ children: createChatHost(HITLComponent),
889
+ });
890
+
891
+ // MockReconnectableAgent triggers a connect cycle on mount (because the
892
+ // provider sets an explicit threadId). AbstractAgent.connectAgent sets
893
+ // isRunning = true for the duration of the connect Observable, which
894
+ // causes the chat input to treat Enter as "stop" instead of "submit".
895
+ // Wait for the connect cycle to finish before attempting to submit.
896
+ await waitForConnectCycleToSettle();
897
+
898
+ await submitMessage("Request approval");
899
+
900
+ const messageId = testId("msg");
901
+ const toolCallId = testId("tc");
902
+
903
+ await agent.emit(runStartedEvent());
904
+ await agent.emit(
905
+ toolCallChunkEvent({
906
+ toolCallId,
907
+ toolCallName: "approvalTool",
908
+ parentMessageId: messageId,
909
+ delta: JSON.stringify({ action: "delete" }),
910
+ }),
911
+ );
912
+
913
+ // While the agent is still running, the HITL tool should be InProgress.
914
+ await waitFor(() => {
915
+ expect(screen.getByTestId("hitl-status").textContent).toBe(
916
+ ToolCallStatus.InProgress,
917
+ );
918
+ });
919
+
920
+ await agent.emit(runFinishedEvent());
921
+ await agent.complete();
922
+
923
+ // After the run finishes the tool transitions to Executing (awaiting
924
+ // human response).
925
+ await waitFor(() => {
926
+ expect(screen.getByTestId("hitl-status").textContent).toBe(
927
+ ToolCallStatus.Executing,
928
+ );
929
+ });
930
+
931
+ unmount();
932
+ agent.reset();
933
+
934
+ renderWithCopilotKit({
935
+ agent,
936
+ children: createChatHost(HITLComponent),
937
+ });
938
+
939
+ await waitFor(() => {
940
+ expect(screen.getByTestId("hitl-tool")).toBeDefined();
941
+ });
942
+
943
+ await waitFor(() => {
944
+ expect(screen.getByTestId("hitl-action").textContent).toBe("delete");
945
+ });
946
+
947
+ await waitFor(() => {
948
+ expect(screen.getByTestId("hitl-status").textContent).toBe(
949
+ ToolCallStatus.Executing,
950
+ );
951
+ });
952
+
953
+ expect(screen.getByTestId("hitl-respond")).toBeDefined();
954
+ });
955
+
956
+ it("should handle tool call after connect (fresh run)", async () => {
957
+ const agent = new MockReconnectableAgent();
958
+
959
+ const TaskRenderer = defineComponent({
960
+ props: {
961
+ status: { type: String as PropType<ToolCallStatus>, required: true },
962
+ args: { type: Object as PropType<{ task?: string }>, required: true },
963
+ respond: {
964
+ type: Function as PropType<
965
+ ((result: unknown) => Promise<void>) | undefined
966
+ >,
967
+ required: false,
968
+ },
969
+ },
970
+ template: `
971
+ <div data-testid="task-tool">
972
+ <div data-testid="task-status">{{ status }}</div>
973
+ <div data-testid="task-name">{{ args.task ?? "no-task" }}</div>
974
+ <button
975
+ v-if="respond"
976
+ data-testid="task-respond"
977
+ @click="respond('done')"
978
+ >
979
+ Done
980
+ </button>
981
+ </div>
982
+ `,
983
+ });
984
+
985
+ const HITLComponent = defineComponent({
986
+ setup() {
987
+ const hitlTool = {
988
+ name: "taskTool",
989
+ description: "Task approval",
990
+ parameters: z.object({ task: z.string() }),
991
+ render: TaskRenderer,
992
+ };
993
+ useHumanInTheLoop(hitlTool);
994
+ return {};
995
+ },
996
+ template: `<div />`,
997
+ });
998
+
999
+ renderWithCopilotKit({
1000
+ agent,
1001
+ children: createChatHost(HITLComponent),
1002
+ });
1003
+
1004
+ // Same connect-cycle settling as the reconnection test above.
1005
+ await waitForConnectCycleToSettle();
1006
+
1007
+ await submitMessage("Start task");
1008
+
1009
+ const messageId = testId("msg");
1010
+ const toolCallId = testId("tc");
1011
+
1012
+ await agent.emit(runStartedEvent());
1013
+ await agent.emit(
1014
+ toolCallChunkEvent({
1015
+ toolCallId,
1016
+ toolCallName: "taskTool",
1017
+ parentMessageId: messageId,
1018
+ delta: JSON.stringify({ task: "review PR" }),
1019
+ }),
1020
+ );
1021
+
1022
+ await waitFor(() => {
1023
+ expect(screen.getByTestId("task-status").textContent).toBe(
1024
+ ToolCallStatus.InProgress,
1025
+ );
1026
+ expect(screen.getByTestId("task-name").textContent).toBe("review PR");
1027
+ });
1028
+
1029
+ await agent.emit(runFinishedEvent());
1030
+ await agent.complete();
1031
+
1032
+ await waitFor(() => {
1033
+ expect(screen.getByTestId("task-status").textContent).toBe(
1034
+ ToolCallStatus.Executing,
1035
+ );
1036
+ });
1037
+
1038
+ const respondButton = screen.getByTestId("task-respond");
1039
+ await fireEvent.click(respondButton);
1040
+
1041
+ await waitFor(() => {
1042
+ expect(screen.getByTestId("task-status").textContent).toBe(
1043
+ ToolCallStatus.Complete,
1044
+ );
1045
+ });
1046
+ });
1047
+
1048
+ it("should handle multiple sequential tool calls (HITL executes one at a time)", async () => {
1049
+ const agent = new MockStepwiseAgent();
1050
+
1051
+ const Tool1Renderer = defineComponent({
1052
+ props: {
1053
+ status: { type: String as PropType<ToolCallStatus>, required: true },
1054
+ args: { type: Object as PropType<{ id?: string }>, required: true },
1055
+ respond: {
1056
+ type: Function as PropType<
1057
+ ((result: unknown) => Promise<void>) | undefined
1058
+ >,
1059
+ required: false,
1060
+ },
1061
+ },
1062
+ template: `
1063
+ <div data-testid="tool1">
1064
+ <div data-testid="tool1-status">{{ status }}</div>
1065
+ <div data-testid="tool1-id">{{ args.id ?? "" }}</div>
1066
+ <button
1067
+ v-if="respond"
1068
+ data-testid="tool1-respond"
1069
+ @click="respond('ok')"
1070
+ >
1071
+ OK
1072
+ </button>
1073
+ </div>
1074
+ `,
1075
+ });
1076
+
1077
+ const Tool2Renderer = defineComponent({
1078
+ props: {
1079
+ status: { type: String as PropType<ToolCallStatus>, required: true },
1080
+ args: { type: Object as PropType<{ id?: string }>, required: true },
1081
+ respond: {
1082
+ type: Function as PropType<
1083
+ ((result: unknown) => Promise<void>) | undefined
1084
+ >,
1085
+ required: false,
1086
+ },
1087
+ },
1088
+ template: `
1089
+ <div data-testid="tool2">
1090
+ <div data-testid="tool2-status">{{ status }}</div>
1091
+ <div data-testid="tool2-id">{{ args.id ?? "" }}</div>
1092
+ <button
1093
+ v-if="respond"
1094
+ data-testid="tool2-respond"
1095
+ @click="respond('ok')"
1096
+ >
1097
+ OK
1098
+ </button>
1099
+ </div>
1100
+ `,
1101
+ });
1102
+
1103
+ const MultiToolComponent = defineComponent({
1104
+ setup() {
1105
+ const tool1 = {
1106
+ name: "tool1",
1107
+ description: "First tool",
1108
+ parameters: z.object({ id: z.string() }),
1109
+ render: Tool1Renderer,
1110
+ };
1111
+
1112
+ const tool2 = {
1113
+ name: "tool2",
1114
+ description: "Second tool",
1115
+ parameters: z.object({ id: z.string() }),
1116
+ render: Tool2Renderer,
1117
+ };
1118
+
1119
+ useHumanInTheLoop(tool1);
1120
+ useHumanInTheLoop(tool2);
1121
+ return {};
1122
+ },
1123
+ template: `<div />`,
1124
+ });
1125
+
1126
+ renderWithCopilotKit({
1127
+ agent,
1128
+ children: createChatHost(MultiToolComponent),
1129
+ });
1130
+
1131
+ await submitMessage("Multiple tools");
1132
+
1133
+ const messageId = testId("msg");
1134
+ const tc1 = testId("tc1");
1135
+ const tc2 = testId("tc2");
1136
+
1137
+ await agent.emit(runStartedEvent());
1138
+ await agent.emit(
1139
+ toolCallChunkEvent({
1140
+ toolCallId: tc1,
1141
+ toolCallName: "tool1",
1142
+ parentMessageId: messageId,
1143
+ delta: JSON.stringify({ id: "first" }),
1144
+ }),
1145
+ );
1146
+ await agent.emit(
1147
+ toolCallChunkEvent({
1148
+ toolCallId: tc2,
1149
+ toolCallName: "tool2",
1150
+ parentMessageId: messageId,
1151
+ delta: JSON.stringify({ id: "second" }),
1152
+ }),
1153
+ );
1154
+
1155
+ await waitFor(() => {
1156
+ expect(screen.getByTestId("tool1-status").textContent).toBe(
1157
+ ToolCallStatus.InProgress,
1158
+ );
1159
+ expect(screen.getByTestId("tool2-status").textContent).toBe(
1160
+ ToolCallStatus.InProgress,
1161
+ );
1162
+ });
1163
+
1164
+ await agent.emit(runFinishedEvent());
1165
+ await agent.complete();
1166
+
1167
+ await waitFor(() => {
1168
+ expect(screen.getByTestId("tool1-status").textContent).toBe(
1169
+ ToolCallStatus.Executing,
1170
+ );
1171
+ expect(screen.getByTestId("tool2-status").textContent).toBe(
1172
+ ToolCallStatus.InProgress,
1173
+ );
1174
+ });
1175
+
1176
+ await fireEvent.click(screen.getByTestId("tool1-respond"));
1177
+
1178
+ await waitFor(() => {
1179
+ expect(screen.getByTestId("tool1-status").textContent).toBe(
1180
+ ToolCallStatus.Complete,
1181
+ );
1182
+ expect(screen.getByTestId("tool2-status").textContent).toBe(
1183
+ ToolCallStatus.Executing,
1184
+ );
1185
+ });
1186
+
1187
+ await fireEvent.click(screen.getByTestId("tool2-respond"));
1188
+
1189
+ await waitFor(() => {
1190
+ expect(screen.getByTestId("tool2-status").textContent).toBe(
1191
+ ToolCallStatus.Complete,
1192
+ );
1193
+ });
1194
+ });
1195
+
1196
+ it("should handle late-mounting component that renders executing tool", async () => {
1197
+ const agent = new MockStepwiseAgent();
1198
+
1199
+ const LateRenderer = defineComponent({
1200
+ props: {
1201
+ status: { type: String as PropType<ToolCallStatus>, required: true },
1202
+ args: { type: Object as PropType<{ data?: string }>, required: true },
1203
+ },
1204
+ template: `
1205
+ <div data-testid="late-tool">
1206
+ <div data-testid="late-status">{{ status }}</div>
1207
+ <div data-testid="late-data">{{ args.data ?? "" }}</div>
1208
+ </div>
1209
+ `,
1210
+ });
1211
+
1212
+ const ToggleableHITL = defineComponent({
1213
+ setup() {
1214
+ const showTool = ref(false);
1215
+
1216
+ const hitlTool = {
1217
+ name: "lateTool",
1218
+ description: "Late mounting tool",
1219
+ parameters: z.object({ data: z.string() }),
1220
+ render: LateRenderer,
1221
+ };
1222
+
1223
+ useHumanInTheLoop(hitlTool);
1224
+
1225
+ const show = () => {
1226
+ showTool.value = true;
1227
+ };
1228
+
1229
+ return { showTool, show };
1230
+ },
1231
+ template: `
1232
+ <div>
1233
+ <button data-testid="show-late-tool" @click="show">
1234
+ Show Tool
1235
+ </button>
1236
+ <div v-if="showTool" data-testid="late-tool-container">Tool is visible</div>
1237
+ </div>
1238
+ `,
1239
+ });
1240
+
1241
+ renderWithCopilotKit({
1242
+ agent,
1243
+ children: createChatHost(ToggleableHITL),
1244
+ });
1245
+
1246
+ await submitMessage("Test late mount");
1247
+
1248
+ const messageId = testId("msg");
1249
+ const toolCallId = testId("tc");
1250
+
1251
+ await agent.emit(runStartedEvent());
1252
+ await agent.emit(
1253
+ toolCallChunkEvent({
1254
+ toolCallId,
1255
+ toolCallName: "lateTool",
1256
+ parentMessageId: messageId,
1257
+ delta: JSON.stringify({ data: "late-data" }),
1258
+ }),
1259
+ );
1260
+ await agent.emit(runFinishedEvent());
1261
+ await agent.complete();
1262
+
1263
+ await waitFor(() => {
1264
+ expect(screen.getByTestId("late-status").textContent).toBe(
1265
+ ToolCallStatus.Executing,
1266
+ );
1267
+ });
1268
+
1269
+ await fireEvent.click(screen.getByTestId("show-late-tool"));
1270
+
1271
+ await waitFor(() => {
1272
+ expect(screen.getByTestId("late-tool-container")).toBeDefined();
1273
+ });
1274
+
1275
+ expect(screen.getByTestId("late-status").textContent).toBe(
1276
+ ToolCallStatus.Executing,
1277
+ );
1278
+ });
1279
+
1280
+ it("should maintain executing state across component remount", async () => {
1281
+ const agent = new MockStepwiseAgent();
1282
+
1283
+ const RemountRenderer = defineComponent({
1284
+ props: {
1285
+ status: { type: String as PropType<ToolCallStatus>, required: true },
1286
+ args: { type: Object as PropType<{ action?: string }>, required: true },
1287
+ respond: {
1288
+ type: Function as PropType<
1289
+ ((result: unknown) => Promise<void>) | undefined
1290
+ >,
1291
+ required: false,
1292
+ },
1293
+ },
1294
+ template: `
1295
+ <div data-testid="remount-tool">
1296
+ <div data-testid="remount-status">{{ status }}</div>
1297
+ <div data-testid="remount-action">{{ args.action ?? "" }}</div>
1298
+ <button v-if="respond" data-testid="remount-respond">Done</button>
1299
+ </div>
1300
+ `,
1301
+ });
1302
+
1303
+ const HITLChild = defineComponent({
1304
+ setup() {
1305
+ const hitlTool = {
1306
+ name: "remountTool",
1307
+ description: "Remountable tool",
1308
+ parameters: z.object({ action: z.string() }),
1309
+ render: RemountRenderer,
1310
+ };
1311
+
1312
+ useHumanInTheLoop(hitlTool);
1313
+ return {};
1314
+ },
1315
+ template: `<div />`,
1316
+ });
1317
+
1318
+ const RemountableHITL = defineComponent({
1319
+ components: { HITLChild },
1320
+ setup() {
1321
+ const keyValue = ref(0);
1322
+ const remount = () => {
1323
+ keyValue.value += 1;
1324
+ };
1325
+ return { keyValue, remount };
1326
+ },
1327
+ template: `
1328
+ <div>
1329
+ <button data-testid="remount-toggle" @click="remount">Remount</button>
1330
+ <HITLChild :key="keyValue" />
1331
+ </div>
1332
+ `,
1333
+ });
1334
+
1335
+ renderWithCopilotKit({
1336
+ agent,
1337
+ children: createChatHost(RemountableHITL),
1338
+ });
1339
+
1340
+ await submitMessage("Test remount");
1341
+
1342
+ const messageId = testId("msg");
1343
+ const toolCallId = testId("tc");
1344
+
1345
+ await agent.emit(runStartedEvent());
1346
+ await agent.emit(
1347
+ toolCallChunkEvent({
1348
+ toolCallId,
1349
+ toolCallName: "remountTool",
1350
+ parentMessageId: messageId,
1351
+ delta: JSON.stringify({ action: "test-action" }),
1352
+ }),
1353
+ );
1354
+ await agent.emit(runFinishedEvent());
1355
+ await agent.complete();
1356
+
1357
+ await waitFor(() => {
1358
+ expect(screen.getByTestId("remount-status").textContent).toBe(
1359
+ ToolCallStatus.Executing,
1360
+ );
1361
+ expect(screen.getByTestId("remount-action").textContent).toBe(
1362
+ "test-action",
1363
+ );
1364
+ });
1365
+
1366
+ await fireEvent.click(screen.getByTestId("remount-toggle"));
1367
+
1368
+ await waitFor(() => {
1369
+ expect(screen.getByTestId("remount-status").textContent).toBe(
1370
+ ToolCallStatus.Executing,
1371
+ );
1372
+ expect(screen.getByTestId("remount-action").textContent).toBe(
1373
+ "test-action",
1374
+ );
1375
+ });
1376
+
1377
+ expect(screen.getByTestId("remount-respond")).toBeDefined();
1378
+ });
1379
+ });