@copilotkit/react-core 1.57.2 → 1.57.4

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