@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,60 @@
1
+ import * as React from "react";
2
+ import * as TooltipPrimitive from "@radix-ui/react-tooltip";
3
+
4
+ import { cn } from "../../lib/utils";
5
+
6
+ function TooltipProvider({
7
+ delayDuration = 0,
8
+ ...props
9
+ }: React.ComponentProps<typeof TooltipPrimitive.Provider>) {
10
+ return (
11
+ <TooltipPrimitive.Provider
12
+ data-slot="tooltip-provider"
13
+ delayDuration={delayDuration}
14
+ {...props}
15
+ />
16
+ );
17
+ }
18
+
19
+ function Tooltip({
20
+ ...props
21
+ }: React.ComponentProps<typeof TooltipPrimitive.Root>) {
22
+ return (
23
+ <TooltipProvider>
24
+ <TooltipPrimitive.Root data-slot="tooltip" {...props} />
25
+ </TooltipProvider>
26
+ );
27
+ }
28
+
29
+ function TooltipTrigger({
30
+ ...props
31
+ }: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {
32
+ return <TooltipPrimitive.Trigger data-slot="tooltip-trigger" {...props} />;
33
+ }
34
+
35
+ function TooltipContent({
36
+ className,
37
+ sideOffset = 0,
38
+ children,
39
+ ...props
40
+ }: React.ComponentProps<typeof TooltipPrimitive.Content>) {
41
+ return (
42
+ <TooltipPrimitive.Portal>
43
+ <TooltipPrimitive.Content
44
+ data-copilotkit
45
+ data-slot="tooltip-content"
46
+ sideOffset={sideOffset}
47
+ className={cn(
48
+ "cpk:bg-primary cpk:text-primary-foreground cpk:animate-in cpk:fade-in-0 cpk:zoom-in-95 cpk:data-[state=closed]:animate-out cpk:data-[state=closed]:fade-out-0 cpk:data-[state=closed]:zoom-out-95 cpk:data-[side=bottom]:slide-in-from-top-2 cpk:data-[side=left]:slide-in-from-right-2 cpk:data-[side=right]:slide-in-from-left-2 cpk:data-[side=top]:slide-in-from-bottom-2 cpk:z-50 cpk:w-fit cpk:origin-(--radix-tooltip-content-transform-origin) cpk:rounded-md cpk:px-3 cpk:py-1.5 cpk:text-xs cpk:text-balance",
49
+ className,
50
+ )}
51
+ {...props}
52
+ >
53
+ {children}
54
+ <TooltipPrimitive.Arrow className="cpk:bg-primary cpk:fill-primary cpk:z-50 cpk:size-2.5 cpk:translate-y-[calc(-50%_-_2px)] cpk:rotate-45 cpk:rounded-[2px]" />
55
+ </TooltipPrimitive.Content>
56
+ </TooltipPrimitive.Portal>
57
+ );
58
+ }
59
+
60
+ export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };
@@ -0,0 +1,152 @@
1
+ import React from "react";
2
+ import { describe, it, expectTypeOf } from "vitest";
3
+ import { z } from "zod";
4
+ import * as v from "valibot";
5
+ import { type } from "arktype";
6
+ import type { StandardSchemaV1, InferSchemaOutput } from "@copilotkit/shared";
7
+ import type {
8
+ RenderToolProps,
9
+ RenderToolInProgressProps,
10
+ RenderToolExecutingProps,
11
+ RenderToolCompleteProps,
12
+ } from "../use-render-tool";
13
+ import type { ReactToolCallRenderer } from "../../types/react-tool-call-renderer";
14
+
15
+ describe("RenderToolProps type inference", () => {
16
+ describe("with Zod schemas", () => {
17
+ type ZodSchema = z.ZodObject<{
18
+ query: z.ZodString;
19
+ limit: z.ZodOptional<z.ZodNumber>;
20
+ }>;
21
+
22
+ it("RenderToolInProgressProps has Partial parameters", () => {
23
+ expectTypeOf<
24
+ RenderToolInProgressProps<ZodSchema>["parameters"]
25
+ >().toEqualTypeOf<
26
+ Partial<{ query: string; limit?: number | undefined }>
27
+ >();
28
+ });
29
+
30
+ it("RenderToolExecutingProps has full parameters", () => {
31
+ expectTypeOf<
32
+ RenderToolExecutingProps<ZodSchema>["parameters"]
33
+ >().toEqualTypeOf<{ query: string; limit?: number | undefined }>();
34
+ });
35
+
36
+ it("RenderToolCompleteProps has full parameters and string result", () => {
37
+ type Complete = RenderToolCompleteProps<ZodSchema>;
38
+ expectTypeOf<Complete["parameters"]>().toEqualTypeOf<{
39
+ query: string;
40
+ limit?: number | undefined;
41
+ }>();
42
+ expectTypeOf<Complete["result"]>().toBeString();
43
+ expectTypeOf<Complete["status"]>().toEqualTypeOf<"complete">();
44
+ });
45
+
46
+ it("RenderToolProps is a discriminated union", () => {
47
+ type Props = RenderToolProps<ZodSchema>;
48
+
49
+ // The union should include all three statuses
50
+ expectTypeOf<Props>().toMatchTypeOf<{ name: string }>();
51
+ });
52
+ });
53
+
54
+ describe("with Valibot schemas", () => {
55
+ it("infers Partial parameters for inProgress", () => {
56
+ const schema = v.object({
57
+ city: v.string(),
58
+ temp: v.number(),
59
+ });
60
+ type S = typeof schema;
61
+
62
+ expectTypeOf<RenderToolInProgressProps<S>["parameters"]>().toEqualTypeOf<
63
+ Partial<{ city: string; temp: number }>
64
+ >();
65
+ });
66
+
67
+ it("infers full parameters for executing", () => {
68
+ const schema = v.object({
69
+ city: v.string(),
70
+ });
71
+ type S = typeof schema;
72
+
73
+ expectTypeOf<RenderToolExecutingProps<S>["parameters"]>().toEqualTypeOf<{
74
+ city: string;
75
+ }>();
76
+ });
77
+ });
78
+
79
+ describe("with ArkType schemas", () => {
80
+ it("infers Partial parameters for inProgress", () => {
81
+ const schema = type({
82
+ query: "string",
83
+ limit: "number",
84
+ });
85
+ type S = typeof schema;
86
+
87
+ expectTypeOf<RenderToolInProgressProps<S>["parameters"]>().toEqualTypeOf<
88
+ Partial<{ query: string; limit: number }>
89
+ >();
90
+ });
91
+
92
+ it("infers full parameters for complete", () => {
93
+ const schema = type({
94
+ query: "string",
95
+ });
96
+ type S = typeof schema;
97
+
98
+ expectTypeOf<RenderToolCompleteProps<S>["parameters"]>().toEqualTypeOf<{
99
+ query: string;
100
+ }>();
101
+ });
102
+ });
103
+ });
104
+
105
+ describe("ReactToolCallRenderer type inference", () => {
106
+ it("args field accepts a StandardSchemaV1", () => {
107
+ expectTypeOf<ReactToolCallRenderer<{ x: number }>["args"]>().toMatchTypeOf<
108
+ StandardSchemaV1<any, { x: number }>
109
+ >();
110
+ });
111
+
112
+ it("render component receives typed args", () => {
113
+ type Renderer = ReactToolCallRenderer<{ city: string }>;
114
+
115
+ // The render component should be a ComponentType
116
+ expectTypeOf<Renderer["render"]>().toMatchTypeOf<
117
+ React.ComponentType<any>
118
+ >();
119
+ });
120
+ });
121
+
122
+ describe("useComponent type inference", () => {
123
+ it("InferRenderProps extracts output from StandardSchemaV1", () => {
124
+ // This tests the type utility used inside useComponent
125
+ type InferRenderProps<T> = T extends StandardSchemaV1
126
+ ? InferSchemaOutput<T>
127
+ : any;
128
+
129
+ const zodSchema = z.object({ city: z.string() });
130
+ expectTypeOf<InferRenderProps<typeof zodSchema>>().toEqualTypeOf<{
131
+ city: string;
132
+ }>();
133
+
134
+ const valibotSchema = v.object({ query: v.string() });
135
+ expectTypeOf<InferRenderProps<typeof valibotSchema>>().toEqualTypeOf<{
136
+ query: string;
137
+ }>();
138
+
139
+ const arktypeSchema = type({ id: "string" });
140
+ expectTypeOf<InferRenderProps<typeof arktypeSchema>>().toEqualTypeOf<{
141
+ id: string;
142
+ }>();
143
+ });
144
+
145
+ it("InferRenderProps returns any for undefined", () => {
146
+ type InferRenderProps<T> = T extends StandardSchemaV1
147
+ ? InferSchemaOutput<T>
148
+ : any;
149
+
150
+ expectTypeOf<InferRenderProps<undefined>>().toBeAny();
151
+ });
152
+ });
@@ -0,0 +1,282 @@
1
+ import React from "react";
2
+ import { render } from "@testing-library/react";
3
+ import { describe, it, expect, beforeEach, vi } from "vitest";
4
+ import { z } from "zod";
5
+ import * as v from "valibot";
6
+ import { type } from "arktype";
7
+ import { useRenderTool, type RenderToolProps } from "../use-render-tool";
8
+ import { useComponent } from "../use-component";
9
+ import { useCopilotKit } from "../../providers/CopilotKitProvider";
10
+ import { useFrontendTool } from "../use-frontend-tool";
11
+ import type { ReactToolCallRenderer } from "../../types/react-tool-call-renderer";
12
+ import type { StandardSchemaV1 } from "@copilotkit/shared";
13
+
14
+ vi.mock("../../providers/CopilotKitProvider", () => ({
15
+ useCopilotKit: vi.fn(),
16
+ }));
17
+
18
+ vi.mock("../use-frontend-tool", () => ({
19
+ useFrontendTool: vi.fn(),
20
+ }));
21
+
22
+ type MockCore = {
23
+ renderToolCalls: ReactToolCallRenderer[];
24
+ setRenderToolCalls: ReturnType<typeof vi.fn>;
25
+ addHookRenderToolCall: ReturnType<typeof vi.fn>;
26
+ removeHookRenderToolCall: ReturnType<typeof vi.fn>;
27
+ };
28
+
29
+ const mockUseCopilotKit = useCopilotKit as ReturnType<typeof vi.fn>;
30
+ const mockUseFrontendTool = useFrontendTool as ReturnType<typeof vi.fn>;
31
+
32
+ function createMockCore(
33
+ initialRenderToolCalls: ReactToolCallRenderer[] = [],
34
+ ): MockCore {
35
+ const hookEntries = new Map<string, ReactToolCallRenderer>();
36
+ const core: MockCore = {
37
+ renderToolCalls: initialRenderToolCalls,
38
+ setRenderToolCalls: vi.fn((next: ReactToolCallRenderer[]) => {
39
+ core.renderToolCalls = next;
40
+ }),
41
+ addHookRenderToolCall: vi.fn((entry: ReactToolCallRenderer) => {
42
+ const key = `${entry.agentId ?? ""}:${entry.name}`;
43
+ hookEntries.set(key, entry);
44
+ core.renderToolCalls = [
45
+ ...initialRenderToolCalls,
46
+ ...hookEntries.values(),
47
+ ];
48
+ }),
49
+ removeHookRenderToolCall: vi.fn(),
50
+ };
51
+
52
+ return core;
53
+ }
54
+
55
+ describe("useRenderTool with Standard Schema", () => {
56
+ beforeEach(() => {
57
+ vi.clearAllMocks();
58
+ });
59
+
60
+ describe("Zod schemas (existing behavior)", () => {
61
+ it("registers a renderer with a Zod schema", () => {
62
+ const core = createMockCore();
63
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
64
+
65
+ const schema = z.object({ query: z.string() });
66
+
67
+ const Harness: React.FC = () => {
68
+ useRenderTool(
69
+ {
70
+ name: "search",
71
+ parameters: schema,
72
+ render: () => <div>render</div>,
73
+ },
74
+ [],
75
+ );
76
+ return null;
77
+ };
78
+
79
+ render(<Harness />);
80
+
81
+ expect(core.addHookRenderToolCall).toHaveBeenCalledTimes(1);
82
+ const renderer = core.renderToolCalls.find(
83
+ (item) => item.name === "search",
84
+ );
85
+ expect(renderer).toBeDefined();
86
+ expect(renderer?.args).toBe(schema);
87
+ });
88
+ });
89
+
90
+ describe("Valibot schemas", () => {
91
+ it("registers a renderer with a Valibot schema", () => {
92
+ const core = createMockCore();
93
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
94
+
95
+ const schema = v.object({ query: v.string() });
96
+
97
+ const Harness: React.FC = () => {
98
+ useRenderTool(
99
+ {
100
+ name: "searchValibot",
101
+ parameters: schema as unknown as StandardSchemaV1<
102
+ any,
103
+ { query: string }
104
+ >,
105
+ render: () => <div>valibot render</div>,
106
+ },
107
+ [],
108
+ );
109
+ return null;
110
+ };
111
+
112
+ render(<Harness />);
113
+
114
+ expect(core.addHookRenderToolCall).toHaveBeenCalledTimes(1);
115
+ const renderer = core.renderToolCalls.find(
116
+ (item) => item.name === "searchValibot",
117
+ );
118
+ expect(renderer).toBeDefined();
119
+ expect(renderer?.args["~standard"].vendor).toBe("valibot");
120
+ });
121
+ });
122
+
123
+ describe("ArkType schemas", () => {
124
+ it("registers a renderer with an ArkType schema", () => {
125
+ const core = createMockCore();
126
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
127
+
128
+ const schema = type({ query: "string" });
129
+
130
+ const Harness: React.FC = () => {
131
+ useRenderTool(
132
+ {
133
+ name: "searchArktype",
134
+ parameters: schema as unknown as StandardSchemaV1<
135
+ any,
136
+ { query: string }
137
+ >,
138
+ render: () => <div>arktype render</div>,
139
+ },
140
+ [],
141
+ );
142
+ return null;
143
+ };
144
+
145
+ render(<Harness />);
146
+
147
+ expect(core.addHookRenderToolCall).toHaveBeenCalledTimes(1);
148
+ const renderer = core.renderToolCalls.find(
149
+ (item) => item.name === "searchArktype",
150
+ );
151
+ expect(renderer).toBeDefined();
152
+ expect(renderer?.args["~standard"].vendor).toBe("arktype");
153
+ });
154
+ });
155
+
156
+ describe("Mixed schemas in the same registry", () => {
157
+ it("registers renderers with Zod, Valibot, and ArkType schemas", () => {
158
+ const core = createMockCore();
159
+ mockUseCopilotKit.mockReturnValue({ copilotkit: core });
160
+
161
+ const ZodHarness: React.FC = () => {
162
+ useRenderTool(
163
+ {
164
+ name: "zodRenderer",
165
+ parameters: z.object({ a: z.string() }),
166
+ render: () => <div>zod</div>,
167
+ },
168
+ [],
169
+ );
170
+ return null;
171
+ };
172
+
173
+ const ValibotHarness: React.FC = () => {
174
+ useRenderTool(
175
+ {
176
+ name: "valibotRenderer",
177
+ parameters: v.object({
178
+ b: v.string(),
179
+ }) as unknown as StandardSchemaV1<any, { b: string }>,
180
+ render: () => <div>valibot</div>,
181
+ },
182
+ [],
183
+ );
184
+ return null;
185
+ };
186
+
187
+ const ArktypeHarness: React.FC = () => {
188
+ useRenderTool(
189
+ {
190
+ name: "arktypeRenderer",
191
+ parameters: type({
192
+ c: "string",
193
+ }) as unknown as StandardSchemaV1<any, { c: string }>,
194
+ render: () => <div>arktype</div>,
195
+ },
196
+ [],
197
+ );
198
+ return null;
199
+ };
200
+
201
+ render(
202
+ <>
203
+ <ZodHarness />
204
+ <ValibotHarness />
205
+ <ArktypeHarness />
206
+ </>,
207
+ );
208
+
209
+ expect(core.renderToolCalls).toHaveLength(3);
210
+ const names = core.renderToolCalls.map((r) => r.name);
211
+ expect(names).toContain("zodRenderer");
212
+ expect(names).toContain("valibotRenderer");
213
+ expect(names).toContain("arktypeRenderer");
214
+ });
215
+ });
216
+ });
217
+
218
+ describe("useComponent with Standard Schema", () => {
219
+ beforeEach(() => {
220
+ vi.clearAllMocks();
221
+ });
222
+
223
+ it("registers a component with a Valibot schema", () => {
224
+ const DemoComponent: React.FC<{ city: string }> = ({ city }) => (
225
+ <div>{city}</div>
226
+ );
227
+
228
+ const schema = v.object({
229
+ city: v.string(),
230
+ });
231
+
232
+ const Harness: React.FC = () => {
233
+ useComponent({
234
+ name: "weatherCard",
235
+ parameters: schema as unknown as StandardSchemaV1<
236
+ any,
237
+ { city: string }
238
+ >,
239
+ render: DemoComponent,
240
+ });
241
+ return null;
242
+ };
243
+
244
+ render(<Harness />);
245
+
246
+ expect(mockUseFrontendTool).toHaveBeenCalledTimes(1);
247
+ const [toolConfig] = mockUseFrontendTool.mock.calls[0] as [
248
+ { name: string; parameters: any },
249
+ ];
250
+ expect(toolConfig.name).toBe("weatherCard");
251
+ expect(toolConfig.parameters["~standard"].vendor).toBe("valibot");
252
+ });
253
+
254
+ it("registers a component with an ArkType schema", () => {
255
+ const DemoComponent: React.FC<{ city: string }> = ({ city }) => (
256
+ <div>{city}</div>
257
+ );
258
+
259
+ const schema = type({ city: "string" });
260
+
261
+ const Harness: React.FC = () => {
262
+ useComponent({
263
+ name: "weatherCardArktype",
264
+ parameters: schema as unknown as StandardSchemaV1<
265
+ any,
266
+ { city: string }
267
+ >,
268
+ render: DemoComponent,
269
+ });
270
+ return null;
271
+ };
272
+
273
+ render(<Harness />);
274
+
275
+ expect(mockUseFrontendTool).toHaveBeenCalledTimes(1);
276
+ const [toolConfig] = mockUseFrontendTool.mock.calls[0] as [
277
+ { name: string; parameters: any },
278
+ ];
279
+ expect(toolConfig.name).toBe("weatherCardArktype");
280
+ expect(toolConfig.parameters["~standard"].vendor).toBe("arktype");
281
+ });
282
+ });
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Integration test for the context timing race condition (CPK-7060).
3
+ *
4
+ * When a useFrontendTool handler calls setState(), the corresponding
5
+ * useAgentContext value is updated asynchronously (React defers useEffect to a
6
+ * later scheduler task). Without yielding before the follow-up agent run,
7
+ * runAgent reads stale context from the store.
8
+ *
9
+ * CopilotKitCoreReact.waitForPendingFrameworkUpdates() fixes this by awaiting a
10
+ * zero-delay timeout, which yields to React's scheduler before reading context.
11
+ *
12
+ * This test uses real React rendering in jsdom so that actual useState +
13
+ * useAgentContext + useFrontendTool lifecycle interactions are exercised.
14
+ */
15
+ import React, { useState } from "react";
16
+ import { screen, fireEvent, waitFor } from "@testing-library/react";
17
+ import { z } from "zod";
18
+ import { useFrontendTool } from "../use-frontend-tool";
19
+ import { useAgentContext } from "../use-agent-context";
20
+ import { CopilotChat } from "../../components/chat/CopilotChat";
21
+ import {
22
+ type AgentSubscriber,
23
+ type BaseEvent,
24
+ type RunAgentParameters,
25
+ } from "@ag-ui/client";
26
+ import type { Context } from "@ag-ui/client";
27
+ import {
28
+ MockStepwiseAgent,
29
+ renderWithCopilotKit,
30
+ runStartedEvent,
31
+ runFinishedEvent,
32
+ toolCallChunkEvent,
33
+ testId,
34
+ } from "../../__tests__/utils/test-helpers";
35
+
36
+ describe("useAgentContext timing - follow-up run sees updated context", () => {
37
+ it("follow-up agent run receives context updated by useFrontendTool handler", async () => {
38
+ /**
39
+ * Agent subclass that records the context parameter on every runAgent call.
40
+ * After complete() is called on the subject the Observable completes
41
+ * immediately for subsequent subscriptions, so the follow-up run resolves
42
+ * with no new messages — which is fine; we only need to capture context.
43
+ */
44
+ class ContextCapturingAgent extends MockStepwiseAgent {
45
+ public contextPerRun: Context[][] = [];
46
+
47
+ async runAgent(
48
+ parameters?: RunAgentParameters,
49
+ subscriber?: AgentSubscriber,
50
+ ) {
51
+ this.contextPerRun.push(parameters?.context ?? []);
52
+ return super.runAgent(parameters, subscriber);
53
+ }
54
+ }
55
+
56
+ const agent = new ContextCapturingAgent();
57
+
58
+ /**
59
+ * Component that wires React state into useAgentContext and exposes a
60
+ * frontend tool that updates that state.
61
+ */
62
+ const TestComponent: React.FC = () => {
63
+ const [prefs, setPrefs] = useState<{ spicy: boolean }>({ spicy: true });
64
+
65
+ useAgentContext({
66
+ description: "user preferences",
67
+ value: prefs,
68
+ });
69
+
70
+ useFrontendTool({
71
+ name: "updatePrefs",
72
+ parameters: z.object({}),
73
+ followUp: true,
74
+ handler: async () => {
75
+ setPrefs({ spicy: false });
76
+ },
77
+ });
78
+
79
+ return null;
80
+ };
81
+
82
+ renderWithCopilotKit({
83
+ agent,
84
+ children: (
85
+ <>
86
+ <TestComponent />
87
+ <div style={{ height: 400 }}>
88
+ <CopilotChat welcomeScreen={false} />
89
+ </div>
90
+ </>
91
+ ),
92
+ });
93
+
94
+ // Submit a user message to start the first agent run
95
+ const input = await screen.findByRole("textbox");
96
+ fireEvent.change(input, { target: { value: "Update my preferences" } });
97
+ fireEvent.keyDown(input, { key: "Enter", code: "Enter" });
98
+
99
+ // Wait for the first runAgent call to arrive
100
+ await waitFor(() => {
101
+ expect(agent.contextPerRun.length).toBeGreaterThanOrEqual(1);
102
+ });
103
+
104
+ // First run: the agent calls the updatePrefs tool
105
+ const messageId = testId("msg");
106
+ const toolCallId = testId("tc");
107
+
108
+ agent.emit(runStartedEvent());
109
+ agent.emit(
110
+ toolCallChunkEvent({
111
+ toolCallId,
112
+ toolCallName: "updatePrefs",
113
+ parentMessageId: messageId,
114
+ delta: "{}",
115
+ }),
116
+ );
117
+ agent.emit(runFinishedEvent());
118
+ agent.complete();
119
+
120
+ // Wait for the follow-up run to be triggered by processAgentResult
121
+ await waitFor(() => {
122
+ expect(agent.contextPerRun.length).toBeGreaterThanOrEqual(2);
123
+ });
124
+
125
+ // The follow-up run should see the context updated by the tool handler
126
+ // (spicy: false), not the stale value from before the handler ran (spicy: true).
127
+ const followUpContext = agent.contextPerRun[1];
128
+ expect(followUpContext).toContainEqual(
129
+ expect.objectContaining({ value: '{"spicy":false}' }),
130
+ );
131
+ });
132
+ });