@copilotkit/react-core 1.57.3 → 1.58.0-canary.thread-id-propagation

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 (292) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{copilotkit-CtXcs1ea.cjs → copilotkit-B4ouY7qC.cjs} +14 -3
  3. package/dist/copilotkit-B4ouY7qC.cjs.map +1 -0
  4. package/dist/copilotkit-BK9CVq9A.d.cts.map +1 -1
  5. package/dist/{copilotkit-CC8DjOiC.mjs → copilotkit-L4mM_JqG.mjs} +14 -3
  6. package/dist/copilotkit-L4mM_JqG.mjs.map +1 -0
  7. package/dist/copilotkit-WlmeVijs.d.mts.map +1 -1
  8. package/dist/index.cjs +3 -77
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +2 -2
  11. package/dist/index.d.mts +2 -2
  12. package/dist/index.mjs +3 -77
  13. package/dist/index.mjs.map +1 -1
  14. package/dist/index.umd.js +15 -78
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/v2/headless.cjs +11 -0
  17. package/dist/v2/headless.cjs.map +1 -1
  18. package/dist/v2/headless.d.cts.map +1 -1
  19. package/dist/v2/headless.d.mts.map +1 -1
  20. package/dist/v2/headless.mjs +11 -0
  21. package/dist/v2/headless.mjs.map +1 -1
  22. package/dist/v2/index.cjs +1 -1
  23. package/dist/v2/index.mjs +1 -1
  24. package/dist/v2/index.umd.js +13 -2
  25. package/dist/v2/index.umd.js.map +1 -1
  26. package/package.json +12 -13
  27. package/skills/react-core/SKILL.md +108 -0
  28. package/skills/react-core/references/agent-access.md +288 -0
  29. package/skills/react-core/references/attachments.md +291 -0
  30. package/skills/react-core/references/capabilities.md +138 -0
  31. package/skills/react-core/references/chat-components.md +221 -0
  32. package/skills/react-core/references/client-side-tools.md +358 -0
  33. package/skills/react-core/references/custom-message-renderers.md +226 -0
  34. package/skills/react-core/references/debug-mode.md +153 -0
  35. package/skills/react-core/references/human-in-the-loop.md +312 -0
  36. package/skills/react-core/references/provider-setup.md +326 -0
  37. package/skills/react-core/references/rendering-activity-messages.md +207 -0
  38. package/skills/react-core/references/rendering-tool-calls.md +319 -0
  39. package/skills/react-core/references/suggestions.md +211 -0
  40. package/skills/react-core/references/switching-agents-recipes.md +160 -0
  41. package/skills/react-core/references/switching-agents.md +231 -0
  42. package/skills/react-core/references/threads.md +226 -0
  43. package/.attw.json +0 -3
  44. package/CHANGELOG.md +0 -5043
  45. package/dist/copilotkit-CC8DjOiC.mjs.map +0 -1
  46. package/dist/copilotkit-CtXcs1ea.cjs.map +0 -1
  47. package/scripts/scope-preflight.mjs +0 -100
  48. package/src/components/CopilotListeners.tsx +0 -137
  49. package/src/components/__tests__/CopilotListeners.test.tsx +0 -38
  50. package/src/components/copilot-provider/__tests__/copilot-messages-key.test.tsx +0 -92
  51. package/src/components/copilot-provider/__tests__/copilotkit-error.test.tsx +0 -77
  52. package/src/components/copilot-provider/__tests__/error-visibility-prod.test.tsx +0 -70
  53. package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +0 -107
  54. package/src/components/copilot-provider/copilot-messages.tsx +0 -314
  55. package/src/components/copilot-provider/copilotkit-props.tsx +0 -214
  56. package/src/components/copilot-provider/copilotkit.tsx +0 -853
  57. package/src/components/copilot-provider/index.ts +0 -3
  58. package/src/components/dev-console/console-trigger.tsx +0 -283
  59. package/src/components/dev-console/developer-console-modal.tsx +0 -1016
  60. package/src/components/dev-console/icons.tsx +0 -106
  61. package/src/components/error-boundary/error-boundary.tsx +0 -99
  62. package/src/components/error-boundary/error-utils.tsx +0 -105
  63. package/src/components/index.ts +0 -1
  64. package/src/components/toast/exclamation-mark-icon.tsx +0 -27
  65. package/src/components/toast/toast-provider.tsx +0 -448
  66. package/src/components/usage-banner.tsx +0 -266
  67. package/src/context/__tests__/threads-context.test.tsx +0 -141
  68. package/src/context/coagent-state-renders-context.tsx +0 -89
  69. package/src/context/copilot-context.tsx +0 -365
  70. package/src/context/copilot-messages-context.tsx +0 -35
  71. package/src/context/index.ts +0 -22
  72. package/src/context/threads-context.tsx +0 -69
  73. package/src/hooks/__tests__/use-coagent-config.test.ts +0 -352
  74. package/src/hooks/__tests__/use-coagent-state-render-bridge.helpers.test.ts +0 -107
  75. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +0 -1209
  76. package/src/hooks/__tests__/use-coagent-state-render.test.tsx +0 -356
  77. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +0 -241
  78. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +0 -72
  79. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +0 -102
  80. package/src/hooks/index.ts +0 -33
  81. package/src/hooks/use-agent-nodename.ts +0 -33
  82. package/src/hooks/use-coagent-state-render-bridge.helpers.ts +0 -345
  83. package/src/hooks/use-coagent-state-render-bridge.tsx +0 -222
  84. package/src/hooks/use-coagent-state-render-registry.ts +0 -230
  85. package/src/hooks/use-coagent-state-render.ts +0 -163
  86. package/src/hooks/use-coagent.ts +0 -377
  87. package/src/hooks/use-configure-chat-suggestions.tsx +0 -96
  88. package/src/hooks/use-copilot-action.ts +0 -245
  89. package/src/hooks/use-copilot-additional-instructions.ts +0 -98
  90. package/src/hooks/use-copilot-authenticated-action.ts +0 -73
  91. package/src/hooks/use-copilot-chat-headless_c.ts +0 -264
  92. package/src/hooks/use-copilot-chat-suggestions.tsx +0 -134
  93. package/src/hooks/use-copilot-chat.ts +0 -132
  94. package/src/hooks/use-copilot-chat_internal.ts +0 -875
  95. package/src/hooks/use-copilot-readable.ts +0 -135
  96. package/src/hooks/use-copilot-runtime-client.ts +0 -178
  97. package/src/hooks/use-default-tool.ts +0 -13
  98. package/src/hooks/use-flat-category-store.ts +0 -109
  99. package/src/hooks/use-frontend-tool.ts +0 -113
  100. package/src/hooks/use-human-in-the-loop.ts +0 -138
  101. package/src/hooks/use-langgraph-interrupt.ts +0 -103
  102. package/src/hooks/use-lazy-tool-renderer.tsx +0 -30
  103. package/src/hooks/use-make-copilot-document-readable.ts +0 -30
  104. package/src/hooks/use-render-tool-call.ts +0 -89
  105. package/src/hooks/use-tree.ts +0 -222
  106. package/src/index.tsx +0 -7
  107. package/src/lib/copilot-task.ts +0 -215
  108. package/src/lib/index.ts +0 -1
  109. package/src/lib/status-checker.ts +0 -67
  110. package/src/setupTests.ts +0 -37
  111. package/src/test-helpers/copilot-context.ts +0 -91
  112. package/src/types/chat-suggestion-configuration.ts +0 -23
  113. package/src/types/coagent-action.ts +0 -35
  114. package/src/types/coagent-state.ts +0 -13
  115. package/src/types/crew.ts +0 -89
  116. package/src/types/document-pointer.ts +0 -7
  117. package/src/types/frontend-action.ts +0 -213
  118. package/src/types/index.ts +0 -17
  119. package/src/types/interrupt-action.ts +0 -58
  120. package/src/types/system-message.ts +0 -4
  121. package/src/utils/dev-console.ts +0 -19
  122. package/src/utils/index.ts +0 -2
  123. package/src/utils/suggestions-constants.ts +0 -8
  124. package/src/utils/utils.test.ts +0 -7
  125. package/src/utils/utils.ts +0 -6
  126. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +0 -240
  127. package/src/v2/__tests__/globalSetup.ts +0 -14
  128. package/src/v2/__tests__/setup.ts +0 -93
  129. package/src/v2/__tests__/utils/test-helpers.tsx +0 -570
  130. package/src/v2/a2ui/A2UICatalogContext.tsx +0 -79
  131. package/src/v2/a2ui/A2UIMessageRenderer.tsx +0 -294
  132. package/src/v2/a2ui/A2UIToolCallRenderer.tsx +0 -290
  133. package/src/v2/components/CopilotKitInspector.tsx +0 -52
  134. package/src/v2/components/MCPAppsActivityRenderer.tsx +0 -815
  135. package/src/v2/components/OpenGenerativeUIRenderer.tsx +0 -598
  136. package/src/v2/components/WildcardToolCallRender.tsx +0 -86
  137. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.tsx +0 -665
  138. package/src/v2/components/chat/CopilotChat.tsx +0 -664
  139. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +0 -393
  140. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +0 -374
  141. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +0 -159
  142. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +0 -350
  143. package/src/v2/components/chat/CopilotChatInput.tsx +0 -1412
  144. package/src/v2/components/chat/CopilotChatMessageView.tsx +0 -716
  145. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +0 -265
  146. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +0 -59
  147. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +0 -134
  148. package/src/v2/components/chat/CopilotChatToggleButton.tsx +0 -171
  149. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +0 -40
  150. package/src/v2/components/chat/CopilotChatUserMessage.tsx +0 -445
  151. package/src/v2/components/chat/CopilotChatView.tsx +0 -890
  152. package/src/v2/components/chat/CopilotModalHeader.tsx +0 -129
  153. package/src/v2/components/chat/CopilotPopup.tsx +0 -81
  154. package/src/v2/components/chat/CopilotPopupView.tsx +0 -317
  155. package/src/v2/components/chat/CopilotSidebar.tsx +0 -80
  156. package/src/v2/components/chat/CopilotSidebarView.tsx +0 -269
  157. package/src/v2/components/chat/Lightbox.tsx +0 -103
  158. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +0 -66
  159. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +0 -168
  160. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +0 -1239
  161. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +0 -73
  162. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +0 -432
  163. package/src/v2/components/chat/__tests__/CopilotChat.suggestionsAlways.test.tsx +0 -183
  164. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +0 -184
  165. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +0 -649
  166. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +0 -624
  167. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +0 -702
  168. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.tsx +0 -72
  169. package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.tsx +0 -241
  170. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +0 -107
  171. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +0 -929
  172. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +0 -1567
  173. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +0 -1004
  174. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +0 -279
  175. package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +0 -336
  176. package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.tsx +0 -249
  177. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +0 -530
  178. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +0 -785
  179. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +0 -2416
  180. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +0 -621
  181. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +0 -56
  182. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +0 -264
  183. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +0 -853
  184. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +0 -94
  185. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +0 -1050
  186. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +0 -484
  187. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +0 -612
  188. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.tsx +0 -159
  189. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +0 -502
  190. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +0 -1068
  191. package/src/v2/components/chat/__tests__/MCPAppsProxy.e2e.test.tsx +0 -589
  192. package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.tsx +0 -403
  193. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -137
  194. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +0 -37
  195. package/src/v2/components/chat/__tests__/setup.ts +0 -1
  196. package/src/v2/components/chat/index.ts +0 -90
  197. package/src/v2/components/chat/last-user-message-context.ts +0 -21
  198. package/src/v2/components/chat/normalize-auto-scroll.ts +0 -17
  199. package/src/v2/components/chat/scroll-element-context.ts +0 -13
  200. package/src/v2/components/index.ts +0 -8
  201. package/src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx +0 -286
  202. package/src/v2/components/intelligence-indicator/__tests__/IntelligenceIndicator.e2e.test.tsx +0 -464
  203. package/src/v2/components/intelligence-indicator/index.ts +0 -2
  204. package/src/v2/components/license-warning-banner.tsx +0 -217
  205. package/src/v2/components/ui/button.tsx +0 -124
  206. package/src/v2/components/ui/dropdown-menu.tsx +0 -258
  207. package/src/v2/components/ui/tooltip.tsx +0 -60
  208. package/src/v2/context.ts +0 -62
  209. package/src/v2/headless.ts +0 -64
  210. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +0 -152
  211. package/src/v2/hooks/__tests__/standard-schema.test.tsx +0 -282
  212. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +0 -140
  213. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +0 -401
  214. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +0 -44
  215. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +0 -211
  216. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +0 -1029
  217. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +0 -159
  218. package/src/v2/hooks/__tests__/use-attachments.test.tsx +0 -169
  219. package/src/v2/hooks/__tests__/use-capabilities.test.tsx +0 -76
  220. package/src/v2/hooks/__tests__/use-component.test.tsx +0 -126
  221. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +0 -696
  222. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +0 -153
  223. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +0 -167
  224. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +0 -2148
  225. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +0 -1261
  226. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +0 -397
  227. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +0 -56
  228. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +0 -192
  229. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +0 -219
  230. package/src/v2/hooks/__tests__/use-render-custom-messages.test.tsx +0 -55
  231. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +0 -259
  232. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +0 -524
  233. package/src/v2/hooks/__tests__/use-threads.test.tsx +0 -757
  234. package/src/v2/hooks/__tests__/zod-regression.test.tsx +0 -311
  235. package/src/v2/hooks/index.ts +0 -24
  236. package/src/v2/hooks/use-agent-context.tsx +0 -45
  237. package/src/v2/hooks/use-agent.tsx +0 -227
  238. package/src/v2/hooks/use-attachments.tsx +0 -269
  239. package/src/v2/hooks/use-capabilities.tsx +0 -25
  240. package/src/v2/hooks/use-component.tsx +0 -91
  241. package/src/v2/hooks/use-configure-suggestions.tsx +0 -236
  242. package/src/v2/hooks/use-default-render-tool.tsx +0 -271
  243. package/src/v2/hooks/use-frontend-tool.tsx +0 -46
  244. package/src/v2/hooks/use-human-in-the-loop.tsx +0 -81
  245. package/src/v2/hooks/use-interrupt.tsx +0 -305
  246. package/src/v2/hooks/use-keyboard-height.tsx +0 -67
  247. package/src/v2/hooks/use-pin-to-send.ts +0 -94
  248. package/src/v2/hooks/use-render-activity-message.tsx +0 -72
  249. package/src/v2/hooks/use-render-custom-messages.tsx +0 -93
  250. package/src/v2/hooks/use-render-tool-call.tsx +0 -208
  251. package/src/v2/hooks/use-render-tool.tsx +0 -184
  252. package/src/v2/hooks/use-suggestions.tsx +0 -91
  253. package/src/v2/hooks/use-threads.tsx +0 -325
  254. package/src/v2/hooks/useKatexStyles.ts +0 -27
  255. package/src/v2/index.css +0 -1
  256. package/src/v2/index.ts +0 -27
  257. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +0 -495
  258. package/src/v2/lib/__tests__/processPartialHtml.test.ts +0 -112
  259. package/src/v2/lib/__tests__/renderSlot.test.tsx +0 -588
  260. package/src/v2/lib/__tests__/slots.test.ts +0 -56
  261. package/src/v2/lib/processPartialHtml.ts +0 -45
  262. package/src/v2/lib/react-core.ts +0 -156
  263. package/src/v2/lib/slots.tsx +0 -184
  264. package/src/v2/lib/transcription-client.ts +0 -184
  265. package/src/v2/lib/utils.ts +0 -8
  266. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +0 -196
  267. package/src/v2/providers/CopilotKitProvider.tsx +0 -800
  268. package/src/v2/providers/SandboxFunctionsContext.ts +0 -10
  269. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +0 -652
  270. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +0 -101
  271. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +0 -69
  272. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +0 -881
  273. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.tsx +0 -198
  274. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +0 -740
  275. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +0 -713
  276. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +0 -294
  277. package/src/v2/providers/index.ts +0 -21
  278. package/src/v2/styles/globals.css +0 -349
  279. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +0 -525
  280. package/src/v2/types/defineToolCallRenderer.ts +0 -68
  281. package/src/v2/types/frontend-tool.ts +0 -8
  282. package/src/v2/types/human-in-the-loop.ts +0 -33
  283. package/src/v2/types/index.ts +0 -8
  284. package/src/v2/types/interrupt.ts +0 -15
  285. package/src/v2/types/react-activity-message-renderer.ts +0 -27
  286. package/src/v2/types/react-custom-message-renderer.ts +0 -17
  287. package/src/v2/types/react-tool-call-renderer.ts +0 -35
  288. package/src/v2/types/sandbox-function.ts +0 -11
  289. package/tsconfig.json +0 -8
  290. package/tsdown.config.ts +0 -193
  291. package/typedoc.json +0 -4
  292. package/vitest.config.mjs +0 -31
@@ -1,1068 +0,0 @@
1
- /**
2
- * End-to-end tests for MCP Apps Activity Renderer
3
- *
4
- * Tests the complete flow of rendering MCP Apps UI:
5
- * 1. Activity snapshot received with resourceUri
6
- * 2. Resource fetched via proxied MCP request
7
- * 3. Sandboxed iframe created and communicates via JSON-RPC
8
- * 4. Tool calls proxied back through the agent
9
- */
10
- import { fireEvent, screen, waitFor, act } from "@testing-library/react";
11
- import { z } from "zod";
12
- import { vi } from "vitest";
13
- import {
14
- activitySnapshotEvent,
15
- renderWithCopilotKit,
16
- runFinishedEvent,
17
- runStartedEvent,
18
- testId,
19
- } from "../../../__tests__/utils/test-helpers";
20
- import {
21
- MCPAppsActivityType,
22
- MCPAppsActivityContentSchema,
23
- } from "../../../components/MCPAppsActivityRenderer";
24
- import { ReactActivityMessageRenderer } from "../../../types";
25
- import {
26
- AbstractAgent,
27
- RunAgentInput,
28
- RunAgentResult,
29
- BaseEvent,
30
- EventType,
31
- } from "@ag-ui/client";
32
- import { Observable, Subject } from "rxjs";
33
-
34
- /**
35
- * Mock agent that intercepts runAgent calls for proxied MCP requests
36
- * while preserving normal streaming behavior for regular runs.
37
- */
38
- class MockMCPProxyAgent extends AbstractAgent {
39
- private subject = new Subject<BaseEvent>();
40
-
41
- // Track runAgent calls for verification
42
- public runAgentCalls: Array<{ input: Partial<RunAgentInput> }> = [];
43
-
44
- // Configurable responses for proxied MCP requests
45
- private runAgentResponses: Map<string, unknown> = new Map();
46
-
47
- /**
48
- * Set the response for a specific MCP method
49
- */
50
- setRunAgentResponse(method: string, response: unknown) {
51
- this.runAgentResponses.set(method, response);
52
- }
53
-
54
- /**
55
- * Emit a single agent event
56
- */
57
- emit(event: BaseEvent) {
58
- if (event.type === EventType.RUN_STARTED) {
59
- this.isRunning = true;
60
- } else if (
61
- event.type === EventType.RUN_FINISHED ||
62
- event.type === EventType.RUN_ERROR
63
- ) {
64
- this.isRunning = false;
65
- }
66
- act(() => {
67
- this.subject.next(event);
68
- });
69
- }
70
-
71
- /**
72
- * Complete the agent stream
73
- */
74
- complete() {
75
- this.isRunning = false;
76
- act(() => {
77
- this.subject.complete();
78
- });
79
- }
80
-
81
- clone(): MockMCPProxyAgent {
82
- const cloned = new MockMCPProxyAgent();
83
- cloned.agentId = this.agentId;
84
- type Internal = {
85
- subject: Subject<BaseEvent>;
86
- runAgentCalls: Array<{ input: Partial<RunAgentInput> }>;
87
- runAgentResponses: Map<string, unknown>;
88
- };
89
- (cloned as unknown as Internal).subject = (
90
- this as unknown as Internal
91
- ).subject;
92
- (cloned as unknown as Internal).runAgentCalls = (
93
- this as unknown as Internal
94
- ).runAgentCalls;
95
- (cloned as unknown as Internal).runAgentResponses = (
96
- this as unknown as Internal
97
- ).runAgentResponses;
98
- // Share isRunning with the original so that emit(runFinishedEvent()) on the
99
- // registry is visible to waitForAgentIdle() which now receives the clone.
100
- //
101
- // Also proxy runAgent dynamically so tests that monkey-patch agent.runAgent
102
- // after renderWithCopilotKit (which creates the clone) still take effect.
103
- // The clone is created and cached before tests can override runAgent, so a
104
- // static copy would always see the pre-patch prototype method.
105
- const registry = this;
106
- Object.defineProperty(cloned, "isRunning", {
107
- get() {
108
- return registry.isRunning;
109
- },
110
- set(v: boolean) {
111
- registry.isRunning = v;
112
- },
113
- configurable: true,
114
- enumerable: true,
115
- });
116
- // Override runAgent so that:
117
- // - Proxied MCP requests delegate to registry.runAgent (picking up any
118
- // monkey-patches the test installed after renderWithCopilotKit).
119
- // - User-message runs call the prototype method with `this = clone` so
120
- // that clone.messages is updated (CopilotKit renders clone.messages).
121
- const proto = MockMCPProxyAgent.prototype;
122
- cloned.runAgent = async function (
123
- input?: Partial<RunAgentInput>,
124
- ): Promise<RunAgentResult> {
125
- const proxiedRequest = input?.forwardedProps?.__proxiedMCPRequest;
126
- if (proxiedRequest) {
127
- // Delegate to the registry so that monkey-patches applied by tests
128
- // (e.g. "throws an error", "uses a controlled promise") take effect.
129
- return registry.runAgent(input);
130
- }
131
- // For user-message runs: call the prototype method bound to the clone
132
- // so AbstractAgent.runAgent processes events on clone and updates
133
- // clone.messages (which is what CopilotKit reads to render messages).
134
- return proto.runAgent.call(cloned, input);
135
- };
136
- return cloned;
137
- }
138
-
139
- async detachActiveRun(): Promise<void> {}
140
-
141
- run(_input: RunAgentInput): Observable<BaseEvent> {
142
- return this.subject.asObservable();
143
- }
144
-
145
- /**
146
- * Override runAgent to intercept proxied MCP requests only.
147
- * For normal message flows, delegate to the parent class.
148
- */
149
- async runAgent(input?: Partial<RunAgentInput>): Promise<RunAgentResult> {
150
- const proxiedRequest = input?.forwardedProps?.__proxiedMCPRequest as
151
- | {
152
- serverHash?: string;
153
- serverId?: string;
154
- method: string;
155
- params?: Record<string, unknown>;
156
- }
157
- | undefined;
158
-
159
- // Only intercept proxied MCP requests
160
- if (proxiedRequest) {
161
- if (input) {
162
- this.runAgentCalls.push({ input });
163
- }
164
-
165
- const method = proxiedRequest.method;
166
- const response = this.runAgentResponses.get(method);
167
-
168
- if (response !== undefined) {
169
- return { result: response, newMessages: [] };
170
- }
171
-
172
- // Default responses
173
- if (method === "resources/read") {
174
- return {
175
- result: {
176
- contents: [
177
- {
178
- uri: proxiedRequest.params?.uri,
179
- mimeType: "text/html",
180
- text: "<html><body>Test content</body></html>",
181
- },
182
- ],
183
- },
184
- newMessages: [],
185
- };
186
- }
187
-
188
- if (method === "tools/call") {
189
- return {
190
- result: {
191
- content: [{ type: "text", text: "Tool call result" }],
192
- isError: false,
193
- },
194
- newMessages: [],
195
- };
196
- }
197
-
198
- return { result: {}, newMessages: [] };
199
- }
200
-
201
- // For normal runs (user messages), use the parent's runAgent which
202
- // properly subscribes to run() and processes streaming events
203
- return super.runAgent(input);
204
- }
205
- }
206
-
207
- /**
208
- * Helper to create MCP Apps activity content matching the 0.0.2 schema
209
- */
210
- function mcpAppsActivityContent(overrides: {
211
- resourceUri?: string;
212
- serverHash?: string;
213
- serverId?: string;
214
- toolInput?: Record<string, unknown>;
215
- result?: {
216
- content?: unknown[];
217
- structuredContent?: unknown;
218
- isError?: boolean;
219
- };
220
- }) {
221
- return {
222
- resourceUri: overrides.resourceUri ?? "ui://test-server/test-resource",
223
- serverHash: overrides.serverHash ?? "abc123hash",
224
- serverId: overrides.serverId,
225
- toolInput: overrides.toolInput ?? {},
226
- result: overrides.result ?? {
227
- content: [{ type: "text", text: "Tool output" }],
228
- isError: false,
229
- },
230
- };
231
- }
232
-
233
- describe("MCP Apps Activity Renderer E2E", () => {
234
- beforeEach(() => {
235
- // Reset any global state
236
- vi.clearAllMocks();
237
- });
238
-
239
- describe("Resource Fetching", () => {
240
- it("fetches resource content via proxied MCP request on mount", async () => {
241
- const agent = new MockMCPProxyAgent();
242
- const agentId = "mcp-test-agent";
243
- agent.agentId = agentId;
244
-
245
- // Set up response for resources/read
246
- agent.setRunAgentResponse("resources/read", {
247
- contents: [
248
- {
249
- uri: "ui://test-server/dashboard",
250
- mimeType: "text/html",
251
- text: "<html><body>Dashboard content</body></html>",
252
- },
253
- ],
254
- });
255
-
256
- renderWithCopilotKit({
257
- agents: { [agentId]: agent },
258
- agentId,
259
- });
260
-
261
- const input = await screen.findByRole("textbox");
262
- fireEvent.change(input, { target: { value: "Show dashboard" } });
263
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
264
-
265
- await waitFor(() => {
266
- expect(screen.getByText("Show dashboard")).toBeDefined();
267
- });
268
-
269
- const activityMessageId = testId("mcp-activity");
270
- agent.emit(runStartedEvent());
271
- agent.emit(
272
- activitySnapshotEvent({
273
- messageId: activityMessageId,
274
- activityType: MCPAppsActivityType,
275
- content: mcpAppsActivityContent({
276
- resourceUri: "ui://test-server/dashboard",
277
- serverHash: "dashboard-hash-123",
278
- }),
279
- }),
280
- );
281
- agent.emit(runFinishedEvent());
282
-
283
- // Wait for the activity renderer to mount and show loading
284
- await waitFor(
285
- () => {
286
- expect(screen.getByText("Loading...")).toBeDefined();
287
- },
288
- { timeout: 2000 },
289
- );
290
-
291
- // Wait for resource fetch to be called
292
- await waitFor(
293
- () => {
294
- expect(agent.runAgentCalls.length).toBeGreaterThan(0);
295
- },
296
- { timeout: 2000 },
297
- );
298
-
299
- // Verify the proxied MCP request was made correctly
300
- const resourceCall = agent.runAgentCalls.find(
301
- (call) =>
302
- call.input.forwardedProps?.__proxiedMCPRequest?.method ===
303
- "resources/read",
304
- );
305
- expect(resourceCall).toBeDefined();
306
- expect(
307
- resourceCall?.input.forwardedProps?.__proxiedMCPRequest,
308
- ).toMatchObject({
309
- serverHash: "dashboard-hash-123",
310
- method: "resources/read",
311
- params: { uri: "ui://test-server/dashboard" },
312
- });
313
- });
314
-
315
- it("uses serverId when provided (takes precedence over serverHash)", async () => {
316
- const agent = new MockMCPProxyAgent();
317
- const agentId = "mcp-test-agent";
318
- agent.agentId = agentId;
319
-
320
- agent.setRunAgentResponse("resources/read", {
321
- contents: [
322
- {
323
- uri: "ui://my-app/settings",
324
- mimeType: "text/html",
325
- text: "<html><body>Settings</body></html>",
326
- },
327
- ],
328
- });
329
-
330
- renderWithCopilotKit({
331
- agents: { [agentId]: agent },
332
- agentId,
333
- });
334
-
335
- const input = await screen.findByRole("textbox");
336
- fireEvent.change(input, { target: { value: "Show settings" } });
337
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
338
-
339
- await waitFor(() => {
340
- expect(screen.getByText("Show settings")).toBeDefined();
341
- });
342
-
343
- agent.emit(runStartedEvent());
344
- agent.emit(
345
- activitySnapshotEvent({
346
- messageId: testId("mcp-activity"),
347
- activityType: MCPAppsActivityType,
348
- content: mcpAppsActivityContent({
349
- resourceUri: "ui://my-app/settings",
350
- serverHash: "fallback-hash",
351
- serverId: "my-app-stable-id", // Should take precedence
352
- }),
353
- }),
354
- );
355
- agent.emit(runFinishedEvent());
356
-
357
- await waitFor(() => {
358
- const resourceCall = agent.runAgentCalls.find(
359
- (call) =>
360
- call.input.forwardedProps?.__proxiedMCPRequest?.method ===
361
- "resources/read",
362
- );
363
- expect(resourceCall).toBeDefined();
364
- expect(
365
- resourceCall?.input.forwardedProps?.__proxiedMCPRequest?.serverId,
366
- ).toBe("my-app-stable-id");
367
- expect(
368
- resourceCall?.input.forwardedProps?.__proxiedMCPRequest?.serverHash,
369
- ).toBe("fallback-hash");
370
- });
371
- });
372
-
373
- it("shows loading state while fetching resource", async () => {
374
- const agent = new MockMCPProxyAgent();
375
- const agentId = "mcp-test-agent";
376
- agent.agentId = agentId;
377
-
378
- // Create a promise that we can control
379
- let resolveResource: (value: unknown) => void;
380
- const resourcePromise = new Promise((resolve) => {
381
- resolveResource = resolve;
382
- });
383
-
384
- // Override runAgent to use our controlled promise
385
- const originalRunAgent = agent.runAgent.bind(agent);
386
- agent.runAgent = async (
387
- input?: Partial<RunAgentInput>,
388
- ): Promise<RunAgentResult> => {
389
- const proxiedRequest = input?.forwardedProps?.__proxiedMCPRequest as
390
- | { method: string }
391
- | undefined;
392
- if (proxiedRequest?.method === "resources/read") {
393
- await resourcePromise;
394
- return originalRunAgent(input);
395
- }
396
- return originalRunAgent(input);
397
- };
398
-
399
- renderWithCopilotKit({
400
- agents: { [agentId]: agent },
401
- agentId,
402
- });
403
-
404
- const input = await screen.findByRole("textbox");
405
- fireEvent.change(input, { target: { value: "Load app" } });
406
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
407
-
408
- await waitFor(() => {
409
- expect(screen.getByText("Load app")).toBeDefined();
410
- });
411
-
412
- agent.emit(runStartedEvent());
413
- agent.emit(
414
- activitySnapshotEvent({
415
- messageId: testId("mcp-activity"),
416
- activityType: MCPAppsActivityType,
417
- content: mcpAppsActivityContent({
418
- resourceUri: "ui://test/app",
419
- serverHash: "test-hash",
420
- }),
421
- }),
422
- );
423
- agent.emit(runFinishedEvent());
424
-
425
- // Should show loading state
426
- await waitFor(() => {
427
- expect(screen.getByText("Loading...")).toBeDefined();
428
- });
429
-
430
- // Resolve the resource fetch
431
- act(() => {
432
- resolveResource!(true);
433
- });
434
-
435
- // Loading should eventually disappear (iframe created)
436
- await waitFor(
437
- () => {
438
- expect(screen.queryByText("Loading...")).toBeNull();
439
- },
440
- { timeout: 3000 },
441
- );
442
- });
443
-
444
- it("shows error state when resource fetch fails", async () => {
445
- const agent = new MockMCPProxyAgent();
446
- const agentId = "mcp-test-agent";
447
- agent.agentId = agentId;
448
-
449
- // Make proxied MCP requests throw an error
450
- const originalRunAgent = agent.runAgent.bind(agent);
451
- agent.runAgent = async (
452
- input?: Partial<RunAgentInput>,
453
- ): Promise<RunAgentResult> => {
454
- const proxiedRequest = input?.forwardedProps?.__proxiedMCPRequest as
455
- | { method: string }
456
- | undefined;
457
- if (proxiedRequest) {
458
- throw new Error("Network error: Failed to fetch resource");
459
- }
460
- return originalRunAgent(input);
461
- };
462
-
463
- renderWithCopilotKit({
464
- agents: { [agentId]: agent },
465
- agentId,
466
- });
467
-
468
- const input = await screen.findByRole("textbox");
469
- fireEvent.change(input, { target: { value: "Fetch broken" } });
470
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
471
-
472
- await waitFor(() => {
473
- expect(screen.getByText("Fetch broken")).toBeDefined();
474
- });
475
-
476
- agent.emit(runStartedEvent());
477
- agent.emit(
478
- activitySnapshotEvent({
479
- messageId: testId("mcp-activity"),
480
- activityType: MCPAppsActivityType,
481
- content: mcpAppsActivityContent({
482
- resourceUri: "ui://broken/resource",
483
- serverHash: "broken-hash",
484
- }),
485
- }),
486
- );
487
- agent.emit(runFinishedEvent());
488
-
489
- // Should show error state
490
- await waitFor(() => {
491
- expect(
492
- screen.getByText(/Error:.*Failed to fetch resource/i),
493
- ).toBeDefined();
494
- });
495
- });
496
-
497
- it("handles resource with no content gracefully", async () => {
498
- const agent = new MockMCPProxyAgent();
499
- const agentId = "mcp-test-agent";
500
- agent.agentId = agentId;
501
-
502
- // Return empty contents
503
- agent.setRunAgentResponse("resources/read", {
504
- contents: [],
505
- });
506
-
507
- renderWithCopilotKit({
508
- agents: { [agentId]: agent },
509
- agentId,
510
- });
511
-
512
- const input = await screen.findByRole("textbox");
513
- fireEvent.change(input, { target: { value: "Empty resource" } });
514
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
515
-
516
- await waitFor(() => {
517
- expect(screen.getByText("Empty resource")).toBeDefined();
518
- });
519
-
520
- agent.emit(runStartedEvent());
521
- agent.emit(
522
- activitySnapshotEvent({
523
- messageId: testId("mcp-activity"),
524
- activityType: MCPAppsActivityType,
525
- content: mcpAppsActivityContent({
526
- resourceUri: "ui://empty/resource",
527
- serverHash: "empty-hash",
528
- }),
529
- }),
530
- );
531
- agent.emit(runFinishedEvent());
532
-
533
- // Should show error about no content
534
- await waitFor(() => {
535
- expect(screen.getByText(/Error:.*No resource content/i)).toBeDefined();
536
- });
537
- });
538
- });
539
-
540
- describe("Schema Validation", () => {
541
- it("validates activity content with the correct schema", () => {
542
- // Valid content
543
- const validContent = {
544
- resourceUri: "ui://server/resource",
545
- serverHash: "hash123",
546
- result: {
547
- content: [{ type: "text", text: "output" }],
548
- isError: false,
549
- },
550
- };
551
-
552
- const validResult = MCPAppsActivityContentSchema.safeParse(validContent);
553
- expect(validResult.success).toBe(true);
554
-
555
- // With optional serverId
556
- const withServerId = {
557
- ...validContent,
558
- serverId: "stable-server-id",
559
- };
560
- const serverIdResult =
561
- MCPAppsActivityContentSchema.safeParse(withServerId);
562
- expect(serverIdResult.success).toBe(true);
563
-
564
- // With toolInput
565
- const withToolInput = {
566
- ...validContent,
567
- toolInput: { param1: "value1", param2: 42 },
568
- };
569
- const toolInputResult =
570
- MCPAppsActivityContentSchema.safeParse(withToolInput);
571
- expect(toolInputResult.success).toBe(true);
572
- });
573
-
574
- it("rejects invalid activity content", () => {
575
- // Missing required fields
576
- const missingResourceUri = {
577
- serverHash: "hash123",
578
- result: { isError: false },
579
- };
580
- expect(
581
- MCPAppsActivityContentSchema.safeParse(missingResourceUri).success,
582
- ).toBe(false);
583
-
584
- const missingServerHash = {
585
- resourceUri: "ui://server/resource",
586
- result: { isError: false },
587
- };
588
- expect(
589
- MCPAppsActivityContentSchema.safeParse(missingServerHash).success,
590
- ).toBe(false);
591
-
592
- const missingResult = {
593
- resourceUri: "ui://server/resource",
594
- serverHash: "hash123",
595
- };
596
- expect(
597
- MCPAppsActivityContentSchema.safeParse(missingResult).success,
598
- ).toBe(false);
599
- });
600
- });
601
-
602
- describe("Activity Type Integration", () => {
603
- it("built-in MCP Apps renderer is registered with correct activity type", async () => {
604
- const agent = new MockMCPProxyAgent();
605
- const agentId = "mcp-test-agent";
606
- agent.agentId = agentId;
607
-
608
- renderWithCopilotKit({
609
- agents: { [agentId]: agent },
610
- agentId,
611
- // Don't pass any custom renderers - built-in should be used
612
- });
613
-
614
- const input = await screen.findByRole("textbox");
615
- fireEvent.change(input, { target: { value: "Test MCP" } });
616
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
617
-
618
- await waitFor(() => {
619
- expect(screen.getByText("Test MCP")).toBeDefined();
620
- });
621
-
622
- agent.emit(runStartedEvent());
623
- agent.emit(
624
- activitySnapshotEvent({
625
- messageId: testId("mcp-activity"),
626
- activityType: "mcp-apps", // Should match MCPAppsActivityType
627
- content: mcpAppsActivityContent({
628
- resourceUri: "ui://builtin/test",
629
- serverHash: "builtin-hash",
630
- }),
631
- }),
632
- );
633
- agent.emit(runFinishedEvent());
634
-
635
- // Should show loading (meaning the renderer was matched)
636
- await waitFor(() => {
637
- expect(screen.getByText("Loading...")).toBeDefined();
638
- });
639
- });
640
-
641
- it("user-provided renderer takes precedence over built-in", async () => {
642
- const agent = new MockMCPProxyAgent();
643
- const agentId = "mcp-test-agent";
644
- agent.agentId = agentId;
645
-
646
- // Custom renderer that overrides the built-in
647
- const customRenderer: ReactActivityMessageRenderer<
648
- z.infer<typeof MCPAppsActivityContentSchema>
649
- > = {
650
- activityType: MCPAppsActivityType,
651
- content: MCPAppsActivityContentSchema,
652
- render: ({ content }) => (
653
- <div data-testid="custom-mcp-renderer">
654
- Custom MCP Renderer: {content.resourceUri}
655
- </div>
656
- ),
657
- };
658
-
659
- renderWithCopilotKit({
660
- agents: { [agentId]: agent },
661
- agentId,
662
- renderActivityMessages: [customRenderer],
663
- });
664
-
665
- const input = await screen.findByRole("textbox");
666
- fireEvent.change(input, { target: { value: "Custom renderer" } });
667
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
668
-
669
- await waitFor(() => {
670
- expect(screen.getByText("Custom renderer")).toBeDefined();
671
- });
672
-
673
- agent.emit(runStartedEvent());
674
- agent.emit(
675
- activitySnapshotEvent({
676
- messageId: testId("mcp-activity"),
677
- activityType: MCPAppsActivityType,
678
- content: mcpAppsActivityContent({
679
- resourceUri: "ui://custom/resource",
680
- serverHash: "custom-hash",
681
- }),
682
- }),
683
- );
684
- agent.emit(runFinishedEvent());
685
-
686
- // Should render custom component, not loading
687
- await waitFor(() => {
688
- expect(screen.getByTestId("custom-mcp-renderer")).toBeDefined();
689
- expect(
690
- screen.getByText(/Custom MCP Renderer:.*ui:\/\/custom\/resource/),
691
- ).toBeDefined();
692
- });
693
- });
694
- });
695
-
696
- describe("Multiple Activity Messages", () => {
697
- it("renders multiple MCP Apps activities independently", async () => {
698
- const agent = new MockMCPProxyAgent();
699
- const agentId = "mcp-test-agent";
700
- agent.agentId = agentId;
701
-
702
- // Set up different responses for different URIs
703
- const originalRunAgent = agent.runAgent.bind(agent);
704
- agent.runAgent = async (
705
- input?: Partial<RunAgentInput>,
706
- ): Promise<RunAgentResult> => {
707
- const proxiedRequest = input?.forwardedProps?.__proxiedMCPRequest as {
708
- method: string;
709
- params?: { uri?: string };
710
- };
711
- if (proxiedRequest?.method === "resources/read") {
712
- const uri = proxiedRequest.params?.uri;
713
- if (uri === "ui://first/app") {
714
- return {
715
- result: {
716
- contents: [
717
- { uri, mimeType: "text/html", text: "<div>First App</div>" },
718
- ],
719
- },
720
- newMessages: [],
721
- };
722
- }
723
- if (uri === "ui://second/app") {
724
- return {
725
- result: {
726
- contents: [
727
- { uri, mimeType: "text/html", text: "<div>Second App</div>" },
728
- ],
729
- },
730
- newMessages: [],
731
- };
732
- }
733
- }
734
- // For non-proxied requests, use original behavior
735
- return originalRunAgent(input);
736
- };
737
-
738
- renderWithCopilotKit({
739
- agents: { [agentId]: agent },
740
- agentId,
741
- });
742
-
743
- const input = await screen.findByRole("textbox");
744
- fireEvent.change(input, { target: { value: "Multiple apps" } });
745
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
746
-
747
- await waitFor(() => {
748
- expect(screen.getByText("Multiple apps")).toBeDefined();
749
- });
750
-
751
- agent.emit(runStartedEvent());
752
-
753
- // Emit two activity messages
754
- agent.emit(
755
- activitySnapshotEvent({
756
- messageId: testId("mcp-first"),
757
- activityType: MCPAppsActivityType,
758
- content: mcpAppsActivityContent({
759
- resourceUri: "ui://first/app",
760
- serverHash: "first-hash",
761
- }),
762
- }),
763
- );
764
-
765
- agent.emit(
766
- activitySnapshotEvent({
767
- messageId: testId("mcp-second"),
768
- activityType: MCPAppsActivityType,
769
- content: mcpAppsActivityContent({
770
- resourceUri: "ui://second/app",
771
- serverHash: "second-hash",
772
- }),
773
- }),
774
- );
775
-
776
- agent.emit(runFinishedEvent());
777
-
778
- // Both activities should trigger resource fetches and create iframes.
779
- // Due to async timing, the loading states might clear quickly,
780
- // so we verify both iframes are eventually created.
781
- await waitFor(
782
- () => {
783
- const iframes = document.querySelectorAll("iframe[srcdoc]");
784
- expect(iframes.length).toBe(2);
785
- },
786
- { timeout: 2000 },
787
- );
788
- });
789
- });
790
-
791
- describe("Content Types", () => {
792
- it("handles text content from resource", async () => {
793
- const agent = new MockMCPProxyAgent();
794
- const agentId = "mcp-test-agent";
795
- agent.agentId = agentId;
796
-
797
- agent.setRunAgentResponse("resources/read", {
798
- contents: [
799
- {
800
- uri: "ui://test/text",
801
- mimeType: "text/html",
802
- text: "<html><body><h1>Text Content</h1></body></html>",
803
- },
804
- ],
805
- });
806
-
807
- renderWithCopilotKit({
808
- agents: { [agentId]: agent },
809
- agentId,
810
- });
811
-
812
- const input = await screen.findByRole("textbox");
813
- fireEvent.change(input, { target: { value: "Text content" } });
814
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
815
-
816
- await waitFor(() => {
817
- expect(screen.getByText("Text content")).toBeDefined();
818
- });
819
-
820
- agent.emit(runStartedEvent());
821
- agent.emit(
822
- activitySnapshotEvent({
823
- messageId: testId("mcp-activity"),
824
- activityType: MCPAppsActivityType,
825
- content: mcpAppsActivityContent({
826
- resourceUri: "ui://test/text",
827
- serverHash: "text-hash",
828
- }),
829
- }),
830
- );
831
- agent.emit(runFinishedEvent());
832
-
833
- // Should transition from loading to rendered
834
- await waitFor(() => {
835
- expect(screen.getByText("Loading...")).toBeDefined();
836
- });
837
- });
838
-
839
- it("handles blob (base64) content from resource", async () => {
840
- const agent = new MockMCPProxyAgent();
841
- const agentId = "mcp-test-agent";
842
- agent.agentId = agentId;
843
-
844
- // Base64 encoded "<html><body>Blob Content</body></html>"
845
- const base64Html = btoa("<html><body>Blob Content</body></html>");
846
-
847
- agent.setRunAgentResponse("resources/read", {
848
- contents: [
849
- {
850
- uri: "ui://test/blob",
851
- mimeType: "text/html",
852
- blob: base64Html,
853
- },
854
- ],
855
- });
856
-
857
- renderWithCopilotKit({
858
- agents: { [agentId]: agent },
859
- agentId,
860
- });
861
-
862
- const input = await screen.findByRole("textbox");
863
- fireEvent.change(input, { target: { value: "Blob content" } });
864
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
865
-
866
- await waitFor(() => {
867
- expect(screen.getByText("Blob content")).toBeDefined();
868
- });
869
-
870
- agent.emit(runStartedEvent());
871
- agent.emit(
872
- activitySnapshotEvent({
873
- messageId: testId("mcp-activity"),
874
- activityType: MCPAppsActivityType,
875
- content: mcpAppsActivityContent({
876
- resourceUri: "ui://test/blob",
877
- serverHash: "blob-hash",
878
- }),
879
- }),
880
- );
881
- agent.emit(runFinishedEvent());
882
-
883
- // Should show loading initially
884
- await waitFor(() => {
885
- expect(screen.getByText("Loading...")).toBeDefined();
886
- });
887
- });
888
-
889
- it("handles resource with no text or blob - iframe created but stuck waiting for sandbox", async () => {
890
- // NOTE: In jsdom, the sandbox iframe (using srcdoc) can't fully execute, so the
891
- // component will create an iframe and wait for the sandbox proxy to be ready.
892
- // The actual error for missing text/blob happens inside the sandbox communication
893
- // flow which can't complete in jsdom. This test verifies that:
894
- // 1. The component fetches the resource successfully
895
- // 2. The iframe is created (showing the component progressed past loading)
896
-
897
- const agent = new MockMCPProxyAgent();
898
- const agentId = "mcp-test-agent";
899
- agent.agentId = agentId;
900
-
901
- // Resource with neither text nor blob
902
- agent.setRunAgentResponse("resources/read", {
903
- contents: [
904
- {
905
- uri: "ui://test/empty",
906
- mimeType: "text/html",
907
- // No text or blob field - in real environment this would cause an error
908
- // after sandbox proxy is ready, but in jsdom the proxy never responds
909
- },
910
- ],
911
- });
912
-
913
- renderWithCopilotKit({
914
- agents: { [agentId]: agent },
915
- agentId,
916
- });
917
-
918
- const input = await screen.findByRole("textbox");
919
- fireEvent.change(input, { target: { value: "No content" } });
920
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
921
-
922
- await waitFor(() => {
923
- expect(screen.getByText("No content")).toBeDefined();
924
- });
925
-
926
- agent.emit(runStartedEvent());
927
- agent.emit(
928
- activitySnapshotEvent({
929
- messageId: testId("mcp-activity"),
930
- activityType: MCPAppsActivityType,
931
- content: mcpAppsActivityContent({
932
- resourceUri: "ui://test/empty",
933
- serverHash: "empty-hash",
934
- }),
935
- }),
936
- );
937
- agent.emit(runFinishedEvent());
938
-
939
- // Verify the iframe is created (component progressed past loading)
940
- // In jsdom, the sandbox proxy never responds, so the error for missing text/blob
941
- // is never reached. This is a limitation of jsdom testing.
942
- await waitFor(
943
- () => {
944
- const iframe = document.querySelector("iframe[srcdoc]");
945
- expect(iframe).not.toBeNull();
946
- },
947
- { timeout: 2000 },
948
- );
949
- });
950
- });
951
-
952
- describe("Metadata Handling", () => {
953
- it("applies border styling when prefersBorder is true", async () => {
954
- const agent = new MockMCPProxyAgent();
955
- const agentId = "mcp-test-agent";
956
- agent.agentId = agentId;
957
-
958
- agent.setRunAgentResponse("resources/read", {
959
- contents: [
960
- {
961
- uri: "ui://test/bordered",
962
- mimeType: "text/html",
963
- text: "<html><body>Bordered Content</body></html>",
964
- _meta: {
965
- ui: {
966
- prefersBorder: true,
967
- },
968
- },
969
- },
970
- ],
971
- });
972
-
973
- renderWithCopilotKit({
974
- agents: { [agentId]: agent },
975
- agentId,
976
- });
977
-
978
- const input = await screen.findByRole("textbox");
979
- fireEvent.change(input, { target: { value: "Bordered app" } });
980
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
981
-
982
- await waitFor(() => {
983
- expect(screen.getByText("Bordered app")).toBeDefined();
984
- });
985
-
986
- agent.emit(runStartedEvent());
987
- agent.emit(
988
- activitySnapshotEvent({
989
- messageId: testId("mcp-activity"),
990
- activityType: MCPAppsActivityType,
991
- content: mcpAppsActivityContent({
992
- resourceUri: "ui://test/bordered",
993
- serverHash: "bordered-hash",
994
- }),
995
- }),
996
- );
997
- agent.emit(runFinishedEvent());
998
-
999
- // Wait for resource to be fetched and iframe to be created
1000
- await waitFor(
1001
- () => {
1002
- // Loading should disappear
1003
- expect(screen.queryByText("Loading...")).toBeNull();
1004
- // Iframe should be created
1005
- const iframe = document.querySelector("iframe[srcdoc]");
1006
- expect(iframe).not.toBeNull();
1007
- },
1008
- { timeout: 3000 },
1009
- );
1010
-
1011
- // Note: Border styling is applied via inline styles based on prefersBorder metadata.
1012
- // In jsdom, verifying inline styles is not reliable, but we've verified the component
1013
- // renders successfully with the metadata that includes prefersBorder: true.
1014
- });
1015
-
1016
- it("does not apply border styling when prefersBorder is false", async () => {
1017
- const agent = new MockMCPProxyAgent();
1018
- const agentId = "mcp-test-agent";
1019
- agent.agentId = agentId;
1020
-
1021
- agent.setRunAgentResponse("resources/read", {
1022
- contents: [
1023
- {
1024
- uri: "ui://test/borderless",
1025
- mimeType: "text/html",
1026
- text: "<html><body>Borderless Content</body></html>",
1027
- _meta: {
1028
- ui: {
1029
- prefersBorder: false,
1030
- },
1031
- },
1032
- },
1033
- ],
1034
- });
1035
-
1036
- renderWithCopilotKit({
1037
- agents: { [agentId]: agent },
1038
- agentId,
1039
- });
1040
-
1041
- const input = await screen.findByRole("textbox");
1042
- fireEvent.change(input, { target: { value: "Borderless app" } });
1043
- fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
1044
-
1045
- await waitFor(() => {
1046
- expect(screen.getByText("Borderless app")).toBeDefined();
1047
- });
1048
-
1049
- agent.emit(runStartedEvent());
1050
- agent.emit(
1051
- activitySnapshotEvent({
1052
- messageId: testId("mcp-activity"),
1053
- activityType: MCPAppsActivityType,
1054
- content: mcpAppsActivityContent({
1055
- resourceUri: "ui://test/borderless",
1056
- serverHash: "borderless-hash",
1057
- }),
1058
- }),
1059
- );
1060
- agent.emit(runFinishedEvent());
1061
-
1062
- // Verify component renders without error
1063
- await waitFor(() => {
1064
- expect(screen.getByText("Loading...")).toBeDefined();
1065
- });
1066
- });
1067
- });
1068
- });