@copilotkit/react-core 1.54.1-next.6 → 1.55.0-next.7

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 (183) hide show
  1. package/CHANGELOG.md +121 -106
  2. package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
  3. package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
  4. package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
  5. package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
  6. package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
  7. package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
  8. package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
  9. package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
  10. package/dist/index.cjs +27 -28
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +3 -3
  13. package/dist/index.d.cts.map +1 -1
  14. package/dist/index.d.mts +3 -3
  15. package/dist/index.d.mts.map +1 -1
  16. package/dist/index.mjs +4 -5
  17. package/dist/index.mjs.map +1 -1
  18. package/dist/index.umd.js +1941 -35
  19. package/dist/index.umd.js.map +1 -1
  20. package/dist/v2/index.cjs +77 -7
  21. package/dist/v2/index.css +1 -2
  22. package/dist/v2/index.d.cts +6 -4
  23. package/dist/v2/index.d.mts +6 -4
  24. package/dist/v2/index.mjs +7 -4
  25. package/dist/v2/index.umd.js +5725 -24
  26. package/dist/v2/index.umd.js.map +1 -1
  27. package/package.json +35 -7
  28. package/scripts/scope-preflight.mjs +101 -0
  29. package/src/components/CopilotListeners.tsx +2 -6
  30. package/src/components/copilot-provider/copilot-messages.tsx +1 -1
  31. package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
  32. package/src/components/copilot-provider/copilotkit.tsx +4 -4
  33. package/src/context/copilot-messages-context.tsx +1 -1
  34. package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
  35. package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
  36. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
  37. package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
  38. package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
  39. package/src/hooks/use-agent-nodename.ts +1 -1
  40. package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
  41. package/src/hooks/use-coagent.ts +1 -1
  42. package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
  43. package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
  44. package/src/hooks/use-copilot-chat_internal.ts +2 -2
  45. package/src/hooks/use-copilot-readable.ts +1 -1
  46. package/src/hooks/use-frontend-tool.ts +2 -2
  47. package/src/hooks/use-human-in-the-loop.ts +2 -2
  48. package/src/hooks/use-langgraph-interrupt.ts +2 -5
  49. package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
  50. package/src/hooks/use-render-tool-call.ts +1 -1
  51. package/src/lib/copilot-task.ts +1 -1
  52. package/src/setupTests.ts +18 -14
  53. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
  54. package/src/v2/__tests__/globalSetup.ts +14 -0
  55. package/src/v2/__tests__/setup.ts +93 -0
  56. package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
  57. package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
  58. package/src/v2/components/CopilotKitInspector.tsx +50 -0
  59. package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
  60. package/src/v2/components/WildcardToolCallRender.tsx +86 -0
  61. package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
  62. package/src/v2/components/chat/CopilotChat.tsx +431 -0
  63. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
  64. package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
  65. package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
  66. package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
  67. package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
  68. package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
  69. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
  70. package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
  71. package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
  72. package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
  73. package/src/v2/components/chat/CopilotChatView.tsx +598 -0
  74. package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
  75. package/src/v2/components/chat/CopilotPopup.tsx +81 -0
  76. package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
  77. package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
  78. package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
  79. package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
  80. package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
  81. package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
  82. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
  83. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
  84. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
  85. package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
  86. package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
  87. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
  88. package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
  89. package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
  90. package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
  91. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
  92. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
  93. package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
  94. package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
  95. package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
  96. package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
  97. package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
  98. package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
  99. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
  100. package/src/v2/components/chat/__tests__/setup.ts +1 -0
  101. package/src/v2/components/chat/index.ts +79 -0
  102. package/src/v2/components/index.ts +7 -0
  103. package/src/v2/components/license-warning-banner.tsx +198 -0
  104. package/src/v2/components/ui/button.tsx +123 -0
  105. package/src/v2/components/ui/dropdown-menu.tsx +258 -0
  106. package/src/v2/components/ui/tooltip.tsx +60 -0
  107. package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
  108. package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
  109. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
  110. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
  111. package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
  112. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
  113. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
  114. package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
  115. package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
  116. package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
  117. package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
  118. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
  119. package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
  120. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
  121. package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
  122. package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
  123. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
  124. package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
  125. package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
  126. package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
  127. package/src/v2/hooks/index.ts +18 -0
  128. package/src/v2/hooks/use-agent-context.tsx +45 -0
  129. package/src/v2/hooks/use-agent.tsx +155 -0
  130. package/src/v2/hooks/use-component.tsx +89 -0
  131. package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
  132. package/src/v2/hooks/use-default-render-tool.tsx +254 -0
  133. package/src/v2/hooks/use-frontend-tool.tsx +43 -0
  134. package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
  135. package/src/v2/hooks/use-interrupt.tsx +305 -0
  136. package/src/v2/hooks/use-keyboard-height.tsx +67 -0
  137. package/src/v2/hooks/use-render-activity-message.tsx +73 -0
  138. package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
  139. package/src/v2/hooks/use-render-tool-call.tsx +175 -0
  140. package/src/v2/hooks/use-render-tool.tsx +181 -0
  141. package/src/v2/hooks/use-suggestions.tsx +91 -0
  142. package/src/v2/hooks/use-threads.tsx +256 -0
  143. package/src/v2/hooks/useKatexStyles.ts +27 -0
  144. package/src/v2/index.css +1 -1
  145. package/src/v2/index.ts +18 -2
  146. package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
  147. package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
  148. package/src/v2/lib/react-core.ts +156 -0
  149. package/src/v2/lib/slots.tsx +143 -0
  150. package/src/v2/lib/transcription-client.ts +184 -0
  151. package/src/v2/lib/utils.ts +8 -0
  152. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
  153. package/src/v2/providers/CopilotKitProvider.tsx +600 -0
  154. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
  155. package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
  156. package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
  157. package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
  158. package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
  159. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
  160. package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
  161. package/src/v2/providers/index.ts +14 -0
  162. package/src/v2/styles/globals.css +230 -0
  163. package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
  164. package/src/v2/types/defineToolCallRenderer.ts +65 -0
  165. package/src/v2/types/frontend-tool.ts +8 -0
  166. package/src/v2/types/human-in-the-loop.ts +33 -0
  167. package/src/v2/types/index.ts +7 -0
  168. package/src/v2/types/interrupt.ts +15 -0
  169. package/src/v2/types/react-activity-message-renderer.ts +27 -0
  170. package/src/v2/types/react-custom-message-renderer.ts +17 -0
  171. package/src/v2/types/react-tool-call-renderer.ts +32 -0
  172. package/tsdown.config.ts +34 -10
  173. package/vitest.config.mjs +4 -3
  174. package/LICENSE +0 -21
  175. package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
  176. package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
  177. package/dist/copilotkit-C94ayZbs.cjs +0 -2161
  178. package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
  179. package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
  180. package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
  181. package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
  182. package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
  183. package/dist/v2/index.css.map +0 -1
@@ -0,0 +1,642 @@
1
+ import { render, renderHook, waitFor } from "@testing-library/react";
2
+ import type 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 { ReactHumanInTheLoop } from "../../types/human-in-the-loop";
7
+ import { CopilotKitProvider, useCopilotKit } from "../CopilotKitProvider";
8
+
9
+ // Mock console methods
10
+ const originalConsoleError = console.error;
11
+ const originalConsoleWarn = console.warn;
12
+
13
+ describe("CopilotKitProvider", () => {
14
+ let consoleErrorSpy: ReturnType<typeof vi.spyOn>;
15
+ let consoleWarnSpy: ReturnType<typeof vi.spyOn>;
16
+
17
+ beforeEach(() => {
18
+ consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
19
+ consoleWarnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
20
+ });
21
+
22
+ afterEach(() => {
23
+ consoleErrorSpy.mockRestore();
24
+ consoleWarnSpy.mockRestore();
25
+ });
26
+
27
+ describe("Basic functionality", () => {
28
+ it("provides context to children", () => {
29
+ const { result } = renderHook(() => useCopilotKit(), {
30
+ wrapper: ({ children }) => (
31
+ <CopilotKitProvider>{children}</CopilotKitProvider>
32
+ ),
33
+ });
34
+
35
+ expect(result.current).toBeDefined();
36
+ expect(result.current.copilotkit).toBeDefined();
37
+ });
38
+
39
+ it("throws error when used outside provider", () => {
40
+ // Temporarily restore console.error for this test
41
+ consoleErrorSpy.mockRestore();
42
+ const errorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
43
+
44
+ expect(() => {
45
+ renderHook(() => useCopilotKit());
46
+ }).toThrow();
47
+
48
+ errorSpy.mockRestore();
49
+ consoleErrorSpy = vi.spyOn(console, "error").mockImplementation(() => {});
50
+ });
51
+ });
52
+
53
+ describe("frontendTools prop", () => {
54
+ it("registers frontend tools with CopilotKitCore", () => {
55
+ const mockHandler = vi.fn();
56
+ const frontendTools: ReactFrontendTool[] = [
57
+ {
58
+ name: "testTool",
59
+ description: "A test tool",
60
+ parameters: z.object({
61
+ input: z.string(),
62
+ }),
63
+ handler: mockHandler,
64
+ },
65
+ ];
66
+
67
+ const { result } = renderHook(() => useCopilotKit(), {
68
+ wrapper: ({ children }) => (
69
+ <CopilotKitProvider frontendTools={frontendTools}>
70
+ {children}
71
+ </CopilotKitProvider>
72
+ ),
73
+ });
74
+
75
+ const tool = result.current.copilotkit.getTool({ toolName: "testTool" });
76
+ expect(tool).toBeDefined();
77
+ expect(tool?.name).toBe("testTool");
78
+ expect(tool?.handler).toBe(mockHandler);
79
+ });
80
+
81
+ it("includes render components from frontend tools", () => {
82
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
83
+ const frontendTools: ReactFrontendTool[] = [
84
+ {
85
+ name: "renderTool",
86
+ description: "A tool with render",
87
+ parameters: z.object({
88
+ input: z.string(),
89
+ }),
90
+ render: TestComponent,
91
+ },
92
+ ];
93
+
94
+ const { result } = renderHook(() => useCopilotKit(), {
95
+ wrapper: ({ children }) => (
96
+ <CopilotKitProvider frontendTools={frontendTools}>
97
+ {children}
98
+ </CopilotKitProvider>
99
+ ),
100
+ });
101
+
102
+ const renderTool = result.current.copilotkit.renderToolCalls.find(
103
+ (rc) => rc.name === "renderTool",
104
+ );
105
+ expect(renderTool).toBeDefined();
106
+ expect(renderTool?.render).toBe(TestComponent);
107
+ });
108
+
109
+ it("warns when frontendTools prop changes", async () => {
110
+ const initialTools: ReactFrontendTool[] = [
111
+ {
112
+ name: "tool1",
113
+ description: "Tool 1",
114
+ },
115
+ ];
116
+
117
+ const { rerender } = render(
118
+ <CopilotKitProvider frontendTools={initialTools}>
119
+ <div>Test</div>
120
+ </CopilotKitProvider>,
121
+ );
122
+
123
+ const newTools: ReactFrontendTool[] = [
124
+ {
125
+ name: "tool2",
126
+ description: "Tool 2",
127
+ },
128
+ ];
129
+
130
+ rerender(
131
+ <CopilotKitProvider frontendTools={newTools}>
132
+ <div>Test</div>
133
+ </CopilotKitProvider>,
134
+ );
135
+
136
+ await waitFor(() => {
137
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
138
+ expect.stringContaining("frontendTools must be a stable array"),
139
+ );
140
+ });
141
+ });
142
+ });
143
+
144
+ describe("humanInTheLoop prop", () => {
145
+ it("processes humanInTheLoop tools and creates handlers", () => {
146
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
147
+ const humanInTheLoopTools: ReactHumanInTheLoop[] = [
148
+ {
149
+ name: "approvalTool",
150
+ description: "Requires human approval",
151
+ parameters: z.object({
152
+ question: z.string(),
153
+ }),
154
+ render: TestComponent,
155
+ },
156
+ ];
157
+
158
+ const { result } = renderHook(() => useCopilotKit(), {
159
+ wrapper: ({ children }) => (
160
+ <CopilotKitProvider humanInTheLoop={humanInTheLoopTools}>
161
+ {children}
162
+ </CopilotKitProvider>
163
+ ),
164
+ });
165
+
166
+ // Check that the tool is registered
167
+ const tool = result.current.copilotkit.getTool({
168
+ toolName: "approvalTool",
169
+ });
170
+ expect(tool).toBeDefined();
171
+ expect(tool?.name).toBe("approvalTool");
172
+ expect(tool?.handler).toBeDefined();
173
+
174
+ // Check that render component is registered
175
+ const approvalTool = result.current.copilotkit.renderToolCalls.find(
176
+ (rc) => rc.name === "approvalTool",
177
+ );
178
+ expect(approvalTool).toBeDefined();
179
+ expect(approvalTool?.render).toBe(TestComponent);
180
+ });
181
+
182
+ it("creates placeholder handlers for humanInTheLoop tools", async () => {
183
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
184
+ const humanInTheLoopTools: ReactHumanInTheLoop[] = [
185
+ {
186
+ name: "interactiveTool",
187
+ description: "Interactive tool",
188
+ parameters: z.object({
189
+ data: z.string(),
190
+ }),
191
+ render: TestComponent,
192
+ },
193
+ ];
194
+
195
+ const { result } = renderHook(() => useCopilotKit(), {
196
+ wrapper: ({ children }) => (
197
+ <CopilotKitProvider humanInTheLoop={humanInTheLoopTools}>
198
+ {children}
199
+ </CopilotKitProvider>
200
+ ),
201
+ });
202
+
203
+ const handler = result.current.copilotkit.getTool({
204
+ toolName: "interactiveTool",
205
+ })?.handler;
206
+ expect(handler).toBeDefined();
207
+
208
+ // Call the handler and check for warning
209
+ const handlerPromise = handler!({ data: "test" }, {} as any);
210
+
211
+ await waitFor(() => {
212
+ expect(consoleWarnSpy).toHaveBeenCalledWith(
213
+ expect.stringContaining(
214
+ "Human-in-the-loop tool 'interactiveTool' called",
215
+ ),
216
+ );
217
+ });
218
+
219
+ const result2 = await handlerPromise;
220
+ expect(result2).toBeUndefined();
221
+ });
222
+
223
+ it("warns when humanInTheLoop prop changes", async () => {
224
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
225
+ const initialTools: ReactHumanInTheLoop[] = [
226
+ {
227
+ name: "tool1",
228
+ description: "Tool 1",
229
+ render: TestComponent,
230
+ },
231
+ ];
232
+
233
+ const { rerender } = render(
234
+ <CopilotKitProvider humanInTheLoop={initialTools}>
235
+ <div>Test</div>
236
+ </CopilotKitProvider>,
237
+ );
238
+
239
+ const newTools: ReactHumanInTheLoop[] = [
240
+ {
241
+ name: "tool2",
242
+ description: "Tool 2",
243
+ render: TestComponent,
244
+ },
245
+ ];
246
+
247
+ rerender(
248
+ <CopilotKitProvider humanInTheLoop={newTools}>
249
+ <div>Test</div>
250
+ </CopilotKitProvider>,
251
+ );
252
+
253
+ await waitFor(() => {
254
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
255
+ expect.stringContaining("humanInTheLoop must be a stable array"),
256
+ );
257
+ });
258
+ });
259
+ });
260
+
261
+ describe("Combined tools functionality", () => {
262
+ it("registers both frontendTools and humanInTheLoop tools", () => {
263
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
264
+ const frontendTools: ReactFrontendTool[] = [
265
+ {
266
+ name: "frontendTool",
267
+ description: "Frontend tool",
268
+ handler: vi.fn(),
269
+ },
270
+ ];
271
+ const humanInTheLoopTools: ReactHumanInTheLoop[] = [
272
+ {
273
+ name: "humanTool",
274
+ description: "Human tool",
275
+ render: TestComponent,
276
+ },
277
+ ];
278
+
279
+ const { result } = renderHook(() => useCopilotKit(), {
280
+ wrapper: ({ children }) => (
281
+ <CopilotKitProvider
282
+ frontendTools={frontendTools}
283
+ humanInTheLoop={humanInTheLoopTools}
284
+ >
285
+ {children}
286
+ </CopilotKitProvider>
287
+ ),
288
+ });
289
+
290
+ expect(
291
+ result.current.copilotkit.getTool({ toolName: "frontendTool" }),
292
+ ).toBeDefined();
293
+ expect(
294
+ result.current.copilotkit.getTool({ toolName: "humanTool" }),
295
+ ).toBeDefined();
296
+ });
297
+
298
+ it("should handle agentId in frontend tools", () => {
299
+ const handler1 = vi.fn();
300
+ const handler2 = vi.fn();
301
+
302
+ const frontendTools: ReactFrontendTool[] = [
303
+ {
304
+ name: "globalTool",
305
+ description: "Global tool",
306
+ handler: handler1,
307
+ },
308
+ {
309
+ name: "agentSpecificTool",
310
+ description: "Agent specific tool",
311
+ handler: handler2,
312
+ agentId: "specificAgent",
313
+ },
314
+ ];
315
+
316
+ const { result } = renderHook(() => useCopilotKit(), {
317
+ wrapper: ({ children }) => (
318
+ <CopilotKitProvider frontendTools={frontendTools}>
319
+ {children}
320
+ </CopilotKitProvider>
321
+ ),
322
+ });
323
+
324
+ const globalTool = result.current.copilotkit.getTool({
325
+ toolName: "globalTool",
326
+ });
327
+ expect(globalTool).toBeDefined();
328
+ expect(globalTool?.agentId).toBeUndefined();
329
+ const agentTool = result.current.copilotkit.getTool({
330
+ toolName: "agentSpecificTool",
331
+ agentId: "specificAgent",
332
+ });
333
+ expect(agentTool).toBeDefined();
334
+ expect(agentTool?.agentId).toBe("specificAgent");
335
+ });
336
+
337
+ it("combines render components from all sources", () => {
338
+ const TestComponent1: React.FC<any> = () => <div>Test1</div>;
339
+ const TestComponent2: React.FC<any> = () => <div>Test2</div>;
340
+ const TestComponent3: React.FC<any> = () => <div>Test3</div>;
341
+
342
+ const frontendTools: ReactFrontendTool[] = [
343
+ {
344
+ name: "frontendRenderTool",
345
+ description: "Frontend render tool",
346
+ parameters: z.object({ a: z.string() }),
347
+ render: TestComponent1,
348
+ },
349
+ ];
350
+
351
+ const humanInTheLoopTools: ReactHumanInTheLoop[] = [
352
+ {
353
+ name: "humanRenderTool",
354
+ description: "Human render tool",
355
+ parameters: z.object({ b: z.string() }),
356
+ render: TestComponent2,
357
+ },
358
+ ];
359
+
360
+ const renderToolCalls = [
361
+ {
362
+ name: "directRenderTool",
363
+ args: z.object({ c: z.string() }),
364
+ render: TestComponent3,
365
+ },
366
+ ];
367
+
368
+ const { result } = renderHook(() => useCopilotKit(), {
369
+ wrapper: ({ children }) => (
370
+ <CopilotKitProvider
371
+ frontendTools={frontendTools}
372
+ humanInTheLoop={humanInTheLoopTools}
373
+ renderToolCalls={renderToolCalls}
374
+ >
375
+ {children}
376
+ </CopilotKitProvider>
377
+ ),
378
+ });
379
+
380
+ const frontendRenderTool = result.current.copilotkit.renderToolCalls.find(
381
+ (rc) => rc.name === "frontendRenderTool",
382
+ );
383
+ const humanRenderTool = result.current.copilotkit.renderToolCalls.find(
384
+ (rc) => rc.name === "humanRenderTool",
385
+ );
386
+ const directRenderTool = result.current.copilotkit.renderToolCalls.find(
387
+ (rc) => rc.name === "directRenderTool",
388
+ );
389
+
390
+ expect(frontendRenderTool).toBeDefined();
391
+ expect(humanRenderTool).toBeDefined();
392
+ expect(directRenderTool).toBeDefined();
393
+
394
+ expect(frontendRenderTool?.render).toBe(TestComponent1);
395
+ expect(humanRenderTool?.render).toBe(TestComponent2);
396
+ expect(directRenderTool?.render).toBe(TestComponent3);
397
+ });
398
+ });
399
+
400
+ describe("renderToolCalls management", () => {
401
+ it("includes render tools from frontendTools prop", async () => {
402
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
403
+ const frontendTools: ReactFrontendTool[] = [
404
+ {
405
+ name: "tool1",
406
+ description: "Tool 1",
407
+ parameters: z.object({ a: z.string() }),
408
+ render: TestComponent,
409
+ },
410
+ ];
411
+
412
+ const { result } = renderHook(() => useCopilotKit(), {
413
+ wrapper: ({ children }) => (
414
+ <CopilotKitProvider frontendTools={frontendTools}>
415
+ {children}
416
+ </CopilotKitProvider>
417
+ ),
418
+ });
419
+
420
+ const tool1 = result.current.copilotkit.renderToolCalls.find(
421
+ (rc) => rc.name === "tool1",
422
+ );
423
+ expect(tool1).toBeDefined();
424
+ expect(tool1?.render).toBe(TestComponent);
425
+ });
426
+ });
427
+
428
+ describe("a2ui prop", () => {
429
+ const originalFetch = global.fetch;
430
+ const originalWindow = (globalThis as { window?: unknown }).window;
431
+
432
+ beforeEach(() => {
433
+ (globalThis as { window?: unknown }).window = {};
434
+ });
435
+
436
+ afterEach(() => {
437
+ global.fetch = originalFetch;
438
+ if (originalWindow === undefined) {
439
+ delete (globalThis as { window?: unknown }).window;
440
+ } else {
441
+ (globalThis as { window?: unknown }).window = originalWindow;
442
+ }
443
+ });
444
+
445
+ it("does not register an a2ui-surface renderer by default", () => {
446
+ const { result } = renderHook(() => useCopilotKit(), {
447
+ wrapper: ({ children }) => (
448
+ <CopilotKitProvider>{children}</CopilotKitProvider>
449
+ ),
450
+ });
451
+
452
+ const a2uiRenderer =
453
+ result.current.copilotkit.renderActivityMessages.find(
454
+ (r) => r.activityType === "a2ui-surface",
455
+ );
456
+ expect(a2uiRenderer).toBeUndefined();
457
+ });
458
+
459
+ it("does not register an a2ui-surface renderer when a2ui.theme is provided but runtime has not signaled a2uiEnabled", () => {
460
+ const customTheme = { components: {}, elements: {}, markdown: {} } as any;
461
+
462
+ const { result } = renderHook(() => useCopilotKit(), {
463
+ wrapper: ({ children }) => (
464
+ <CopilotKitProvider a2ui={{ theme: customTheme }}>
465
+ {children}
466
+ </CopilotKitProvider>
467
+ ),
468
+ });
469
+
470
+ const a2uiRenderer =
471
+ result.current.copilotkit.renderActivityMessages.find(
472
+ (r) => r.activityType === "a2ui-surface",
473
+ );
474
+ expect(a2uiRenderer).toBeUndefined();
475
+ });
476
+
477
+ it("registers an a2ui-surface renderer when runtime reports a2uiEnabled: true", async () => {
478
+ global.fetch = vi.fn().mockResolvedValue({
479
+ ok: true,
480
+ json: async () => ({
481
+ version: "1.0.0",
482
+ agents: {},
483
+ audioFileTranscriptionEnabled: false,
484
+ a2uiEnabled: true,
485
+ }),
486
+ });
487
+
488
+ const { result } = renderHook(() => useCopilotKit(), {
489
+ wrapper: ({ children }) => (
490
+ <CopilotKitProvider runtimeUrl="http://localhost:3000/api">
491
+ {children}
492
+ </CopilotKitProvider>
493
+ ),
494
+ });
495
+
496
+ await vi.waitFor(() => {
497
+ const a2uiRenderer =
498
+ result.current.copilotkit.renderActivityMessages.find(
499
+ (r) => r.activityType === "a2ui-surface",
500
+ );
501
+ expect(a2uiRenderer).toBeDefined();
502
+ });
503
+ });
504
+
505
+ it("does not register an a2ui-surface renderer when runtime reports a2uiEnabled: false", async () => {
506
+ global.fetch = vi.fn().mockResolvedValue({
507
+ ok: true,
508
+ json: async () => ({
509
+ version: "1.0.0",
510
+ agents: {},
511
+ audioFileTranscriptionEnabled: false,
512
+ a2uiEnabled: false,
513
+ }),
514
+ });
515
+
516
+ const { result } = renderHook(() => useCopilotKit(), {
517
+ wrapper: ({ children }) => (
518
+ <CopilotKitProvider runtimeUrl="http://localhost:3000/api">
519
+ {children}
520
+ </CopilotKitProvider>
521
+ ),
522
+ });
523
+
524
+ // Let the connection settle
525
+ await vi.waitFor(() => {
526
+ expect(global.fetch).toHaveBeenCalled();
527
+ });
528
+
529
+ const a2uiRenderer =
530
+ result.current.copilotkit.renderActivityMessages.find(
531
+ (r) => r.activityType === "a2ui-surface",
532
+ );
533
+ expect(a2uiRenderer).toBeUndefined();
534
+ });
535
+
536
+ it("user-provided renderActivityMessages with activityType a2ui-surface takes precedence over built-in", async () => {
537
+ global.fetch = vi.fn().mockResolvedValue({
538
+ ok: true,
539
+ json: async () => ({
540
+ version: "1.0.0",
541
+ agents: {},
542
+ audioFileTranscriptionEnabled: false,
543
+ a2uiEnabled: true,
544
+ }),
545
+ });
546
+
547
+ const userRenderer = {
548
+ activityType: "a2ui-surface",
549
+ content: {} as any,
550
+ render: () => null,
551
+ };
552
+
553
+ const { result } = renderHook(() => useCopilotKit(), {
554
+ wrapper: ({ children }) => (
555
+ <CopilotKitProvider
556
+ runtimeUrl="http://localhost:3000/api"
557
+ renderActivityMessages={[userRenderer]}
558
+ >
559
+ {children}
560
+ </CopilotKitProvider>
561
+ ),
562
+ });
563
+
564
+ await vi.waitFor(() => {
565
+ const renderers =
566
+ result.current.copilotkit.renderActivityMessages.filter(
567
+ (r) => r.activityType === "a2ui-surface",
568
+ );
569
+ // Both present; user-provided comes first (index 0)
570
+ expect(renderers.length).toBeGreaterThanOrEqual(1);
571
+ expect(renderers[0].render).toBe(userRenderer.render);
572
+ });
573
+ });
574
+ });
575
+
576
+ describe("Edge cases", () => {
577
+ it("handles empty arrays for tools", () => {
578
+ const { result } = renderHook(() => useCopilotKit(), {
579
+ wrapper: ({ children }) => (
580
+ <CopilotKitProvider frontendTools={[]} humanInTheLoop={[]}>
581
+ {children}
582
+ </CopilotKitProvider>
583
+ ),
584
+ });
585
+
586
+ expect(result.current.copilotkit.tools).toHaveLength(0);
587
+ expect(result.current.copilotkit.renderToolCalls).toHaveLength(0);
588
+ });
589
+
590
+ it("handles tools without render components", () => {
591
+ const frontendTools: ReactFrontendTool[] = [
592
+ {
593
+ name: "noRenderTool",
594
+ description: "Tool without render",
595
+ handler: vi.fn(),
596
+ },
597
+ ];
598
+
599
+ const { result } = renderHook(() => useCopilotKit(), {
600
+ wrapper: ({ children }) => (
601
+ <CopilotKitProvider frontendTools={frontendTools}>
602
+ {children}
603
+ </CopilotKitProvider>
604
+ ),
605
+ });
606
+
607
+ expect(
608
+ result.current.copilotkit.getTool({ toolName: "noRenderTool" }),
609
+ ).toBeDefined();
610
+ const noRenderTool = result.current.copilotkit.renderToolCalls.find(
611
+ (rc) => rc.name === "noRenderTool",
612
+ );
613
+ expect(noRenderTool).toBeUndefined();
614
+ });
615
+
616
+ it("handles humanInTheLoop tools with followUp flag", () => {
617
+ const TestComponent: React.FC<any> = () => <div>Test</div>;
618
+ const humanInTheLoopTools: ReactHumanInTheLoop[] = [
619
+ {
620
+ name: "followUpTool",
621
+ description: "Tool with followUp",
622
+ parameters: z.object({ a: z.string() }),
623
+ followUp: false,
624
+ render: TestComponent,
625
+ },
626
+ ];
627
+
628
+ const { result } = renderHook(() => useCopilotKit(), {
629
+ wrapper: ({ children }) => (
630
+ <CopilotKitProvider humanInTheLoop={humanInTheLoopTools}>
631
+ {children}
632
+ </CopilotKitProvider>
633
+ ),
634
+ });
635
+
636
+ expect(
637
+ result.current.copilotkit.getTool({ toolName: "followUpTool" })
638
+ ?.followUp,
639
+ ).toBe(false);
640
+ });
641
+ });
642
+ });