@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.
- package/AGENTS.md +50 -0
- package/CHANGELOG.md +13 -0
- package/PARITY.md +434 -0
- package/README.md +396 -0
- package/dist/components/copilot-provider/CopilotKit.vue.d.ts +20 -0
- package/dist/components/copilot-provider/CopilotKit.vue.d.ts.map +1 -0
- package/dist/components/copilot-provider/index.d.ts +3 -0
- package/dist/components/copilot-provider/index.d.ts.map +1 -0
- package/dist/components/copilot-provider/types.d.ts +22 -0
- package/dist/components/copilot-provider/types.d.ts.map +1 -0
- package/dist/hooks/index.d.ts +7 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/use-copilot-action.d.ts +27 -0
- package/dist/hooks/use-copilot-action.d.ts.map +1 -0
- package/dist/hooks/use-copilot-readable.d.ts +20 -0
- package/dist/hooks/use-copilot-readable.d.ts.map +1 -0
- package/dist/hooks/use-frontend-tool.d.ts +21 -0
- package/dist/hooks/use-frontend-tool.d.ts.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +10 -0
- package/dist/index.d.mts +10 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.mjs +252 -0
- package/dist/index.mjs.map +1 -0
- package/dist/styles.css +2 -0
- package/dist/use-render-activity-message-BRL1Rpl-.cjs +85 -0
- package/dist/use-render-activity-message-BRL1Rpl-.cjs.map +1 -0
- package/dist/use-render-activity-message-CqtxiFSs.js +8927 -0
- package/dist/use-render-activity-message-CqtxiFSs.js.map +1 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts +9 -0
- package/dist/v2/components/A2UIMessageRenderer.d.ts.map +1 -0
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts +16 -0
- package/dist/v2/components/A2UISurfaceActivityRenderer.vue.d.ts.map +1 -0
- package/dist/v2/components/CopilotKitInspector.vue.d.ts +7 -0
- package/dist/v2/components/CopilotKitInspector.vue.d.ts.map +1 -0
- package/dist/v2/components/InlineFeatureWarning.vue.d.ts +6 -0
- package/dist/v2/components/InlineFeatureWarning.vue.d.ts.map +1 -0
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts +18 -0
- package/dist/v2/components/LicenseWarningBanner.vue.d.ts.map +1 -0
- package/dist/v2/components/MCPAppsActivityRenderer.d.ts +88 -0
- package/dist/v2/components/MCPAppsActivityRenderer.d.ts.map +1 -0
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts +154 -0
- package/dist/v2/components/OpenGenerativeUIRenderer.d.ts.map +1 -0
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts +19 -0
- package/dist/v2/components/a2ui/A2UIBuiltInToolCallRenderer.d.ts.map +1 -0
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts +16 -0
- package/dist/v2/components/a2ui/A2UICatalogContext.d.ts.map +1 -0
- package/dist/v2/components/a2ui/VueSurface.d.ts +62 -0
- package/dist/v2/components/a2ui/VueSurface.d.ts.map +1 -0
- package/dist/v2/components/a2ui/adapter.d.ts +38 -0
- package/dist/v2/components/a2ui/adapter.d.ts.map +1 -0
- package/dist/v2/components/a2ui/catalog.d.ts +29 -0
- package/dist/v2/components/a2ui/catalog.d.ts.map +1 -0
- package/dist/v2/components/a2ui/index.d.ts +5 -0
- package/dist/v2/components/a2ui/index.d.ts.map +1 -0
- package/dist/v2/components/a2ui/utils.d.ts +18 -0
- package/dist/v2/components/a2ui/utils.d.ts.map +1 -0
- package/dist/v2/components/a2ui.d.ts +12 -0
- package/dist/v2/components/a2ui.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts +50 -0
- package/dist/v2/components/chat/CopilotChat.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts +164 -0
- package/dist/v2/components/chat/CopilotChatAssistantMessage.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts +12 -0
- package/dist/v2/components/chat/CopilotChatAttachmentQueue.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts +7 -0
- package/dist/v2/components/chat/CopilotChatAttachmentRenderer.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts +12 -0
- package/dist/v2/components/chat/CopilotChatAudioRecorder.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts +290 -0
- package/dist/v2/components/chat/CopilotChatInput.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts +72 -0
- package/dist/v2/components/chat/CopilotChatMessageView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts +65 -0
- package/dist/v2/components/chat/CopilotChatReasoningMessage.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts +27 -0
- package/dist/v2/components/chat/CopilotChatSuggestionPill.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts +26 -0
- package/dist/v2/components/chat/CopilotChatSuggestionView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts +17 -0
- package/dist/v2/components/chat/CopilotChatToggleButton.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonCloseIcon.d.ts +5 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonCloseIcon.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonOpenIcon.d.ts +5 -0
- package/dist/v2/components/chat/CopilotChatToggleButtonOpenIcon.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts +21 -0
- package/dist/v2/components/chat/CopilotChatToolCallsView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts +34 -0
- package/dist/v2/components/chat/CopilotChatUserMessage.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts +106 -0
- package/dist/v2/components/chat/CopilotChatView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts +15 -0
- package/dist/v2/components/chat/CopilotModalHeader.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts +5 -0
- package/dist/v2/components/chat/CopilotModalHeaderCloseButton.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts +5 -0
- package/dist/v2/components/chat/CopilotModalHeaderTitle.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts +50 -0
- package/dist/v2/components/chat/CopilotPopup.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts +55 -0
- package/dist/v2/components/chat/CopilotPopupView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts +55 -0
- package/dist/v2/components/chat/CopilotPopupViewInternal.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts +28 -0
- package/dist/v2/components/chat/CopilotPopupWelcomeScreen.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts +48 -0
- package/dist/v2/components/chat/CopilotSidebar.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts +62 -0
- package/dist/v2/components/chat/CopilotSidebarView.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts +53 -0
- package/dist/v2/components/chat/CopilotSidebarViewInternal.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts +28 -0
- package/dist/v2/components/chat/CopilotSidebarWelcomeScreen.vue.d.ts.map +1 -0
- package/dist/v2/components/chat/audioRecorder.d.ts +11 -0
- package/dist/v2/components/chat/audioRecorder.d.ts.map +1 -0
- package/dist/v2/components/chat/index.d.ts +682 -0
- package/dist/v2/components/chat/index.d.ts.map +1 -0
- package/dist/v2/components/chat/last-user-message-context.d.ts +29 -0
- package/dist/v2/components/chat/last-user-message-context.d.ts.map +1 -0
- package/dist/v2/components/chat/normalize-auto-scroll.d.ts +3 -0
- package/dist/v2/components/chat/normalize-auto-scroll.d.ts.map +1 -0
- package/dist/v2/components/chat/types.d.ts +380 -0
- package/dist/v2/components/chat/types.d.ts.map +1 -0
- package/dist/v2/components/icons/index.d.ts +2 -0
- package/dist/v2/components/icons/index.d.ts.map +1 -0
- package/dist/v2/components/index.d.ts +8 -0
- package/dist/v2/components/index.d.ts.map +1 -0
- package/dist/v2/hooks/index.d.ts +24 -0
- package/dist/v2/hooks/index.d.ts.map +1 -0
- package/dist/v2/hooks/use-agent-context.d.ts +24 -0
- package/dist/v2/hooks/use-agent-context.d.ts.map +1 -0
- package/dist/v2/hooks/use-agent.d.ts +53 -0
- package/dist/v2/hooks/use-agent.d.ts.map +1 -0
- package/dist/v2/hooks/use-attachments.d.ts +21 -0
- package/dist/v2/hooks/use-attachments.d.ts.map +1 -0
- package/dist/v2/hooks/use-capabilities.d.ts +16 -0
- package/dist/v2/hooks/use-capabilities.d.ts.map +1 -0
- package/dist/v2/hooks/use-component.d.ts +13 -0
- package/dist/v2/hooks/use-component.d.ts.map +1 -0
- package/dist/v2/hooks/use-configure-suggestions.d.ts +24 -0
- package/dist/v2/hooks/use-configure-suggestions.d.ts.map +1 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts +14 -0
- package/dist/v2/hooks/use-default-render-tool.d.ts.map +1 -0
- package/dist/v2/hooks/use-frontend-tool.d.ts +19 -0
- package/dist/v2/hooks/use-frontend-tool.d.ts.map +1 -0
- package/dist/v2/hooks/use-human-in-the-loop.d.ts +19 -0
- package/dist/v2/hooks/use-human-in-the-loop.d.ts.map +1 -0
- package/dist/v2/hooks/use-interrupt.d.ts +36 -0
- package/dist/v2/hooks/use-interrupt.d.ts.map +1 -0
- package/dist/v2/hooks/use-katex-styles.d.ts +22 -0
- package/dist/v2/hooks/use-katex-styles.d.ts.map +1 -0
- package/dist/v2/hooks/use-keyboard-height.d.ts +33 -0
- package/dist/v2/hooks/use-keyboard-height.d.ts.map +1 -0
- package/dist/v2/hooks/use-pin-to-send.d.ts +28 -0
- package/dist/v2/hooks/use-pin-to-send.d.ts.map +1 -0
- package/dist/v2/hooks/use-render-activity-message.d.ts +21 -0
- package/dist/v2/hooks/use-render-activity-message.d.ts.map +1 -0
- package/dist/v2/hooks/use-render-custom-messages.d.ts +27 -0
- package/dist/v2/hooks/use-render-custom-messages.d.ts.map +1 -0
- package/dist/v2/hooks/use-render-tool.d.ts +36 -0
- package/dist/v2/hooks/use-render-tool.d.ts.map +1 -0
- package/dist/v2/hooks/use-suggestions.d.ts +26 -0
- package/dist/v2/hooks/use-suggestions.d.ts.map +1 -0
- package/dist/v2/hooks/use-threads.d.ts +42 -0
- package/dist/v2/hooks/use-threads.d.ts.map +1 -0
- package/dist/v2/index.cjs +2 -0
- package/dist/v2/index.cjs.map +1 -0
- package/dist/v2/index.d.cts +9 -0
- package/dist/v2/index.d.mts +9 -0
- package/dist/v2/index.d.ts +9 -0
- package/dist/v2/index.d.ts.map +1 -0
- package/dist/v2/index.mjs +75 -0
- package/dist/v2/index.mjs.map +1 -0
- package/dist/v2/lib/processPartialHtml.d.ts +3 -0
- package/dist/v2/lib/processPartialHtml.d.ts.map +1 -0
- package/dist/v2/lib/shallow-stable.d.ts +7 -0
- package/dist/v2/lib/shallow-stable.d.ts.map +1 -0
- package/dist/v2/lib/transcription-client.d.ts +19 -0
- package/dist/v2/lib/transcription-client.d.ts.map +1 -0
- package/dist/v2/lib/vue-core.d.ts +47 -0
- package/dist/v2/lib/vue-core.d.ts.map +1 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.types.d.ts +15 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.types.d.ts.map +1 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts +17 -0
- package/dist/v2/providers/CopilotChatConfigurationProvider.vue.d.ts.map +1 -0
- package/dist/v2/providers/CopilotKitProvider.types.d.ts +61 -0
- package/dist/v2/providers/CopilotKitProvider.types.d.ts.map +1 -0
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts +37 -0
- package/dist/v2/providers/CopilotKitProvider.vue.d.ts.map +1 -0
- package/dist/v2/providers/SandboxFunctionsContext.d.ts +4 -0
- package/dist/v2/providers/SandboxFunctionsContext.d.ts.map +1 -0
- package/dist/v2/providers/index.d.ts +13 -0
- package/dist/v2/providers/index.d.ts.map +1 -0
- package/dist/v2/providers/keys.d.ts +17 -0
- package/dist/v2/providers/keys.d.ts.map +1 -0
- package/dist/v2/providers/license-context.d.ts +7 -0
- package/dist/v2/providers/license-context.d.ts.map +1 -0
- package/dist/v2/providers/types.d.ts +38 -0
- package/dist/v2/providers/types.d.ts.map +1 -0
- package/dist/v2/providers/useCopilotChatConfiguration.d.ts +4 -0
- package/dist/v2/providers/useCopilotChatConfiguration.d.ts.map +1 -0
- package/dist/v2/providers/useCopilotKit.d.ts +2 -0
- package/dist/v2/providers/useCopilotKit.d.ts.map +1 -0
- package/dist/v2/providers/useLicenseContext.d.ts +14 -0
- package/dist/v2/providers/useLicenseContext.d.ts.map +1 -0
- package/dist/v2/types/a2ui.d.ts +5 -0
- package/dist/v2/types/a2ui.d.ts.map +1 -0
- package/dist/v2/types/defineToolCallRenderer.d.ts +15 -0
- package/dist/v2/types/defineToolCallRenderer.d.ts.map +1 -0
- package/dist/v2/types/frontend-tool.d.ts +6 -0
- package/dist/v2/types/frontend-tool.d.ts.map +1 -0
- package/dist/v2/types/human-in-the-loop.d.ts +29 -0
- package/dist/v2/types/human-in-the-loop.d.ts.map +1 -0
- package/dist/v2/types/index.d.ts +10 -0
- package/dist/v2/types/index.d.ts.map +1 -0
- package/dist/v2/types/interrupt.d.ts +14 -0
- package/dist/v2/types/interrupt.d.ts.map +1 -0
- package/dist/v2/types/sandbox-function.d.ts +8 -0
- package/dist/v2/types/sandbox-function.d.ts.map +1 -0
- package/dist/v2/types/vue-activity-message-renderer.d.ts +18 -0
- package/dist/v2/types/vue-activity-message-renderer.d.ts.map +1 -0
- package/dist/v2/types/vue-custom-message-renderer.d.ts +19 -0
- package/dist/v2/types/vue-custom-message-renderer.d.ts.map +1 -0
- package/dist/v2/types/vue-tool-call-renderer.d.ts +37 -0
- package/dist/v2/types/vue-tool-call-renderer.d.ts.map +1 -0
- package/env.d.ts +7 -0
- package/eslint.config.mjs +42 -0
- package/package.json +130 -0
- package/scripts/scope-preflight.mjs +100 -0
- package/src/components/copilot-provider/CopilotKit.vue +18 -0
- package/src/components/copilot-provider/index.ts +2 -0
- package/src/components/copilot-provider/types.ts +24 -0
- package/src/hooks/index.ts +9 -0
- package/src/hooks/use-copilot-action.ts +168 -0
- package/src/hooks/use-copilot-readable.ts +75 -0
- package/src/hooks/use-frontend-tool.ts +76 -0
- package/src/index.ts +12 -0
- package/src/styles/globals.css +314 -0
- package/src/v2/__tests__/exports.test.ts +35 -0
- package/src/v2/__tests__/mocks/web-inspector.ts +5 -0
- package/src/v2/__tests__/setup.ts +141 -0
- package/src/v2/__tests__/utils/agents.ts +391 -0
- package/src/v2/__tests__/utils/mount.ts +83 -0
- package/src/v2/__tests__/utils/test-helpers.ts +712 -0
- package/src/v2/components/A2UIMessageRenderer.ts +125 -0
- package/src/v2/components/A2UISurfaceActivityRenderer.vue +186 -0
- package/src/v2/components/CopilotKitInspector.vue +42 -0
- package/src/v2/components/InlineFeatureWarning.vue +35 -0
- package/src/v2/components/LicenseWarningBanner.vue +196 -0
- package/src/v2/components/MCPAppsActivityRenderer.ts +778 -0
- package/src/v2/components/OpenGenerativeUIRenderer.ts +550 -0
- package/src/v2/components/__tests__/A2UIMessageRenderer.test.ts +271 -0
- package/src/v2/components/__tests__/CopilotKitInspector.test.ts +57 -0
- package/src/v2/components/__tests__/MCPAppsActivityRenderer.e2e.test.ts +851 -0
- package/src/v2/components/__tests__/MCPAppsActivityRenderer.test.ts +237 -0
- package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.ts +516 -0
- package/src/v2/components/a2ui/A2UIBuiltInToolCallRenderer.ts +295 -0
- package/src/v2/components/a2ui/A2UICatalogContext.ts +190 -0
- package/src/v2/components/a2ui/VueSurface.ts +144 -0
- package/src/v2/components/a2ui/adapter.ts +156 -0
- package/src/v2/components/a2ui/catalog.ts +858 -0
- package/src/v2/components/a2ui/index.ts +7 -0
- package/src/v2/components/a2ui/utils.ts +67 -0
- package/src/v2/components/a2ui.ts +30 -0
- package/src/v2/components/chat/CopilotChat.vue +777 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.vue +891 -0
- package/src/v2/components/chat/CopilotChatAttachmentQueue.vue +411 -0
- package/src/v2/components/chat/CopilotChatAttachmentRenderer.vue +87 -0
- package/src/v2/components/chat/CopilotChatAudioRecorder.vue +269 -0
- package/src/v2/components/chat/CopilotChatInput.vue +1271 -0
- package/src/v2/components/chat/CopilotChatMessageView.vue +476 -0
- package/src/v2/components/chat/CopilotChatReasoningMessage.vue +247 -0
- package/src/v2/components/chat/CopilotChatSuggestionPill.vue +56 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.vue +93 -0
- package/src/v2/components/chat/CopilotChatToggleButton.vue +145 -0
- package/src/v2/components/chat/CopilotChatToggleButtonCloseIcon.ts +17 -0
- package/src/v2/components/chat/CopilotChatToggleButtonOpenIcon.ts +18 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.vue +161 -0
- package/src/v2/components/chat/CopilotChatUserMessage.vue +322 -0
- package/src/v2/components/chat/CopilotChatView.vue +740 -0
- package/src/v2/components/chat/CopilotModalHeader.vue +73 -0
- package/src/v2/components/chat/CopilotModalHeaderCloseButton.ts +38 -0
- package/src/v2/components/chat/CopilotModalHeaderTitle.ts +22 -0
- package/src/v2/components/chat/CopilotPopup.vue +182 -0
- package/src/v2/components/chat/CopilotPopupView.vue +168 -0
- package/src/v2/components/chat/CopilotPopupViewInternal.vue +453 -0
- package/src/v2/components/chat/CopilotPopupWelcomeScreen.vue +140 -0
- package/src/v2/components/chat/CopilotSidebar.vue +178 -0
- package/src/v2/components/chat/CopilotSidebarView.vue +172 -0
- package/src/v2/components/chat/CopilotSidebarViewInternal.vue +366 -0
- package/src/v2/components/chat/CopilotSidebarWelcomeScreen.vue +142 -0
- package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.ts +237 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.ts +1240 -0
- package/src/v2/components/chat/__tests__/CopilotChat.licenseWarning.test.ts +138 -0
- package/src/v2/components/chat/__tests__/CopilotChat.onError.test.ts +85 -0
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.ts +141 -0
- package/src/v2/components/chat/__tests__/CopilotChat.test.ts +652 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.ts +683 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.ts +768 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.ts +1108 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.ts +87 -0
- package/src/v2/components/chat/__tests__/CopilotChatAttachmentQueue.test.ts +277 -0
- package/src/v2/components/chat/__tests__/CopilotChatAttachmentRenderer.test.ts +124 -0
- package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.ts +230 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.bottomAnchored.test.ts +83 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.ts +1139 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.ts +1051 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.ts +141 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.ts +494 -0
- package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.ts +181 -0
- package/src/v2/components/chat/__tests__/CopilotChatReasoningMessage.test.ts +73 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionPill.test.ts +73 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.ts +674 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.test.ts +91 -0
- package/src/v2/components/chat/__tests__/CopilotChatToggleButton.test.ts +93 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolCallsView.test.ts +382 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.ts +1019 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.ts +516 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.ts +701 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.test.ts +337 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.ts +135 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.ts +278 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.ts +1082 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.ts +166 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.ts +1145 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.test.ts +374 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.ts +636 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.test.ts +112 -0
- package/src/v2/components/chat/__tests__/CopilotPopup.test.ts +58 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.ts +725 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.test.ts +112 -0
- package/src/v2/components/chat/__tests__/CopilotSidebar.test.ts +58 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.ts +603 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.test.ts +214 -0
- package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.ts +394 -0
- package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.ts +82 -0
- package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +39 -0
- package/src/v2/components/chat/audioRecorder.ts +15 -0
- package/src/v2/components/chat/index.ts +52 -0
- package/src/v2/components/chat/last-user-message-context.ts +39 -0
- package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
- package/src/v2/components/chat/types.ts +481 -0
- package/src/v2/components/icons/__tests__/icons.test.ts +86 -0
- package/src/v2/components/icons/index.ts +22 -0
- package/src/v2/components/index.ts +7 -0
- package/src/v2/hooks/__tests__/standard-schema-types.test.ts +149 -0
- package/src/v2/hooks/__tests__/standard-schema.test.ts +315 -0
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.ts +144 -0
- package/src/v2/hooks/__tests__/use-agent-context.test.ts +271 -0
- package/src/v2/hooks/__tests__/use-agent-error-state.test.ts +64 -0
- package/src/v2/hooks/__tests__/use-agent-stability.test.ts +268 -0
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.ts +433 -0
- package/src/v2/hooks/__tests__/use-agent-throttle.test.ts +747 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.ts +187 -0
- package/src/v2/hooks/__tests__/use-agent.test.ts +126 -0
- package/src/v2/hooks/__tests__/use-attachments.test.ts +181 -0
- package/src/v2/hooks/__tests__/use-component.test.ts +145 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.ts +527 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.test.ts +399 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.ts +214 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-available.test.ts +220 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.ts +2320 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.test.ts +648 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.ts +1379 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.test.ts +282 -0
- package/src/v2/hooks/__tests__/use-interrupt.test.ts +345 -0
- package/src/v2/hooks/__tests__/use-katex-styles.test.ts +69 -0
- package/src/v2/hooks/__tests__/use-keyboard-height.test.ts +199 -0
- package/src/v2/hooks/__tests__/use-pin-to-send.test.ts +363 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.ts +329 -0
- package/src/v2/hooks/__tests__/use-suggestions.e2e.test.ts +397 -0
- package/src/v2/hooks/__tests__/use-suggestions.test.ts +198 -0
- package/src/v2/hooks/__tests__/use-threads.test.ts +1041 -0
- package/src/v2/hooks/__tests__/zod-regression.test.ts +339 -0
- package/src/v2/hooks/index.ts +29 -0
- package/src/v2/hooks/use-agent-context.ts +55 -0
- package/src/v2/hooks/use-agent.ts +345 -0
- package/src/v2/hooks/use-attachments.ts +261 -0
- package/src/v2/hooks/use-capabilities.ts +30 -0
- package/src/v2/hooks/use-component.ts +46 -0
- package/src/v2/hooks/use-configure-suggestions.ts +252 -0
- package/src/v2/hooks/use-default-render-tool.ts +130 -0
- package/src/v2/hooks/use-frontend-tool.ts +68 -0
- package/src/v2/hooks/use-human-in-the-loop.ts +90 -0
- package/src/v2/hooks/use-interrupt.ts +257 -0
- package/src/v2/hooks/use-katex-styles.ts +44 -0
- package/src/v2/hooks/use-keyboard-height.ts +87 -0
- package/src/v2/hooks/use-pin-to-send.ts +160 -0
- package/src/v2/hooks/use-render-activity-message.ts +92 -0
- package/src/v2/hooks/use-render-custom-messages.ts +129 -0
- package/src/v2/hooks/use-render-tool.ts +128 -0
- package/src/v2/hooks/use-suggestions.ts +98 -0
- package/src/v2/hooks/use-threads.ts +208 -0
- package/src/v2/index.ts +11 -0
- package/src/v2/lib/__tests__/processPartialHtml.test.ts +84 -0
- package/src/v2/lib/__tests__/transcription-client.test.ts +65 -0
- package/src/v2/lib/processPartialHtml.ts +21 -0
- package/src/v2/lib/shallow-stable.ts +54 -0
- package/src/v2/lib/transcription-client.ts +151 -0
- package/src/v2/lib/vue-core.ts +161 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.types.ts +15 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.vue +95 -0
- package/src/v2/providers/CopilotKitProvider.types.ts +66 -0
- package/src/v2/providers/CopilotKitProvider.vue +653 -0
- package/src/v2/providers/SandboxFunctionsContext.ts +11 -0
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.ts +309 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.debug.test.ts +295 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.license.test.ts +110 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.ts +67 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.ts +901 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.ts +141 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.ts +871 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.ts +603 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.ts +104 -0
- package/src/v2/providers/index.ts +21 -0
- package/src/v2/providers/keys.ts +25 -0
- package/src/v2/providers/license-context.ts +16 -0
- package/src/v2/providers/types.ts +40 -0
- package/src/v2/providers/useCopilotChatConfiguration.ts +11 -0
- package/src/v2/providers/useCopilotKit.ts +11 -0
- package/src/v2/providers/useLicenseContext.ts +21 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.ts +157 -0
- package/src/v2/types/a2ui.ts +5 -0
- package/src/v2/types/defineToolCallRenderer.ts +32 -0
- package/src/v2/types/frontend-tool.ts +8 -0
- package/src/v2/types/human-in-the-loop.ts +38 -0
- package/src/v2/types/index.ts +9 -0
- package/src/v2/types/interrupt.ts +15 -0
- package/src/v2/types/sandbox-function.ts +8 -0
- package/src/v2/types/vue-activity-message-renderer.ts +22 -0
- package/src/v2/types/vue-custom-message-renderer.ts +24 -0
- package/src/v2/types/vue-tool-call-renderer.ts +44 -0
- package/tsconfig.json +27 -0
- package/vite.config.ts +49 -0
- 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
|
+
});
|