@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.
- package/CHANGELOG.md +121 -106
- package/dist/copilotkit-B3Mb1yVE.cjs +7975 -0
- package/dist/copilotkit-B3Mb1yVE.cjs.map +1 -0
- package/dist/copilotkit-DBzgOMby.d.cts +2182 -0
- package/dist/copilotkit-DBzgOMby.d.cts.map +1 -0
- package/dist/copilotkit-DNYSFuz5.mjs +7562 -0
- package/dist/copilotkit-DNYSFuz5.mjs.map +1 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts +2182 -0
- package/dist/copilotkit-Dy5w3qEV.d.mts.map +1 -0
- package/dist/index.cjs +27 -28
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -3
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +3 -3
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +4 -5
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1941 -35
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +77 -7
- package/dist/v2/index.css +1 -2
- package/dist/v2/index.d.cts +6 -4
- package/dist/v2/index.d.mts +6 -4
- package/dist/v2/index.mjs +7 -4
- package/dist/v2/index.umd.js +5725 -24
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +35 -7
- package/scripts/scope-preflight.mjs +101 -0
- package/src/components/CopilotListeners.tsx +2 -6
- package/src/components/copilot-provider/copilot-messages.tsx +1 -1
- package/src/components/copilot-provider/copilotkit-props.tsx +1 -1
- package/src/components/copilot-provider/copilotkit.tsx +4 -4
- package/src/context/copilot-messages-context.tsx +1 -1
- package/src/hooks/__tests__/use-coagent-config.test.ts +2 -2
- package/src/hooks/__tests__/use-coagent-state-render.e2e.test.tsx +2 -2
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +3 -7
- package/src/hooks/__tests__/use-frontend-tool-available.test.tsx +1 -1
- package/src/hooks/__tests__/use-frontend-tool-remount.e2e.test.tsx +4 -4
- package/src/hooks/use-agent-nodename.ts +1 -1
- package/src/hooks/use-coagent-state-render-bridge.tsx +1 -4
- package/src/hooks/use-coagent.ts +1 -1
- package/src/hooks/use-configure-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat-suggestions.tsx +2 -2
- package/src/hooks/use-copilot-chat_internal.ts +2 -2
- package/src/hooks/use-copilot-readable.ts +1 -1
- package/src/hooks/use-frontend-tool.ts +2 -2
- package/src/hooks/use-human-in-the-loop.ts +2 -2
- package/src/hooks/use-langgraph-interrupt.ts +2 -5
- package/src/hooks/use-lazy-tool-renderer.tsx +1 -1
- package/src/hooks/use-render-tool-call.ts +1 -1
- package/src/lib/copilot-task.ts +1 -1
- package/src/setupTests.ts +18 -14
- package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +176 -0
- package/src/v2/__tests__/globalSetup.ts +14 -0
- package/src/v2/__tests__/setup.ts +93 -0
- package/src/v2/__tests__/utils/test-helpers.tsx +470 -0
- package/src/v2/a2ui/A2UIMessageRenderer.tsx +206 -0
- package/src/v2/components/CopilotKitInspector.tsx +50 -0
- package/src/v2/components/MCPAppsActivityRenderer.tsx +785 -0
- package/src/v2/components/WildcardToolCallRender.tsx +86 -0
- package/src/v2/components/__tests__/license-warning-banner.test.tsx +46 -0
- package/src/v2/components/chat/CopilotChat.tsx +431 -0
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +375 -0
- package/src/v2/components/chat/CopilotChatAudioRecorder.tsx +350 -0
- package/src/v2/components/chat/CopilotChatInput.tsx +1302 -0
- package/src/v2/components/chat/CopilotChatMessageView.tsx +556 -0
- package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +252 -0
- package/src/v2/components/chat/CopilotChatSuggestionPill.tsx +59 -0
- package/src/v2/components/chat/CopilotChatSuggestionView.tsx +133 -0
- package/src/v2/components/chat/CopilotChatToggleButton.tsx +171 -0
- package/src/v2/components/chat/CopilotChatToolCallsView.tsx +40 -0
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +388 -0
- package/src/v2/components/chat/CopilotChatView.tsx +598 -0
- package/src/v2/components/chat/CopilotModalHeader.tsx +129 -0
- package/src/v2/components/chat/CopilotPopup.tsx +81 -0
- package/src/v2/components/chat/CopilotPopupView.tsx +317 -0
- package/src/v2/components/chat/CopilotSidebar.tsx +76 -0
- package/src/v2/components/chat/CopilotSidebarView.tsx +255 -0
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +1113 -0
- package/src/v2/components/chat/__tests__/CopilotChat.onError.test.tsx +73 -0
- package/src/v2/components/chat/__tests__/CopilotChat.slots.e2e.test.tsx +432 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +150 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.slots.e2e.test.tsx +624 -0
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +702 -0
- package/src/v2/components/chat/__tests__/CopilotChatCssClasses.test.tsx +107 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.slots.e2e.test.tsx +929 -0
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +986 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.slots.e2e.test.tsx +1004 -0
- package/src/v2/components/chat/__tests__/CopilotChatMessageView.test.tsx +169 -0
- package/src/v2/components/chat/__tests__/CopilotChatSuggestionView.slots.e2e.test.tsx +530 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +782 -0
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2413 -0
- package/src/v2/components/chat/__tests__/CopilotChatUserMessage.slots.e2e.test.tsx +621 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.onClick.e2e.test.tsx +853 -0
- package/src/v2/components/chat/__tests__/CopilotChatView.slots.e2e.test.tsx +1050 -0
- package/src/v2/components/chat/__tests__/CopilotModalHeader.slots.e2e.test.tsx +484 -0
- package/src/v2/components/chat/__tests__/CopilotPopupView.slots.e2e.test.tsx +612 -0
- package/src/v2/components/chat/__tests__/CopilotSidebarView.slots.e2e.test.tsx +502 -0
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1011 -0
- package/src/v2/components/chat/__tests__/setup.ts +1 -0
- package/src/v2/components/chat/index.ts +79 -0
- package/src/v2/components/index.ts +7 -0
- package/src/v2/components/license-warning-banner.tsx +198 -0
- package/src/v2/components/ui/button.tsx +123 -0
- package/src/v2/components/ui/dropdown-menu.tsx +258 -0
- package/src/v2/components/ui/tooltip.tsx +60 -0
- package/src/v2/hooks/__tests__/standard-schema-types.test.tsx +152 -0
- package/src/v2/hooks/__tests__/standard-schema.test.tsx +282 -0
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +132 -0
- package/src/v2/hooks/__tests__/use-agent-context.test.tsx +401 -0
- package/src/v2/hooks/__tests__/use-agent-error-state.test.tsx +44 -0
- package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +205 -0
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +148 -0
- package/src/v2/hooks/__tests__/use-component.test.tsx +123 -0
- package/src/v2/hooks/__tests__/use-configure-suggestions.e2e.test.tsx +696 -0
- package/src/v2/hooks/__tests__/use-default-render-tool.test.tsx +153 -0
- package/src/v2/hooks/__tests__/use-frontend-tool-available.test.tsx +167 -0
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +2129 -0
- package/src/v2/hooks/__tests__/use-human-in-the-loop.e2e.test.tsx +1261 -0
- package/src/v2/hooks/__tests__/use-interrupt.test.tsx +397 -0
- package/src/v2/hooks/__tests__/use-katex-styles.test.tsx +56 -0
- package/src/v2/hooks/__tests__/use-keyboard-height.test.tsx +192 -0
- package/src/v2/hooks/__tests__/use-render-tool.test.tsx +259 -0
- package/src/v2/hooks/__tests__/use-suggestions.e2e.test.tsx +524 -0
- package/src/v2/hooks/__tests__/use-threads.test.tsx +433 -0
- package/src/v2/hooks/__tests__/zod-regression.test.tsx +311 -0
- package/src/v2/hooks/index.ts +18 -0
- package/src/v2/hooks/use-agent-context.tsx +45 -0
- package/src/v2/hooks/use-agent.tsx +155 -0
- package/src/v2/hooks/use-component.tsx +89 -0
- package/src/v2/hooks/use-configure-suggestions.tsx +187 -0
- package/src/v2/hooks/use-default-render-tool.tsx +254 -0
- package/src/v2/hooks/use-frontend-tool.tsx +43 -0
- package/src/v2/hooks/use-human-in-the-loop.tsx +81 -0
- package/src/v2/hooks/use-interrupt.tsx +305 -0
- package/src/v2/hooks/use-keyboard-height.tsx +67 -0
- package/src/v2/hooks/use-render-activity-message.tsx +73 -0
- package/src/v2/hooks/use-render-custom-messages.tsx +93 -0
- package/src/v2/hooks/use-render-tool-call.tsx +175 -0
- package/src/v2/hooks/use-render-tool.tsx +181 -0
- package/src/v2/hooks/use-suggestions.tsx +91 -0
- package/src/v2/hooks/use-threads.tsx +256 -0
- package/src/v2/hooks/useKatexStyles.ts +27 -0
- package/src/v2/index.css +1 -1
- package/src/v2/index.ts +18 -2
- package/src/v2/lib/__tests__/completePartialMarkdown.test.ts +495 -0
- package/src/v2/lib/__tests__/renderSlot.test.tsx +588 -0
- package/src/v2/lib/react-core.ts +156 -0
- package/src/v2/lib/slots.tsx +143 -0
- package/src/v2/lib/transcription-client.ts +184 -0
- package/src/v2/lib/utils.ts +8 -0
- package/src/v2/providers/CopilotChatConfigurationProvider.tsx +162 -0
- package/src/v2/providers/CopilotKitProvider.tsx +600 -0
- package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +546 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.license.test.tsx +101 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.onError.test.tsx +69 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.renderCustomMessages.e2e.test.tsx +881 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.stability.test.tsx +740 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +642 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.wildcard.test.tsx +294 -0
- package/src/v2/providers/index.ts +14 -0
- package/src/v2/styles/globals.css +230 -0
- package/src/v2/types/__tests__/defineToolCallRenderer.test.tsx +525 -0
- package/src/v2/types/defineToolCallRenderer.ts +65 -0
- package/src/v2/types/frontend-tool.ts +8 -0
- package/src/v2/types/human-in-the-loop.ts +33 -0
- package/src/v2/types/index.ts +7 -0
- package/src/v2/types/interrupt.ts +15 -0
- package/src/v2/types/react-activity-message-renderer.ts +27 -0
- package/src/v2/types/react-custom-message-renderer.ts +17 -0
- package/src/v2/types/react-tool-call-renderer.ts +32 -0
- package/tsdown.config.ts +34 -10
- package/vitest.config.mjs +4 -3
- package/LICENSE +0 -21
- package/dist/copilotkit-BRPQ2sqS.d.cts +0 -670
- package/dist/copilotkit-BRPQ2sqS.d.cts.map +0 -1
- package/dist/copilotkit-C94ayZbs.cjs +0 -2161
- package/dist/copilotkit-C94ayZbs.cjs.map +0 -1
- package/dist/copilotkit-CwZMFmSK.d.mts +0 -670
- package/dist/copilotkit-CwZMFmSK.d.mts.map +0 -1
- package/dist/copilotkit-Yh_Ld_FX.mjs +0 -2031
- package/dist/copilotkit-Yh_Ld_FX.mjs.map +0 -1
- 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
|
+
});
|