@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.
- package/CHANGELOG.md +117 -116
- 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 +37 -9
- 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,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
|
+
}
|