@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,740 +0,0 @@
1
- import { render } from "@testing-library/react";
2
- import React from "react";
3
- import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
- import { z } from "zod";
5
- import type { ReactFrontendTool } from "../../types/frontend-tool";
6
- import type { ReactToolCallRenderer } from "../../types";
7
- import {
8
- CopilotKitProvider,
9
- useCopilotKit,
10
- type CopilotKitContextValue,
11
- } from "../CopilotKitProvider";
12
- import { CopilotKitCoreReact } from "../../lib/react-core";
13
- import { useFrontendTool } from "../../hooks/use-frontend-tool";
14
-
15
- // Mock console methods to suppress expected warnings
16
- let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
17
- let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
18
-
19
- beforeEach(() => {
20
- consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
21
- consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
22
- });
23
-
24
- afterEach(() => {
25
- consoleErrorSpy.mockRestore();
26
- consoleWarnSpy.mockRestore();
27
- });
28
-
29
- describe("CopilotKitProvider stability", () => {
30
- describe("instance stability", () => {
31
- it("returns the same copilotkit instance after re-render with new renderToolCalls array", () => {
32
- const instances: CopilotKitCoreReact[] = [];
33
-
34
- function Collector({ children }: { children?: React.ReactNode }) {
35
- const { copilotkit } = useCopilotKit();
36
- instances.push(copilotkit);
37
- return <>{children}</>;
38
- }
39
-
40
- const renderToolCalls1: ReactToolCallRenderer<any>[] = [
41
- {
42
- name: "tool1",
43
- args: z.object({ a: z.string() }),
44
- render: () => <div>Tool 1</div>,
45
- },
46
- ];
47
-
48
- const renderToolCalls2: ReactToolCallRenderer<any>[] = [
49
- {
50
- name: "tool1",
51
- args: z.object({ a: z.string() }),
52
- render: () => <div>Tool 1 updated</div>,
53
- },
54
- ];
55
-
56
- const { rerender } = render(
57
- <CopilotKitProvider renderToolCalls={renderToolCalls1}>
58
- <Collector />
59
- </CopilotKitProvider>,
60
- );
61
-
62
- rerender(
63
- <CopilotKitProvider renderToolCalls={renderToolCalls2}>
64
- <Collector />
65
- </CopilotKitProvider>,
66
- );
67
-
68
- expect(instances.length).toBeGreaterThanOrEqual(2);
69
- const first = instances[0];
70
- for (const instance of instances) {
71
- expect(instance).toBe(first);
72
- }
73
- });
74
-
75
- it("returns the same copilotkit instance after re-render with new frontendTools array", () => {
76
- const instances: CopilotKitCoreReact[] = [];
77
-
78
- function Collector() {
79
- const { copilotkit } = useCopilotKit();
80
- instances.push(copilotkit);
81
- return null;
82
- }
83
-
84
- const tools1: ReactFrontendTool[] = [
85
- { name: "toolA", description: "Tool A", handler: vi.fn() },
86
- ];
87
- const tools2: ReactFrontendTool[] = [
88
- { name: "toolB", description: "Tool B", handler: vi.fn() },
89
- ];
90
-
91
- const { rerender } = render(
92
- <CopilotKitProvider frontendTools={tools1}>
93
- <Collector />
94
- </CopilotKitProvider>,
95
- );
96
-
97
- rerender(
98
- <CopilotKitProvider frontendTools={tools2}>
99
- <Collector />
100
- </CopilotKitProvider>,
101
- );
102
-
103
- expect(instances.length).toBeGreaterThanOrEqual(2);
104
- const first = instances[0];
105
- for (const instance of instances) {
106
- expect(instance).toBe(first);
107
- }
108
- });
109
- });
110
-
111
- describe("context value stability", () => {
112
- it("does not change context value reference when only tools change", () => {
113
- const contextValues: CopilotKitContextValue[] = [];
114
-
115
- function Collector() {
116
- const context = useCopilotKit();
117
- contextValues.push(context);
118
- return null;
119
- }
120
-
121
- const tools1: ReactFrontendTool[] = [
122
- { name: "toolA", description: "Tool A" },
123
- ];
124
- const tools2: ReactFrontendTool[] = [
125
- { name: "toolB", description: "Tool B" },
126
- ];
127
-
128
- const { rerender } = render(
129
- <CopilotKitProvider frontendTools={tools1}>
130
- <Collector />
131
- </CopilotKitProvider>,
132
- );
133
-
134
- const initialContext = contextValues[contextValues.length - 1];
135
-
136
- rerender(
137
- <CopilotKitProvider frontendTools={tools2}>
138
- <Collector />
139
- </CopilotKitProvider>,
140
- );
141
-
142
- const afterRerender = contextValues[contextValues.length - 1];
143
-
144
- expect(afterRerender?.copilotkit).toBe(initialContext?.copilotkit);
145
- expect(afterRerender?.executingToolCallIds).toBe(
146
- initialContext?.executingToolCallIds,
147
- );
148
- });
149
- });
150
-
151
- describe("setter calls on prop changes", () => {
152
- it("calls setTools when frontendTools change instead of recreating instance", () => {
153
- const setToolsSpy = vi.fn();
154
- let spyAttached = false;
155
-
156
- function SpyAttacher() {
157
- const { copilotkit } = useCopilotKit();
158
- if (!spyAttached) {
159
- const originalSetTools = copilotkit.setTools.bind(copilotkit);
160
- copilotkit.setTools = (tools) => {
161
- setToolsSpy(tools);
162
- return originalSetTools(tools);
163
- };
164
- spyAttached = true;
165
- }
166
- return null;
167
- }
168
-
169
- const tools1: ReactFrontendTool[] = [
170
- { name: "toolA", description: "Tool A", handler: vi.fn() },
171
- ];
172
- const tools2: ReactFrontendTool[] = [
173
- { name: "toolB", description: "Tool B", handler: vi.fn() },
174
- ];
175
-
176
- const { rerender } = render(
177
- <CopilotKitProvider frontendTools={tools1}>
178
- <SpyAttacher />
179
- </CopilotKitProvider>,
180
- );
181
-
182
- setToolsSpy.mockClear();
183
-
184
- rerender(
185
- <CopilotKitProvider frontendTools={tools2}>
186
- <SpyAttacher />
187
- </CopilotKitProvider>,
188
- );
189
-
190
- expect(setToolsSpy).toHaveBeenCalled();
191
- });
192
-
193
- it("calls setRenderToolCalls when renderToolCalls change", () => {
194
- const setRenderToolCallsSpy = vi.fn();
195
- let spyAttached = false;
196
-
197
- function SpyAttacher() {
198
- const { copilotkit } = useCopilotKit();
199
- if (!spyAttached) {
200
- const original = copilotkit.setRenderToolCalls.bind(copilotkit);
201
- copilotkit.setRenderToolCalls = (renderToolCalls) => {
202
- setRenderToolCallsSpy(renderToolCalls);
203
- return original(renderToolCalls);
204
- };
205
- spyAttached = true;
206
- }
207
- return null;
208
- }
209
-
210
- const rtc1: ReactToolCallRenderer<any>[] = [
211
- {
212
- name: "render1",
213
- args: z.object({ x: z.string() }),
214
- render: () => <div>R1</div>,
215
- },
216
- ];
217
- const rtc2: ReactToolCallRenderer<any>[] = [
218
- {
219
- name: "render2",
220
- args: z.object({ y: z.string() }),
221
- render: () => <div>R2</div>,
222
- },
223
- ];
224
-
225
- const { rerender } = render(
226
- <CopilotKitProvider renderToolCalls={rtc1}>
227
- <SpyAttacher />
228
- </CopilotKitProvider>,
229
- );
230
-
231
- setRenderToolCallsSpy.mockClear();
232
-
233
- rerender(
234
- <CopilotKitProvider renderToolCalls={rtc2}>
235
- <SpyAttacher />
236
- </CopilotKitProvider>,
237
- );
238
-
239
- expect(setRenderToolCallsSpy).toHaveBeenCalled();
240
- });
241
- });
242
-
243
- describe("no unnecessary re-renders from stable props", () => {
244
- it("does not re-render children when provider re-renders with same stable props", () => {
245
- let childRenderCount = 0;
246
-
247
- function Child() {
248
- childRenderCount++;
249
- useCopilotKit();
250
- return <div>child</div>;
251
- }
252
-
253
- const stableTools: ReactFrontendTool[] = [
254
- { name: "tool1", description: "Tool 1" },
255
- ];
256
-
257
- const { rerender } = render(
258
- <CopilotKitProvider frontendTools={stableTools}>
259
- <Child />
260
- </CopilotKitProvider>,
261
- );
262
-
263
- const initialCount = childRenderCount;
264
-
265
- rerender(
266
- <CopilotKitProvider frontendTools={stableTools}>
267
- <Child />
268
- </CopilotKitProvider>,
269
- );
270
-
271
- expect(childRenderCount - initialCount).toBeLessThanOrEqual(1);
272
- });
273
- });
274
-
275
- describe("setter effects skip initial mount (didMountRef guard)", () => {
276
- it("does not call setTools on initial mount (constructor handles it)", () => {
277
- const setToolsCalls: unknown[][] = [];
278
- let spyAttached = false;
279
-
280
- function SpyAttacher() {
281
- const { copilotkit } = useCopilotKit();
282
- if (!spyAttached) {
283
- const originalSetTools = copilotkit.setTools.bind(copilotkit);
284
- copilotkit.setTools = (tools) => {
285
- setToolsCalls.push([tools]);
286
- return originalSetTools(tools);
287
- };
288
- spyAttached = true;
289
- }
290
- return null;
291
- }
292
-
293
- const tools: ReactFrontendTool[] = [
294
- { name: "toolA", description: "Tool A", handler: vi.fn() },
295
- ];
296
-
297
- render(
298
- <CopilotKitProvider frontendTools={tools}>
299
- <SpyAttacher />
300
- </CopilotKitProvider>,
301
- );
302
-
303
- // setTools should NOT have been called on initial mount
304
- // because the constructor already sets the initial tools
305
- // and the didMountRef guard skips the first effect invocation.
306
- expect(setToolsCalls).toHaveLength(0);
307
- });
308
-
309
- it("does not call setRenderToolCalls on initial mount", () => {
310
- const calls: unknown[][] = [];
311
- let spyAttached = false;
312
-
313
- function SpyAttacher() {
314
- const { copilotkit } = useCopilotKit();
315
- if (!spyAttached) {
316
- const original = copilotkit.setRenderToolCalls.bind(copilotkit);
317
- copilotkit.setRenderToolCalls = (renderToolCalls) => {
318
- calls.push([renderToolCalls]);
319
- return original(renderToolCalls);
320
- };
321
- spyAttached = true;
322
- }
323
- return null;
324
- }
325
-
326
- const rtc: ReactToolCallRenderer<any>[] = [
327
- {
328
- name: "render1",
329
- args: z.object({ x: z.string() }),
330
- render: () => <div>R1</div>,
331
- },
332
- ];
333
-
334
- render(
335
- <CopilotKitProvider renderToolCalls={rtc}>
336
- <SpyAttacher />
337
- </CopilotKitProvider>,
338
- );
339
-
340
- // Provider setter effects are skipped on mount;
341
- // only the constructor sets the initial render tool calls.
342
- expect(calls).toHaveLength(0);
343
- });
344
- });
345
-
346
- describe("dynamic tool preservation on mount", () => {
347
- it("preserves dynamically registered tools from child hooks after provider mounts", () => {
348
- let capturedInstance: CopilotKitCoreReact | null = null;
349
-
350
- function DynamicToolChild() {
351
- const { copilotkit } = useCopilotKit();
352
- capturedInstance = copilotkit;
353
-
354
- // Register a tool dynamically via the hook
355
- useFrontendTool({
356
- name: "dynamicTool",
357
- description: "A dynamically registered tool",
358
- handler: async () => "result",
359
- });
360
-
361
- return null;
362
- }
363
-
364
- // Provider has its own tool via props
365
- const providerTools: ReactFrontendTool[] = [
366
- {
367
- name: "providerTool",
368
- description: "From provider props",
369
- handler: vi.fn(),
370
- },
371
- ];
372
-
373
- render(
374
- <CopilotKitProvider frontendTools={providerTools}>
375
- <DynamicToolChild />
376
- </CopilotKitProvider>,
377
- );
378
-
379
- // Both the provider tool (from constructor) and the dynamic tool
380
- // (from useFrontendTool hook) should exist on the instance.
381
- // If the provider's setter effects ran on mount and called setTools(),
382
- // the dynamic tool would be wiped out.
383
- expect(capturedInstance).not.toBeNull();
384
- const dynamicTool = capturedInstance!.getTool({
385
- toolName: "dynamicTool",
386
- });
387
- const providerTool = capturedInstance!.getTool({
388
- toolName: "providerTool",
389
- });
390
- expect(dynamicTool).toBeDefined();
391
- expect(providerTool).toBeDefined();
392
- });
393
-
394
- it("preserves dynamically registered render tool calls from child hooks after provider mounts", () => {
395
- let capturedInstance: CopilotKitCoreReact | null = null;
396
-
397
- function DynamicRenderChild() {
398
- const { copilotkit } = useCopilotKit();
399
- capturedInstance = copilotkit;
400
-
401
- useFrontendTool({
402
- name: "renderableTool",
403
- description: "Has a render function",
404
- parameters: z.object({ msg: z.string() }),
405
- handler: async () => "ok",
406
- render: () => <div>Rendered!</div>,
407
- });
408
-
409
- return null;
410
- }
411
-
412
- const providerRtc: ReactToolCallRenderer<any>[] = [
413
- {
414
- name: "providerRenderer",
415
- args: z.object({ x: z.string() }),
416
- render: () => <div>Provider Render</div>,
417
- },
418
- ];
419
-
420
- render(
421
- <CopilotKitProvider renderToolCalls={providerRtc}>
422
- <DynamicRenderChild />
423
- </CopilotKitProvider>,
424
- );
425
-
426
- expect(capturedInstance).not.toBeNull();
427
- const renderToolCalls = capturedInstance!.renderToolCalls;
428
-
429
- // Both the provider-level renderer and the hook-registered renderer
430
- // should exist. If setter effects ran on mount, only the provider
431
- // renderer would remain.
432
- const providerRenderer = renderToolCalls.find(
433
- (r) => r.name === "providerRenderer",
434
- );
435
- const hookRenderer = renderToolCalls.find(
436
- (r) => r.name === "renderableTool",
437
- );
438
- expect(providerRenderer).toBeDefined();
439
- expect(hookRenderer).toBeDefined();
440
- });
441
- });
442
-
443
- describe("React.StrictMode", () => {
444
- it("returns the same copilotkit instance in StrictMode", () => {
445
- const instances: CopilotKitCoreReact[] = [];
446
-
447
- function Collector() {
448
- const { copilotkit } = useCopilotKit();
449
- instances.push(copilotkit);
450
- return null;
451
- }
452
-
453
- render(
454
- <React.StrictMode>
455
- <CopilotKitProvider>
456
- <Collector />
457
- </CopilotKitProvider>
458
- </React.StrictMode>,
459
- );
460
-
461
- // StrictMode double-renders in dev, so we expect multiple captures
462
- expect(instances.length).toBeGreaterThanOrEqual(2);
463
- const first = instances[0];
464
- for (const instance of instances) {
465
- expect(instance).toBe(first);
466
- }
467
- });
468
-
469
- it("calls setTools at most once during StrictMode mount cycle", () => {
470
- const setToolsCalls: unknown[][] = [];
471
- let spyAttached = false;
472
-
473
- function SpyAttacher() {
474
- const { copilotkit } = useCopilotKit();
475
- if (!spyAttached) {
476
- const originalSetTools = copilotkit.setTools.bind(copilotkit);
477
- copilotkit.setTools = (tools) => {
478
- setToolsCalls.push([tools]);
479
- return originalSetTools(tools);
480
- };
481
- spyAttached = true;
482
- }
483
- return null;
484
- }
485
-
486
- const tools: ReactFrontendTool[] = [
487
- { name: "toolA", description: "Tool A", handler: vi.fn() },
488
- ];
489
-
490
- render(
491
- <React.StrictMode>
492
- <CopilotKitProvider frontendTools={tools}>
493
- <SpyAttacher />
494
- </CopilotKitProvider>
495
- </React.StrictMode>,
496
- );
497
-
498
- // StrictMode fires effects twice (mount → cleanup → remount).
499
- // The didMountRef guard skips the initial mount. After cleanup,
500
- // didMountRef.current stays true (refs persist), so the remount
501
- // call fires setTools once. This is expected and harmless — it
502
- // sets the same tools the constructor already established.
503
- // The critical invariant (dynamic tools not overwritten) is
504
- // verified by the separate "preserves dynamically registered
505
- // tools through StrictMode remount cycle" test.
506
- expect(setToolsCalls.length).toBeLessThanOrEqual(1);
507
- });
508
-
509
- it("preserves dynamically registered tools through StrictMode remount cycle", () => {
510
- let capturedInstance: CopilotKitCoreReact | null = null;
511
-
512
- function DynamicToolChild() {
513
- const { copilotkit } = useCopilotKit();
514
- capturedInstance = copilotkit;
515
-
516
- useFrontendTool({
517
- name: "strictModeTool",
518
- description: "Survives StrictMode remount",
519
- handler: async () => "ok",
520
- });
521
-
522
- return null;
523
- }
524
-
525
- render(
526
- <React.StrictMode>
527
- <CopilotKitProvider>
528
- <DynamicToolChild />
529
- </CopilotKitProvider>
530
- </React.StrictMode>,
531
- );
532
-
533
- expect(capturedInstance).not.toBeNull();
534
- const tool = capturedInstance!.getTool({ toolName: "strictModeTool" });
535
- expect(tool).toBeDefined();
536
- });
537
-
538
- it("preserves dynamically registered render tool calls through StrictMode remount cycle", () => {
539
- let capturedInstance: CopilotKitCoreReact | null = null;
540
-
541
- function DynamicRenderChild() {
542
- const { copilotkit } = useCopilotKit();
543
- capturedInstance = copilotkit;
544
-
545
- useFrontendTool({
546
- name: "strictModeRenderTool",
547
- description: "Has render, survives StrictMode remount",
548
- parameters: z.object({ topic: z.string() }),
549
- handler: async () => "ok",
550
- render: () => <div>Rendered!</div>,
551
- });
552
-
553
- return null;
554
- }
555
-
556
- render(
557
- <React.StrictMode>
558
- <CopilotKitProvider>
559
- <DynamicRenderChild />
560
- </CopilotKitProvider>
561
- </React.StrictMode>,
562
- );
563
-
564
- expect(capturedInstance).not.toBeNull();
565
- const renderToolCalls = capturedInstance!.renderToolCalls;
566
- const hookRenderer = renderToolCalls.find(
567
- (r) => r.name === "strictModeRenderTool",
568
- );
569
- expect(hookRenderer).toBeDefined();
570
- });
571
-
572
- it("hook render entries coexist with prop render entries through StrictMode remount", () => {
573
- let capturedInstance: CopilotKitCoreReact | null = null;
574
-
575
- function DynamicRenderChild() {
576
- const { copilotkit } = useCopilotKit();
577
- capturedInstance = copilotkit;
578
-
579
- useFrontendTool({
580
- name: "hookTool",
581
- description: "Registered via hook",
582
- parameters: z.object({ x: z.string() }),
583
- handler: async () => "ok",
584
- render: () => <div>Hook render</div>,
585
- });
586
-
587
- return null;
588
- }
589
-
590
- const propRtc: ReactToolCallRenderer<any>[] = [
591
- {
592
- name: "propTool",
593
- args: z.object({ y: z.string() }),
594
- render: () => <div>Prop render</div>,
595
- },
596
- ];
597
-
598
- render(
599
- <React.StrictMode>
600
- <CopilotKitProvider renderToolCalls={propRtc}>
601
- <DynamicRenderChild />
602
- </CopilotKitProvider>
603
- </React.StrictMode>,
604
- );
605
-
606
- expect(capturedInstance).not.toBeNull();
607
- const renderToolCalls = capturedInstance!.renderToolCalls;
608
- expect(renderToolCalls.find((r) => r.name === "propTool")).toBeDefined();
609
- expect(renderToolCalls.find((r) => r.name === "hookTool")).toBeDefined();
610
- });
611
-
612
- it("context value is stable through StrictMode remount", () => {
613
- const contextValues: CopilotKitContextValue[] = [];
614
-
615
- function Collector() {
616
- const context = useCopilotKit();
617
- contextValues.push(context);
618
- return null;
619
- }
620
-
621
- render(
622
- <React.StrictMode>
623
- <CopilotKitProvider>
624
- <Collector />
625
- </CopilotKitProvider>
626
- </React.StrictMode>,
627
- );
628
-
629
- expect(contextValues.length).toBeGreaterThanOrEqual(2);
630
- const first = contextValues[0]!;
631
- for (const ctx of contextValues) {
632
- expect(ctx.copilotkit).toBe(first.copilotkit);
633
- expect(ctx.executingToolCallIds).toBe(first.executingToolCallIds);
634
- }
635
- });
636
- });
637
-
638
- describe("hook render entries survive prop changes", () => {
639
- it("preserves hook-registered render entries when provider renderToolCalls prop changes", () => {
640
- let capturedInstance: CopilotKitCoreReact | null = null;
641
-
642
- function DynamicRenderChild() {
643
- const { copilotkit } = useCopilotKit();
644
- capturedInstance = copilotkit;
645
-
646
- useFrontendTool({
647
- name: "hookTool",
648
- description: "Registered via hook",
649
- parameters: z.object({ x: z.string() }),
650
- handler: async () => "ok",
651
- render: () => <div>Hook render</div>,
652
- });
653
-
654
- return null;
655
- }
656
-
657
- const rtc1: ReactToolCallRenderer<any>[] = [
658
- {
659
- name: "propToolA",
660
- args: z.object({ a: z.string() }),
661
- render: () => <div>A</div>,
662
- },
663
- ];
664
- const rtc2: ReactToolCallRenderer<any>[] = [
665
- {
666
- name: "propToolB",
667
- args: z.object({ b: z.string() }),
668
- render: () => <div>B</div>,
669
- },
670
- ];
671
-
672
- const { rerender } = render(
673
- <CopilotKitProvider renderToolCalls={rtc1}>
674
- <DynamicRenderChild />
675
- </CopilotKitProvider>,
676
- );
677
-
678
- // Rerender with new prop render tool calls
679
- rerender(
680
- <CopilotKitProvider renderToolCalls={rtc2}>
681
- <DynamicRenderChild />
682
- </CopilotKitProvider>,
683
- );
684
-
685
- expect(capturedInstance).not.toBeNull();
686
- const renderToolCalls = capturedInstance!.renderToolCalls;
687
- // propToolA should be gone (replaced by propToolB)
688
- expect(
689
- renderToolCalls.find((r) => r.name === "propToolA"),
690
- ).toBeUndefined();
691
- // propToolB should exist (from new props)
692
- expect(renderToolCalls.find((r) => r.name === "propToolB")).toBeDefined();
693
- // hookTool should survive the prop change
694
- expect(renderToolCalls.find((r) => r.name === "hookTool")).toBeDefined();
695
- });
696
- });
697
-
698
- describe("runtimeUrl deduplication", () => {
699
- it("always calls setRuntimeUrl with the same URL on re-render (AgentRegistry deduplicates)", () => {
700
- const setRuntimeUrlCalls: unknown[] = [];
701
- let spyAttached = false;
702
-
703
- function SpyAttacher() {
704
- const { copilotkit } = useCopilotKit();
705
- if (!spyAttached) {
706
- const original = copilotkit.setRuntimeUrl.bind(copilotkit);
707
- copilotkit.setRuntimeUrl = (...args: [string | undefined]) => {
708
- setRuntimeUrlCalls.push(args[0]);
709
- return original(...args);
710
- };
711
- spyAttached = true;
712
- }
713
- return null;
714
- }
715
-
716
- const { rerender } = render(
717
- <CopilotKitProvider runtimeUrl="http://localhost:3000/api">
718
- <SpyAttacher />
719
- </CopilotKitProvider>,
720
- );
721
-
722
- // Re-render with the SAME runtimeUrl
723
- rerender(
724
- <CopilotKitProvider runtimeUrl="http://localhost:3000/api">
725
- <SpyAttacher />
726
- </CopilotKitProvider>,
727
- );
728
-
729
- // The config effect may re-fire if other deps (mergedHeaders, etc.)
730
- // change reference on rerender. The actual deduplication of /info
731
- // fetches happens inside AgentRegistry.setRuntimeUrl(), which has
732
- // a guard: `if (this._runtimeUrl === normalizedRuntimeUrl) return`.
733
- // Here we verify every call receives the same URL.
734
- expect(setRuntimeUrlCalls.length).toBeGreaterThanOrEqual(1);
735
- for (const url of setRuntimeUrlCalls) {
736
- expect(url).toBe("http://localhost:3000/api");
737
- }
738
- });
739
- });
740
- });