@copilotkit/react-core 1.57.3 → 1.58.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (282) hide show
  1. package/LICENSE +21 -0
  2. package/dist/{copilotkit-CC8DjOiC.mjs → copilotkit-BIn7HE8f.mjs} +2 -2
  3. package/dist/{copilotkit-CC8DjOiC.mjs.map → copilotkit-BIn7HE8f.mjs.map} +1 -1
  4. package/dist/{copilotkit-CtXcs1ea.cjs → copilotkit-Drw-g6zA.cjs} +2 -2
  5. package/dist/{copilotkit-CtXcs1ea.cjs.map → copilotkit-Drw-g6zA.cjs.map} +1 -1
  6. package/dist/index.cjs +3 -77
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +2 -2
  9. package/dist/index.d.mts +2 -2
  10. package/dist/index.mjs +3 -77
  11. package/dist/index.mjs.map +1 -1
  12. package/dist/index.umd.js +3 -77
  13. package/dist/index.umd.js.map +1 -1
  14. package/dist/v2/index.cjs +1 -1
  15. package/dist/v2/index.mjs +1 -1
  16. package/dist/v2/index.umd.js +1 -1
  17. package/dist/v2/index.umd.js.map +1 -1
  18. package/package.json +12 -13
  19. package/skills/react-core/SKILL.md +108 -0
  20. package/skills/react-core/references/agent-access.md +288 -0
  21. package/skills/react-core/references/attachments.md +291 -0
  22. package/skills/react-core/references/capabilities.md +138 -0
  23. package/skills/react-core/references/chat-components.md +221 -0
  24. package/skills/react-core/references/client-side-tools.md +358 -0
  25. package/skills/react-core/references/custom-message-renderers.md +226 -0
  26. package/skills/react-core/references/debug-mode.md +153 -0
  27. package/skills/react-core/references/human-in-the-loop.md +312 -0
  28. package/skills/react-core/references/provider-setup.md +326 -0
  29. package/skills/react-core/references/rendering-activity-messages.md +207 -0
  30. package/skills/react-core/references/rendering-tool-calls.md +319 -0
  31. package/skills/react-core/references/suggestions.md +211 -0
  32. package/skills/react-core/references/switching-agents-recipes.md +160 -0
  33. package/skills/react-core/references/switching-agents.md +231 -0
  34. package/skills/react-core/references/threads.md +226 -0
  35. package/.attw.json +0 -3
  36. package/CHANGELOG.md +0 -5043
  37. package/scripts/scope-preflight.mjs +0 -100
  38. package/src/components/CopilotListeners.tsx +0 -137
  39. package/src/components/__tests__/CopilotListeners.test.tsx +0 -38
  40. package/src/components/copilot-provider/__tests__/copilot-messages-key.test.tsx +0 -92
  41. package/src/components/copilot-provider/__tests__/copilotkit-error.test.tsx +0 -77
  42. package/src/components/copilot-provider/__tests__/error-visibility-prod.test.tsx +0 -70
  43. package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +0 -107
  44. package/src/components/copilot-provider/copilot-messages.tsx +0 -314
  45. package/src/components/copilot-provider/copilotkit-props.tsx +0 -214
  46. package/src/components/copilot-provider/copilotkit.tsx +0 -853
  47. package/src/components/copilot-provider/index.ts +0 -3
  48. package/src/components/dev-console/console-trigger.tsx +0 -283
  49. package/src/components/dev-console/developer-console-modal.tsx +0 -1016
  50. package/src/components/dev-console/icons.tsx +0 -106
  51. package/src/components/error-boundary/error-boundary.tsx +0 -99
  52. package/src/components/error-boundary/error-utils.tsx +0 -105
  53. package/src/components/index.ts +0 -1
  54. package/src/components/toast/exclamation-mark-icon.tsx +0 -27
  55. package/src/components/toast/toast-provider.tsx +0 -448
  56. package/src/components/usage-banner.tsx +0 -266
  57. package/src/context/__tests__/threads-context.test.tsx +0 -141
  58. package/src/context/coagent-state-renders-context.tsx +0 -89
  59. package/src/context/copilot-context.tsx +0 -365
  60. package/src/context/copilot-messages-context.tsx +0 -35
  61. package/src/context/index.ts +0 -22
  62. package/src/context/threads-context.tsx +0 -69
  63. package/src/hooks/__tests__/use-coagent-config.test.ts +0 -352
  64. package/src/hooks/__tests__/use-coagent-state-render-bridge.helpers.test.ts +0 -107
  65. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +0 -1209
  66. package/src/hooks/__tests__/use-coagent-state-render.test.tsx +0 -356
  67. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +0 -241
  68. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +0 -72
  69. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +0 -102
  70. package/src/hooks/index.ts +0 -33
  71. package/src/hooks/use-agent-nodename.ts +0 -33
  72. package/src/hooks/use-coagent-state-render-bridge.helpers.ts +0 -345
  73. package/src/hooks/use-coagent-state-render-bridge.tsx +0 -222
  74. package/src/hooks/use-coagent-state-render-registry.ts +0 -230
  75. package/src/hooks/use-coagent-state-render.ts +0 -163
  76. package/src/hooks/use-coagent.ts +0 -377
  77. package/src/hooks/use-configure-chat-suggestions.tsx +0 -96
  78. package/src/hooks/use-copilot-action.ts +0 -245
  79. package/src/hooks/use-copilot-additional-instructions.ts +0 -98
  80. package/src/hooks/use-copilot-authenticated-action.ts +0 -73
  81. package/src/hooks/use-copilot-chat-headless_c.ts +0 -264
  82. package/src/hooks/use-copilot-chat-suggestions.tsx +0 -134
  83. package/src/hooks/use-copilot-chat.ts +0 -132
  84. package/src/hooks/use-copilot-chat_internal.ts +0 -875
  85. package/src/hooks/use-copilot-readable.ts +0 -135
  86. package/src/hooks/use-copilot-runtime-client.ts +0 -178
  87. package/src/hooks/use-default-tool.ts +0 -13
  88. package/src/hooks/use-flat-category-store.ts +0 -109
  89. package/src/hooks/use-frontend-tool.ts +0 -113
  90. package/src/hooks/use-human-in-the-loop.ts +0 -138
  91. package/src/hooks/use-langgraph-interrupt.ts +0 -103
  92. package/src/hooks/use-lazy-tool-renderer.tsx +0 -30
  93. package/src/hooks/use-make-copilot-document-readable.ts +0 -30
  94. package/src/hooks/use-render-tool-call.ts +0 -89
  95. package/src/hooks/use-tree.ts +0 -222
  96. package/src/index.tsx +0 -7
  97. package/src/lib/copilot-task.ts +0 -215
  98. package/src/lib/index.ts +0 -1
  99. package/src/lib/status-checker.ts +0 -67
  100. package/src/setupTests.ts +0 -37
  101. package/src/test-helpers/copilot-context.ts +0 -91
  102. package/src/types/chat-suggestion-configuration.ts +0 -23
  103. package/src/types/coagent-action.ts +0 -35
  104. package/src/types/coagent-state.ts +0 -13
  105. package/src/types/crew.ts +0 -89
  106. package/src/types/document-pointer.ts +0 -7
  107. package/src/types/frontend-action.ts +0 -213
  108. package/src/types/index.ts +0 -17
  109. package/src/types/interrupt-action.ts +0 -58
  110. package/src/types/system-message.ts +0 -4
  111. package/src/utils/dev-console.ts +0 -19
  112. package/src/utils/index.ts +0 -2
  113. package/src/utils/suggestions-constants.ts +0 -8
  114. package/src/utils/utils.test.ts +0 -7
  115. package/src/utils/utils.ts +0 -6
  116. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +0 -240
  117. package/src/v2/__tests__/globalSetup.ts +0 -14
  118. package/src/v2/__tests__/setup.ts +0 -93
  119. package/src/v2/__tests__/utils/test-helpers.tsx +0 -570
  120. package/src/v2/a2ui/A2UICatalogContext.tsx +0 -79
  121. package/src/v2/a2ui/A2UIMessageRenderer.tsx +0 -294
  122. package/src/v2/a2ui/A2UIToolCallRenderer.tsx +0 -290
  123. package/src/v2/components/CopilotKitInspector.tsx +0 -52
  124. package/src/v2/components/MCPAppsActivityRenderer.tsx +0 -815
  125. package/src/v2/components/OpenGenerativeUIRenderer.tsx +0 -598
  126. package/src/v2/components/WildcardToolCallRender.tsx +0 -86
  127. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.tsx +0 -665
  128. package/src/v2/components/chat/CopilotChat.tsx +0 -664
  129. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +0 -393
  130. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +0 -374
  131. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +0 -159
  132. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +0 -350
  133. package/src/v2/components/chat/CopilotChatInput.tsx +0 -1412
  134. package/src/v2/components/chat/CopilotChatMessageView.tsx +0 -716
  135. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +0 -265
  136. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +0 -59
  137. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +0 -134
  138. package/src/v2/components/chat/CopilotChatToggleButton.tsx +0 -171
  139. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +0 -40
  140. package/src/v2/components/chat/CopilotChatUserMessage.tsx +0 -445
  141. package/src/v2/components/chat/CopilotChatView.tsx +0 -890
  142. package/src/v2/components/chat/CopilotModalHeader.tsx +0 -129
  143. package/src/v2/components/chat/CopilotPopup.tsx +0 -81
  144. package/src/v2/components/chat/CopilotPopupView.tsx +0 -317
  145. package/src/v2/components/chat/CopilotSidebar.tsx +0 -80
  146. package/src/v2/components/chat/CopilotSidebarView.tsx +0 -269
  147. package/src/v2/components/chat/Lightbox.tsx +0 -103
  148. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +0 -66
  149. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +0 -168
  150. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +0 -1239
  151. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +0 -73
  152. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +0 -432
  153. package/src/v2/components/chat/__tests__/CopilotChat.suggestionsAlways.test.tsx +0 -183
  154. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +0 -184
  155. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +0 -649
  156. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +0 -624
  157. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +0 -702
  158. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.tsx +0 -72
  159. package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.tsx +0 -241
  160. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +0 -107
  161. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +0 -929
  162. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +0 -1567
  163. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +0 -1004
  164. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +0 -279
  165. package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +0 -336
  166. package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.tsx +0 -249
  167. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +0 -530
  168. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +0 -785
  169. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +0 -2416
  170. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +0 -621
  171. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +0 -56
  172. package/src/v2/components/chat/__tests__/CopilotChatView.inputOverlay.test.tsx +0 -264
  173. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +0 -853
  174. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +0 -94
  175. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +0 -1050
  176. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +0 -484
  177. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +0 -612
  178. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.tsx +0 -159
  179. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +0 -502
  180. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +0 -1068
  181. package/src/v2/components/chat/__tests__/MCPAppsProxy.e2e.test.tsx +0 -589
  182. package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.tsx +0 -403
  183. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -137
  184. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +0 -37
  185. package/src/v2/components/chat/__tests__/setup.ts +0 -1
  186. package/src/v2/components/chat/index.ts +0 -90
  187. package/src/v2/components/chat/last-user-message-context.ts +0 -21
  188. package/src/v2/components/chat/normalize-auto-scroll.ts +0 -17
  189. package/src/v2/components/chat/scroll-element-context.ts +0 -13
  190. package/src/v2/components/index.ts +0 -8
  191. package/src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx +0 -286
  192. package/src/v2/components/intelligence-indicator/__tests__/IntelligenceIndicator.e2e.test.tsx +0 -464
  193. package/src/v2/components/intelligence-indicator/index.ts +0 -2
  194. package/src/v2/components/license-warning-banner.tsx +0 -217
  195. package/src/v2/components/ui/button.tsx +0 -124
  196. package/src/v2/components/ui/dropdown-menu.tsx +0 -258
  197. package/src/v2/components/ui/tooltip.tsx +0 -60
  198. package/src/v2/context.ts +0 -62
  199. package/src/v2/headless.ts +0 -64
  200. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +0 -152
  201. package/src/v2/hooks/__tests__/standard-schema.test.tsx +0 -282
  202. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +0 -140
  203. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +0 -401
  204. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +0 -44
  205. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +0 -211
  206. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +0 -1029
  207. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +0 -159
  208. package/src/v2/hooks/__tests__/use-attachments.test.tsx +0 -169
  209. package/src/v2/hooks/__tests__/use-capabilities.test.tsx +0 -76
  210. package/src/v2/hooks/__tests__/use-component.test.tsx +0 -126
  211. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +0 -696
  212. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +0 -153
  213. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +0 -167
  214. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +0 -2148
  215. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +0 -1261
  216. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +0 -397
  217. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +0 -56
  218. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +0 -192
  219. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +0 -219
  220. package/src/v2/hooks/__tests__/use-render-custom-messages.test.tsx +0 -55
  221. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +0 -259
  222. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +0 -524
  223. package/src/v2/hooks/__tests__/use-threads.test.tsx +0 -757
  224. package/src/v2/hooks/__tests__/zod-regression.test.tsx +0 -311
  225. package/src/v2/hooks/index.ts +0 -24
  226. package/src/v2/hooks/use-agent-context.tsx +0 -45
  227. package/src/v2/hooks/use-agent.tsx +0 -227
  228. package/src/v2/hooks/use-attachments.tsx +0 -269
  229. package/src/v2/hooks/use-capabilities.tsx +0 -25
  230. package/src/v2/hooks/use-component.tsx +0 -91
  231. package/src/v2/hooks/use-configure-suggestions.tsx +0 -236
  232. package/src/v2/hooks/use-default-render-tool.tsx +0 -271
  233. package/src/v2/hooks/use-frontend-tool.tsx +0 -46
  234. package/src/v2/hooks/use-human-in-the-loop.tsx +0 -81
  235. package/src/v2/hooks/use-interrupt.tsx +0 -305
  236. package/src/v2/hooks/use-keyboard-height.tsx +0 -67
  237. package/src/v2/hooks/use-pin-to-send.ts +0 -94
  238. package/src/v2/hooks/use-render-activity-message.tsx +0 -72
  239. package/src/v2/hooks/use-render-custom-messages.tsx +0 -93
  240. package/src/v2/hooks/use-render-tool-call.tsx +0 -208
  241. package/src/v2/hooks/use-render-tool.tsx +0 -184
  242. package/src/v2/hooks/use-suggestions.tsx +0 -91
  243. package/src/v2/hooks/use-threads.tsx +0 -325
  244. package/src/v2/hooks/useKatexStyles.ts +0 -27
  245. package/src/v2/index.css +0 -1
  246. package/src/v2/index.ts +0 -27
  247. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +0 -495
  248. package/src/v2/lib/__tests__/processPartialHtml.test.ts +0 -112
  249. package/src/v2/lib/__tests__/renderSlot.test.tsx +0 -588
  250. package/src/v2/lib/__tests__/slots.test.ts +0 -56
  251. package/src/v2/lib/processPartialHtml.ts +0 -45
  252. package/src/v2/lib/react-core.ts +0 -156
  253. package/src/v2/lib/slots.tsx +0 -184
  254. package/src/v2/lib/transcription-client.ts +0 -184
  255. package/src/v2/lib/utils.ts +0 -8
  256. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +0 -196
  257. package/src/v2/providers/CopilotKitProvider.tsx +0 -800
  258. package/src/v2/providers/SandboxFunctionsContext.ts +0 -10
  259. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +0 -652
  260. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +0 -101
  261. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +0 -69
  262. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +0 -881
  263. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.tsx +0 -198
  264. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +0 -740
  265. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +0 -713
  266. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +0 -294
  267. package/src/v2/providers/index.ts +0 -21
  268. package/src/v2/styles/globals.css +0 -349
  269. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +0 -525
  270. package/src/v2/types/defineToolCallRenderer.ts +0 -68
  271. package/src/v2/types/frontend-tool.ts +0 -8
  272. package/src/v2/types/human-in-the-loop.ts +0 -33
  273. package/src/v2/types/index.ts +0 -8
  274. package/src/v2/types/interrupt.ts +0 -15
  275. package/src/v2/types/react-activity-message-renderer.ts +0 -27
  276. package/src/v2/types/react-custom-message-renderer.ts +0 -17
  277. package/src/v2/types/react-tool-call-renderer.ts +0 -35
  278. package/src/v2/types/sandbox-function.ts +0 -11
  279. package/tsconfig.json +0 -8
  280. package/tsdown.config.ts +0 -193
  281. package/typedoc.json +0 -4
  282. 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
- });