@copilotkit/react-core 1.54.1 → 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 +117 -116
  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 +37 -9
  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,311 @@
1
+ /**
2
+ * Regression tests proving that React hooks and helpers still work
3
+ * identically with Zod schemas after the Standard Schema migration.
4
+ *
5
+ * Covers:
6
+ * 1. useRenderTool with complex Zod schemas
7
+ * 2. defineToolCallRenderer with Zod (named + wildcard default z.any())
8
+ * 3. useComponent with Zod schemas
9
+ * 4. The registered schema object is the original Zod instance (identity check)
10
+ */
11
+ import React from "react";
12
+ import { render } from "@testing-library/react";
13
+ import { describe, it, expect, beforeEach, vi } from "vitest";
14
+ import { z } from "zod";
15
+ import { useRenderTool } from "../use-render-tool";
16
+ import { useComponent } from "../use-component";
17
+ import { defineToolCallRenderer } from "../../types/defineToolCallRenderer";
18
+ import { useCopilotKit } from "../../providers/CopilotKitProvider";
19
+ import { useFrontendTool } from "../use-frontend-tool";
20
+ import type { ReactToolCallRenderer } from "../../types/react-tool-call-renderer";
21
+
22
+ vi.mock("../../providers/CopilotKitProvider", () => ({
23
+ useCopilotKit: vi.fn(),
24
+ }));
25
+
26
+ vi.mock("../use-frontend-tool", () => ({
27
+ useFrontendTool: vi.fn(),
28
+ }));
29
+
30
+ type MockCore = {
31
+ renderToolCalls: ReactToolCallRenderer[];
32
+ setRenderToolCalls: ReturnType<typeof vi.fn>;
33
+ addHookRenderToolCall: ReturnType<typeof vi.fn>;
34
+ removeHookRenderToolCall: ReturnType<typeof vi.fn>;
35
+ };
36
+
37
+ const mockUseCopilotKit = useCopilotKit as ReturnType<typeof vi.fn>;
38
+ const mockUseFrontendTool = useFrontendTool as ReturnType<typeof vi.fn>;
39
+
40
+ function createMockCore(initial: ReactToolCallRenderer[] = []): MockCore {
41
+ const hookEntries = new Map<string, ReactToolCallRenderer>();
42
+ const core: MockCore = {
43
+ renderToolCalls: initial,
44
+ setRenderToolCalls: vi.fn((next: ReactToolCallRenderer[]) => {
45
+ core.renderToolCalls = next;
46
+ }),
47
+ addHookRenderToolCall: vi.fn((entry: ReactToolCallRenderer) => {
48
+ const key = `${entry.agentId ?? ""}:${entry.name}`;
49
+ hookEntries.set(key, entry);
50
+ core.renderToolCalls = [...initial, ...hookEntries.values()];
51
+ }),
52
+ removeHookRenderToolCall: vi.fn(),
53
+ };
54
+ return core;
55
+ }
56
+
57
+ describe("useRenderTool Zod regression", () => {
58
+ beforeEach(() => vi.clearAllMocks());
59
+
60
+ it("registers with a complex Zod schema and preserves schema identity", () => {
61
+ const core = createMockCore();
62
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
63
+
64
+ const schema = z.object({
65
+ query: z.string().describe("Search query"),
66
+ filters: z
67
+ .object({
68
+ category: z.enum(["books", "movies"]).optional(),
69
+ tags: z.array(z.string()).optional(),
70
+ })
71
+ .optional(),
72
+ });
73
+
74
+ const Harness: React.FC = () => {
75
+ useRenderTool(
76
+ {
77
+ name: "complexSearch",
78
+ parameters: schema,
79
+ render: () => <div>result</div>,
80
+ },
81
+ [],
82
+ );
83
+ return null;
84
+ };
85
+
86
+ render(<Harness />);
87
+
88
+ expect(core.addHookRenderToolCall).toHaveBeenCalledTimes(1);
89
+ const renderer = core.renderToolCalls.find(
90
+ (r) => r.name === "complexSearch",
91
+ );
92
+ expect(renderer).toBeDefined();
93
+ // The args should be the exact same Zod schema object (identity)
94
+ expect(renderer?.args).toBe(schema);
95
+ });
96
+
97
+ it("registers with a Zod schema using enums and defaults", () => {
98
+ const core = createMockCore();
99
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
100
+
101
+ const schema = z.object({
102
+ sortBy: z.enum(["relevance", "date", "rating"]).default("relevance"),
103
+ page: z.number().int().positive().default(1),
104
+ });
105
+
106
+ const Harness: React.FC = () => {
107
+ useRenderTool(
108
+ {
109
+ name: "sortable",
110
+ parameters: schema,
111
+ render: () => <div>sorted</div>,
112
+ },
113
+ [],
114
+ );
115
+ return null;
116
+ };
117
+
118
+ render(<Harness />);
119
+
120
+ const renderer = core.renderToolCalls.find((r) => r.name === "sortable");
121
+ expect(renderer).toBeDefined();
122
+ expect(renderer?.args).toBe(schema);
123
+ });
124
+
125
+ it("registers with a Zod schema using discriminated union", () => {
126
+ const core = createMockCore();
127
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
128
+
129
+ const schema = z.object({
130
+ action: z.discriminatedUnion("type", [
131
+ z.object({ type: z.literal("search"), query: z.string() }),
132
+ z.object({ type: z.literal("navigate"), url: z.string().url() }),
133
+ ]),
134
+ });
135
+
136
+ const Harness: React.FC = () => {
137
+ useRenderTool(
138
+ {
139
+ name: "actionRenderer",
140
+ parameters: schema,
141
+ render: () => <div>action</div>,
142
+ },
143
+ [],
144
+ );
145
+ return null;
146
+ };
147
+
148
+ render(<Harness />);
149
+
150
+ const renderer = core.renderToolCalls.find(
151
+ (r) => r.name === "actionRenderer",
152
+ );
153
+ expect(renderer).toBeDefined();
154
+ expect(renderer?.args).toBe(schema);
155
+ });
156
+
157
+ it("registers with a Zod schema using nullable and arrays", () => {
158
+ const core = createMockCore();
159
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
160
+
161
+ const schema = z.object({
162
+ title: z.string(),
163
+ description: z.string().nullable(),
164
+ tags: z.array(z.string()),
165
+ });
166
+
167
+ const Harness: React.FC = () => {
168
+ useRenderTool(
169
+ {
170
+ name: "taggedItem",
171
+ parameters: schema,
172
+ render: () => <div>item</div>,
173
+ },
174
+ [],
175
+ );
176
+ return null;
177
+ };
178
+
179
+ render(<Harness />);
180
+
181
+ const renderer = core.renderToolCalls.find((r) => r.name === "taggedItem");
182
+ expect(renderer).toBeDefined();
183
+ expect(renderer?.args).toBe(schema);
184
+ });
185
+ });
186
+
187
+ describe("defineToolCallRenderer Zod regression", () => {
188
+ it("wildcard tool defaults to z.any() for args", () => {
189
+ const renderer = defineToolCallRenderer({
190
+ name: "*",
191
+ render: () => React.createElement("div", null, "wildcard"),
192
+ });
193
+
194
+ expect(renderer.name).toBe("*");
195
+ // z.any() has vendor "zod" on ~standard
196
+ expect(renderer.args["~standard"].vendor).toBe("zod");
197
+ });
198
+
199
+ it("named tool with Zod args preserves schema identity", () => {
200
+ const schema = z.object({
201
+ city: z.string(),
202
+ units: z.enum(["celsius", "fahrenheit"]).optional(),
203
+ });
204
+
205
+ const renderer = defineToolCallRenderer({
206
+ name: "weather",
207
+ args: schema,
208
+ render: () => React.createElement("div", null, "weather"),
209
+ });
210
+
211
+ expect(renderer.name).toBe("weather");
212
+ expect(renderer.args).toBe(schema);
213
+ });
214
+
215
+ it("named tool with complex Zod args", () => {
216
+ const schema = z.object({
217
+ results: z.array(
218
+ z.object({
219
+ id: z.number(),
220
+ title: z.string(),
221
+ score: z.number().min(0).max(1),
222
+ }),
223
+ ),
224
+ });
225
+
226
+ const renderer = defineToolCallRenderer({
227
+ name: "searchResults",
228
+ args: schema,
229
+ render: () => React.createElement("div", null, "results"),
230
+ });
231
+
232
+ expect(renderer.name).toBe("searchResults");
233
+ expect(renderer.args).toBe(schema);
234
+ });
235
+
236
+ it("named tool with agentId", () => {
237
+ const schema = z.object({ query: z.string() });
238
+
239
+ const renderer = defineToolCallRenderer({
240
+ name: "agentSearch",
241
+ args: schema,
242
+ render: () => React.createElement("div", null, "agent"),
243
+ agentId: "agent-123",
244
+ });
245
+
246
+ expect(renderer.name).toBe("agentSearch");
247
+ expect(renderer.agentId).toBe("agent-123");
248
+ expect(renderer.args).toBe(schema);
249
+ });
250
+ });
251
+
252
+ describe("useComponent Zod regression", () => {
253
+ beforeEach(() => vi.clearAllMocks());
254
+
255
+ it("registers a component with a Zod schema", () => {
256
+ const WeatherCard: React.FC<{ city: string }> = ({ city }) => (
257
+ <div>{city}</div>
258
+ );
259
+
260
+ const schema = z.object({ city: z.string() });
261
+
262
+ const Harness: React.FC = () => {
263
+ useComponent({
264
+ name: "weatherCard",
265
+ parameters: schema,
266
+ render: WeatherCard,
267
+ });
268
+ return null;
269
+ };
270
+
271
+ render(<Harness />);
272
+
273
+ expect(mockUseFrontendTool).toHaveBeenCalledTimes(1);
274
+ const [toolConfig] = mockUseFrontendTool.mock.calls[0] as [
275
+ { name: string; parameters: any },
276
+ ];
277
+ expect(toolConfig.name).toBe("weatherCard");
278
+ expect(toolConfig.parameters).toBe(schema);
279
+ expect(toolConfig.parameters["~standard"].vendor).toBe("zod");
280
+ });
281
+
282
+ it("registers a component with a complex Zod schema", () => {
283
+ const DataGrid: React.FC<{
284
+ columns: string[];
285
+ sortBy: string;
286
+ }> = () => <div>grid</div>;
287
+
288
+ const schema = z.object({
289
+ columns: z.array(z.string()),
290
+ sortBy: z.enum(["name", "date", "size"]).default("name"),
291
+ });
292
+
293
+ const Harness: React.FC = () => {
294
+ useComponent({
295
+ name: "dataGrid",
296
+ parameters: schema,
297
+ render: DataGrid,
298
+ });
299
+ return null;
300
+ };
301
+
302
+ render(<Harness />);
303
+
304
+ expect(mockUseFrontendTool).toHaveBeenCalledTimes(1);
305
+ const [toolConfig] = mockUseFrontendTool.mock.calls[0] as [
306
+ { name: string; parameters: any },
307
+ ];
308
+ expect(toolConfig.name).toBe("dataGrid");
309
+ expect(toolConfig.parameters).toBe(schema);
310
+ });
311
+ });
@@ -0,0 +1,18 @@
1
+ // React hooks for CopilotKit2
2
+ export { useRenderToolCall } from "./use-render-tool-call";
3
+ export { useRenderCustomMessages } from "./use-render-custom-messages";
4
+ export { useRenderActivityMessage } from "./use-render-activity-message";
5
+ export { useFrontendTool } from "./use-frontend-tool";
6
+ export { useComponent } from "./use-component";
7
+ export { useRenderTool } from "./use-render-tool";
8
+ export { useDefaultRenderTool } from "./use-default-render-tool";
9
+ export { useHumanInTheLoop } from "./use-human-in-the-loop";
10
+ export { useAgent, UseAgentUpdate } from "./use-agent";
11
+ export { useAgentContext } from "./use-agent-context";
12
+ export type { AgentContextInput, JsonSerializable } from "./use-agent-context";
13
+ export { useSuggestions } from "./use-suggestions";
14
+ export { useConfigureSuggestions } from "./use-configure-suggestions";
15
+ export { useInterrupt } from "./use-interrupt";
16
+ export type { UseInterruptConfig } from "./use-interrupt";
17
+ export { useThreads } from "./use-threads";
18
+ export type { Thread, UseThreadsInput, UseThreadsResult } from "./use-threads";
@@ -0,0 +1,45 @@
1
+ import { useCopilotKit } from "../providers/CopilotKitProvider";
2
+ import { useLayoutEffect, useMemo } from "react";
3
+
4
+ /**
5
+ * Represents any value that can be serialized to JSON.
6
+ */
7
+ export type JsonSerializable =
8
+ | string
9
+ | number
10
+ | boolean
11
+ | null
12
+ | JsonSerializable[]
13
+ | { [key: string]: JsonSerializable };
14
+
15
+ /**
16
+ * Context configuration for useAgentContext.
17
+ * Accepts any JSON-serializable value which will be converted to a string.
18
+ */
19
+ export interface AgentContextInput {
20
+ /** A human-readable description of what this context represents */
21
+ description: string;
22
+ /** The context value - will be converted to a JSON string if not already a string */
23
+ value: JsonSerializable;
24
+ }
25
+
26
+ export function useAgentContext(context: AgentContextInput) {
27
+ const { description, value } = context;
28
+ const { copilotkit } = useCopilotKit();
29
+
30
+ const stringValue = useMemo(() => {
31
+ if (typeof value === "string") {
32
+ return value;
33
+ }
34
+ return JSON.stringify(value);
35
+ }, [value]);
36
+
37
+ useLayoutEffect(() => {
38
+ if (!copilotkit) return;
39
+
40
+ const id = copilotkit.addContext({ description, value: stringValue });
41
+ return () => {
42
+ copilotkit.removeContext(id);
43
+ };
44
+ }, [description, stringValue, copilotkit]);
45
+ }
@@ -0,0 +1,155 @@
1
+ import { useCopilotKit } from "../providers/CopilotKitProvider";
2
+ import { useMemo, useEffect, useReducer, useRef } from "react";
3
+ import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
4
+ import { AbstractAgent } from "@ag-ui/client";
5
+ import {
6
+ ProxiedCopilotRuntimeAgent,
7
+ CopilotKitCoreRuntimeConnectionStatus,
8
+ } from "@copilotkit/core";
9
+
10
+ export enum UseAgentUpdate {
11
+ OnMessagesChanged = "OnMessagesChanged",
12
+ OnStateChanged = "OnStateChanged",
13
+ OnRunStatusChanged = "OnRunStatusChanged",
14
+ }
15
+
16
+ const ALL_UPDATES: UseAgentUpdate[] = [
17
+ UseAgentUpdate.OnMessagesChanged,
18
+ UseAgentUpdate.OnStateChanged,
19
+ UseAgentUpdate.OnRunStatusChanged,
20
+ ];
21
+
22
+ export interface UseAgentProps {
23
+ agentId?: string;
24
+ updates?: UseAgentUpdate[];
25
+ }
26
+
27
+ export function useAgent({ agentId, updates }: UseAgentProps = {}) {
28
+ agentId ??= DEFAULT_AGENT_ID;
29
+
30
+ const { copilotkit } = useCopilotKit();
31
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
32
+
33
+ const updateFlags = useMemo(
34
+ () => updates ?? ALL_UPDATES,
35
+ [JSON.stringify(updates)],
36
+ );
37
+
38
+ // Cache provisional agents to avoid creating new references on every render
39
+ // while the runtime is still connecting. A new reference would cascade into
40
+ // CopilotChat's connectAgent effect, causing unnecessary HTTP calls.
41
+ const provisionalAgentCache = useRef<Map<string, ProxiedCopilotRuntimeAgent>>(
42
+ new Map(),
43
+ );
44
+
45
+ const agent: AbstractAgent = useMemo(() => {
46
+ const existing = copilotkit.getAgent(agentId);
47
+ if (existing) {
48
+ // Real agent found — clear any cached provisional for this ID
49
+ provisionalAgentCache.current.delete(agentId);
50
+ return existing;
51
+ }
52
+
53
+ const isRuntimeConfigured = copilotkit.runtimeUrl !== undefined;
54
+ const status = copilotkit.runtimeConnectionStatus;
55
+
56
+ // While runtime is not yet synced, return a provisional runtime agent
57
+ if (
58
+ isRuntimeConfigured &&
59
+ (status === CopilotKitCoreRuntimeConnectionStatus.Disconnected ||
60
+ status === CopilotKitCoreRuntimeConnectionStatus.Connecting)
61
+ ) {
62
+ // Return cached provisional if available (keeps reference stable)
63
+ const cached = provisionalAgentCache.current.get(agentId);
64
+ if (cached) {
65
+ // Update headers on the cached agent in case they changed
66
+ cached.headers = { ...copilotkit.headers };
67
+ return cached;
68
+ }
69
+
70
+ const provisional = new ProxiedCopilotRuntimeAgent({
71
+ runtimeUrl: copilotkit.runtimeUrl,
72
+ agentId,
73
+ transport: copilotkit.runtimeTransport,
74
+ runtimeMode: "pending",
75
+ });
76
+ // Apply current headers so runs/connects inherit them
77
+ provisional.headers = { ...copilotkit.headers };
78
+ provisionalAgentCache.current.set(agentId, provisional);
79
+ return provisional;
80
+ }
81
+
82
+ // Runtime is in Error state — return a provisional agent instead of throwing.
83
+ // The error has already been emitted through the subscriber system
84
+ // (RUNTIME_INFO_FETCH_FAILED). Throwing here would crash the React tree;
85
+ // returning a provisional agent lets onError handlers fire while keeping
86
+ // the app alive.
87
+ if (
88
+ isRuntimeConfigured &&
89
+ status === CopilotKitCoreRuntimeConnectionStatus.Error
90
+ ) {
91
+ const provisional = new ProxiedCopilotRuntimeAgent({
92
+ runtimeUrl: copilotkit.runtimeUrl,
93
+ agentId,
94
+ transport: copilotkit.runtimeTransport,
95
+ runtimeMode: "pending",
96
+ });
97
+ provisional.headers = { ...copilotkit.headers };
98
+ return provisional;
99
+ }
100
+
101
+ // No runtime configured and agent doesn't exist — this is a configuration error.
102
+ const knownAgents = Object.keys(copilotkit.agents ?? {});
103
+ const runtimePart = isRuntimeConfigured
104
+ ? `runtimeUrl=${copilotkit.runtimeUrl}`
105
+ : "no runtimeUrl";
106
+ throw new Error(
107
+ `useAgent: Agent '${agentId}' not found after runtime sync (${runtimePart}). ` +
108
+ (knownAgents.length
109
+ ? `Known agents: [${knownAgents.join(", ")}]`
110
+ : "No agents registered.") +
111
+ " Verify your runtime /info and/or agents__unsafe_dev_only.",
112
+ );
113
+ // eslint-disable-next-line react-hooks/exhaustive-deps
114
+ }, [
115
+ agentId,
116
+ copilotkit.agents,
117
+ copilotkit.runtimeConnectionStatus,
118
+ copilotkit.runtimeUrl,
119
+ copilotkit.runtimeTransport,
120
+ JSON.stringify(copilotkit.headers),
121
+ ]);
122
+
123
+ useEffect(() => {
124
+ if (updateFlags.length === 0) {
125
+ return;
126
+ }
127
+
128
+ const handlers: Parameters<AbstractAgent["subscribe"]>[0] = {};
129
+
130
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
131
+ // Content stripping for immutableContent renderers is handled by CopilotKitCoreReact
132
+ handlers.onMessagesChanged = () => {
133
+ forceUpdate();
134
+ };
135
+ }
136
+
137
+ if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) {
138
+ handlers.onStateChanged = forceUpdate;
139
+ }
140
+
141
+ if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
142
+ handlers.onRunInitialized = forceUpdate;
143
+ handlers.onRunFinalized = forceUpdate;
144
+ handlers.onRunFailed = forceUpdate;
145
+ }
146
+
147
+ const subscription = agent.subscribe(handlers);
148
+ return () => subscription.unsubscribe();
149
+ // eslint-disable-next-line react-hooks/exhaustive-deps
150
+ }, [agent, forceUpdate, JSON.stringify(updateFlags)]);
151
+
152
+ return {
153
+ agent,
154
+ };
155
+ }
@@ -0,0 +1,89 @@
1
+ import type { StandardSchemaV1, InferSchemaOutput } from "@copilotkit/shared";
2
+ import type { ComponentType } from "react";
3
+ import { useFrontendTool } from "./use-frontend-tool";
4
+
5
+ type InferRenderProps<T> = T extends StandardSchemaV1
6
+ ? InferSchemaOutput<T>
7
+ : any;
8
+
9
+ /**
10
+ * Registers a React component as a frontend tool renderer in chat.
11
+ *
12
+ * This hook is a convenience wrapper around `useFrontendTool` that:
13
+ * - builds a model-facing tool description,
14
+ * - forwards optional schema parameters (any Standard Schema V1 compatible library),
15
+ * - renders your component with tool call parameters.
16
+ *
17
+ * Use this when you want to display a typed visual component for a tool call
18
+ * without manually wiring a full frontend tool object.
19
+ *
20
+ * When `parameters` is provided, render props are inferred from the schema.
21
+ * When omitted, the render component may accept any props.
22
+ *
23
+ * @typeParam TSchema - Schema describing tool parameters, or `undefined` when no schema is given.
24
+ * @param config - Tool registration config.
25
+ * @param deps - Optional dependencies to refresh registration (same semantics as `useEffect`).
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * // Without parameters — render accepts any props
30
+ * useComponent({
31
+ * name: "showGreeting",
32
+ * render: ({ message }: { message: string }) => <div>{message}</div>,
33
+ * });
34
+ * ```
35
+ *
36
+ * @example
37
+ * ```tsx
38
+ * // With parameters — render props inferred from schema
39
+ * useComponent({
40
+ * name: "showWeatherCard",
41
+ * parameters: z.object({ city: z.string() }),
42
+ * render: ({ city }) => <div>{city}</div>,
43
+ * });
44
+ * ```
45
+ *
46
+ * @example
47
+ * ```tsx
48
+ * useComponent(
49
+ * {
50
+ * name: "renderProfile",
51
+ * parameters: z.object({ userId: z.string() }),
52
+ * render: ProfileCard,
53
+ * agentId: "support-agent",
54
+ * },
55
+ * [selectedAgentId],
56
+ * );
57
+ * ```
58
+ */
59
+ export function useComponent<
60
+ TSchema extends StandardSchemaV1 | undefined = undefined,
61
+ >(
62
+ config: {
63
+ name: string;
64
+ description?: string;
65
+ parameters?: TSchema;
66
+ render: ComponentType<NoInfer<InferRenderProps<TSchema>>>;
67
+ agentId?: string;
68
+ },
69
+ deps?: ReadonlyArray<unknown>,
70
+ ): void {
71
+ const prefix = `Use this tool to display the "${config.name}" component in the chat. This tool renders a visual UI component for the user.`;
72
+ const fullDescription = config.description
73
+ ? `${prefix}\n\n${config.description}`
74
+ : prefix;
75
+
76
+ useFrontendTool(
77
+ {
78
+ name: config.name,
79
+ description: fullDescription,
80
+ parameters: config.parameters,
81
+ render: ({ args }: { args: unknown }) => {
82
+ const Component = config.render;
83
+ return <Component {...(args as InferRenderProps<TSchema>)} />;
84
+ },
85
+ agentId: config.agentId,
86
+ },
87
+ deps,
88
+ );
89
+ }