@copilotkit/react-core 1.55.0-next.9 → 1.55.0
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 +36 -6
- package/dist/{copilotkit-DeOzjPsb.mjs → copilotkit-BY5S1-0P.mjs} +2402 -552
- package/dist/copilotkit-BY5S1-0P.mjs.map +1 -0
- package/dist/{copilotkit-BqcyhQjT.d.mts → copilotkit-BuhSUZHb.d.mts} +228 -17
- package/dist/copilotkit-BuhSUZHb.d.mts.map +1 -0
- package/dist/{copilotkit-BDNjFNmk.cjs → copilotkit-Bz5-ImDl.cjs} +2421 -541
- package/dist/copilotkit-Bz5-ImDl.cjs.map +1 -0
- package/dist/{copilotkit-l-IBF4Xp.d.cts → copilotkit-dwDWYpya.d.cts} +228 -17
- package/dist/copilotkit-dwDWYpya.d.cts.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.umd.js +1400 -238
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +13 -1
- package/dist/v2/index.css +1 -1
- package/dist/v2/index.d.cts +3 -3
- package/dist/v2/index.d.mts +3 -3
- package/dist/v2/index.mjs +3 -2
- package/dist/v2/index.umd.js +2442 -552
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +62 -54
- package/scripts/scope-preflight.mjs +1 -2
- package/src/components/CopilotListeners.tsx +41 -8
- package/src/components/copilot-provider/copilotkit-props.tsx +4 -2
- package/src/components/toast/toast-provider.tsx +269 -194
- package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +86 -22
- package/src/v2/__tests__/utils/test-helpers.tsx +67 -0
- package/src/v2/a2ui/A2UICatalogContext.tsx +79 -0
- package/src/v2/a2ui/A2UIMessageRenderer.tsx +125 -37
- package/src/v2/a2ui/A2UIToolCallRenderer.tsx +290 -0
- package/src/v2/components/CopilotKitInspector.tsx +2 -0
- package/src/v2/components/OpenGenerativeUIRenderer.tsx +598 -0
- package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.tsx +665 -0
- package/src/v2/components/chat/CopilotChat.tsx +193 -50
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +17 -2
- package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +481 -0
- package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +139 -0
- package/src/v2/components/chat/CopilotChatInput.tsx +146 -77
- package/src/v2/components/chat/CopilotChatMessageView.tsx +253 -149
- package/src/v2/components/chat/CopilotChatSuggestionView.tsx +1 -0
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +54 -0
- package/src/v2/components/chat/CopilotChatView.tsx +179 -66
- package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +168 -0
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +63 -2
- package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +544 -1
- package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +268 -0
- package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.tsx +249 -0
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +43 -2
- package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +138 -0
- package/src/v2/components/chat/index.ts +9 -0
- package/src/v2/components/chat/scroll-element-context.ts +13 -0
- package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +1003 -0
- package/src/v2/hooks/__tests__/use-attachments.test.tsx +169 -0
- package/src/v2/hooks/__tests__/use-threads.test.tsx +54 -0
- package/src/v2/hooks/index.ts +5 -0
- package/src/v2/hooks/use-agent.tsx +95 -10
- package/src/v2/hooks/use-attachments.tsx +269 -0
- package/src/v2/hooks/use-frontend-tool.tsx +5 -2
- package/src/v2/hooks/use-render-activity-message.tsx +9 -2
- package/src/v2/hooks/use-threads.tsx +35 -15
- package/src/v2/index.ts +5 -1
- package/src/v2/lib/__tests__/processPartialHtml.test.ts +112 -0
- package/src/v2/lib/__tests__/slots.test.ts +56 -0
- package/src/v2/lib/processPartialHtml.ts +45 -0
- package/src/v2/lib/slots.tsx +42 -1
- package/src/v2/providers/CopilotChatConfigurationProvider.tsx +9 -3
- package/src/v2/providers/CopilotKitProvider.tsx +268 -32
- package/src/v2/providers/SandboxFunctionsContext.ts +10 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.tsx +198 -0
- package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +71 -0
- package/src/v2/providers/index.ts +7 -0
- package/src/v2/styles/globals.css +2 -1
- package/src/v2/types/index.ts +1 -0
- package/src/v2/types/sandbox-function.ts +11 -0
- package/dist/copilotkit-BDNjFNmk.cjs.map +0 -1
- package/dist/copilotkit-BqcyhQjT.d.mts.map +0 -1
- package/dist/copilotkit-DeOzjPsb.mjs.map +0 -1
- package/dist/copilotkit-l-IBF4Xp.d.cts.map +0 -1
- package/src/v2/components/__tests__/license-warning-banner.test.tsx +0 -46
|
@@ -9,12 +9,14 @@ import {
|
|
|
9
9
|
type ReactNode,
|
|
10
10
|
useMemo,
|
|
11
11
|
useEffect,
|
|
12
|
+
useLayoutEffect,
|
|
12
13
|
useReducer,
|
|
13
14
|
useRef,
|
|
14
15
|
useState,
|
|
15
16
|
} from "react";
|
|
16
17
|
import { z } from "zod";
|
|
17
18
|
import { CopilotKitInspector } from "../components/CopilotKitInspector";
|
|
19
|
+
import type { Anchor } from "@copilotkit/web-inspector";
|
|
18
20
|
import { LicenseWarningBanner } from "../components/license-warning-banner";
|
|
19
21
|
import {
|
|
20
22
|
createLicenseContextValue,
|
|
@@ -26,7 +28,16 @@ import {
|
|
|
26
28
|
MCPAppsActivityRenderer,
|
|
27
29
|
MCPAppsActivityType,
|
|
28
30
|
} from "../components/MCPAppsActivityRenderer";
|
|
31
|
+
import {
|
|
32
|
+
OpenGenerativeUIActivityType,
|
|
33
|
+
OpenGenerativeUIContentSchema,
|
|
34
|
+
OpenGenerativeUIActivityRenderer,
|
|
35
|
+
OpenGenerativeUIToolRenderer,
|
|
36
|
+
GenerateSandboxedUiArgsSchema,
|
|
37
|
+
} from "../components/OpenGenerativeUIRenderer";
|
|
29
38
|
import { createA2UIMessageRenderer } from "../a2ui/A2UIMessageRenderer";
|
|
39
|
+
import { A2UIBuiltInToolCallRenderer } from "../a2ui/A2UIToolCallRenderer";
|
|
40
|
+
import { A2UICatalogContext } from "../a2ui/A2UICatalogContext";
|
|
30
41
|
import { viewerTheme } from "@copilotkit/a2ui-renderer";
|
|
31
42
|
import type { Theme as A2UITheme } from "@copilotkit/a2ui-renderer";
|
|
32
43
|
import { CopilotKitCoreReact } from "../lib/react-core";
|
|
@@ -37,10 +48,39 @@ import type {
|
|
|
37
48
|
import type { ReactFrontendTool } from "../types/frontend-tool";
|
|
38
49
|
import type { ReactHumanInTheLoop } from "../types/human-in-the-loop";
|
|
39
50
|
import type { ReactCustomMessageRenderer } from "../types/react-custom-message-renderer";
|
|
51
|
+
import type { SandboxFunction } from "../types/sandbox-function";
|
|
52
|
+
import { SandboxFunctionsContext } from "./SandboxFunctionsContext";
|
|
53
|
+
import { schemaToJsonSchema } from "@copilotkit/shared";
|
|
54
|
+
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
40
55
|
|
|
41
56
|
const HEADER_NAME = "X-CopilotCloud-Public-Api-Key";
|
|
42
57
|
const COPILOT_CLOUD_CHAT_URL = "https://api.cloud.copilotkit.ai/copilotkit/v1";
|
|
43
58
|
|
|
59
|
+
const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follow these design principles inspired by shadcn/ui:
|
|
60
|
+
|
|
61
|
+
- Use a minimal, flat aesthetic. Avoid drop shadows and gradients — rely on subtle borders (1px solid, light gray like #e5e7eb) to define surfaces.
|
|
62
|
+
- Neutral base palette: white backgrounds, zinc/slate gray text (#09090b for headings, #71717a for secondary text). One accent color for interactive elements.
|
|
63
|
+
- Use system font stacks (system-ui, -apple-system, sans-serif) at readable sizes (14px body, 600 weight for headings). Tight line-heights.
|
|
64
|
+
- Small, consistent border-radius (6–8px). Cards and containers use border, not shadow, for separation.
|
|
65
|
+
- Buttons: solid fill for primary (dark bg, white text), outline for secondary (border + transparent bg). Subtle hover state (slight opacity or background shift).
|
|
66
|
+
- Use CSS Grid or Flexbox for layout. Ensure the UI looks good at any width.
|
|
67
|
+
- Minimal transitions (150ms) for hover/focus states only. No decorative animations.
|
|
68
|
+
- Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
|
|
69
|
+
|
|
70
|
+
const GENERATE_SANDBOXED_UI_DESCRIPTION =
|
|
71
|
+
"Generate sandboxed UI. " +
|
|
72
|
+
"IMPORTANT: The generated code runs in a sandboxed iframe WITHOUT same-origin access. " +
|
|
73
|
+
"Do NOT use localStorage, sessionStorage, document.cookie, IndexedDB, or fetch/XMLHttpRequest to same-origin URLs. " +
|
|
74
|
+
"To communicate with the host application, use Websandbox.connection.remote.<functionName>(args) which returns a Promise.\n\n" +
|
|
75
|
+
"You CAN use external libraries from CDNs by including <script> or <link> tags in the HTML <head> (e.g., Chart.js, D3, Three.js, x-data-spreadsheet, etc.). " +
|
|
76
|
+
"CDN resources load normally inside the sandbox.\n\n" +
|
|
77
|
+
"PARAMETER ORDER IS CRITICAL — generate parameters in exactly this order:\n" +
|
|
78
|
+
"1. initialHeight + placeholderMessages (shown to user while generating)\n" +
|
|
79
|
+
"2. css (all styles FIRST — the user sees a placeholder until CSS is complete)\n" +
|
|
80
|
+
"3. html (streams in live — the user watches the UI build as HTML is generated)\n" +
|
|
81
|
+
"4. jsFunctions (reusable helper functions)\n" +
|
|
82
|
+
"5. jsExpressions (applied one-by-one — the user sees each expression take effect)";
|
|
83
|
+
|
|
44
84
|
// Define the context value interface - idiomatic React naming
|
|
45
85
|
export interface CopilotKitContextValue {
|
|
46
86
|
copilotkit: CopilotKitCoreReact;
|
|
@@ -99,6 +139,39 @@ export interface CopilotKitProviderProps {
|
|
|
99
139
|
renderCustomMessages?: ReactCustomMessageRenderer[];
|
|
100
140
|
frontendTools?: ReactFrontendTool[];
|
|
101
141
|
humanInTheLoop?: ReactHumanInTheLoop[];
|
|
142
|
+
/**
|
|
143
|
+
* Configuration for OpenGenerativeUI — sandboxed UI generated by the LLM.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* <CopilotKit
|
|
148
|
+
* runtimeUrl="/api/copilotkit"
|
|
149
|
+
* openGenerativeUI={{
|
|
150
|
+
* sandboxFunctions: [{ name: "addToCart", description: "…", parameters: schema, handler: fn }],
|
|
151
|
+
* }}
|
|
152
|
+
* >
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
openGenerativeUI?: {
|
|
156
|
+
/**
|
|
157
|
+
* Functions made available inside sandboxed iframes.
|
|
158
|
+
* Each function is described to the LLM via agent context and exposed
|
|
159
|
+
* via websandbox's `localApi`.
|
|
160
|
+
*
|
|
161
|
+
* Inside the iframe, call them with:
|
|
162
|
+
* ```js
|
|
163
|
+
* await Websandbox.connection.remote.<functionName>(args)
|
|
164
|
+
* ```
|
|
165
|
+
*/
|
|
166
|
+
sandboxFunctions?: SandboxFunction[];
|
|
167
|
+
/**
|
|
168
|
+
* Design guidelines injected as agent context for the `generateSandboxedUi` tool.
|
|
169
|
+
* Override this to control the visual style of generated UIs.
|
|
170
|
+
*
|
|
171
|
+
* A sensible default is provided if omitted.
|
|
172
|
+
*/
|
|
173
|
+
designSkill?: string;
|
|
174
|
+
};
|
|
102
175
|
showDevConsole?: boolean | "auto";
|
|
103
176
|
/**
|
|
104
177
|
* Error handler called when CopilotKit encounters an error.
|
|
@@ -128,7 +201,39 @@ export interface CopilotKitProviderProps {
|
|
|
128
201
|
* When omitted, the built-in `viewerTheme` from `@copilotkit/a2ui-renderer` is used.
|
|
129
202
|
*/
|
|
130
203
|
theme?: A2UITheme;
|
|
204
|
+
/**
|
|
205
|
+
* Optional component catalog to pass to the A2UI renderer.
|
|
206
|
+
* When omitted, the default basicCatalog is used.
|
|
207
|
+
*/
|
|
208
|
+
catalog?: any;
|
|
209
|
+
/**
|
|
210
|
+
* Optional custom loading component shown while an A2UI surface is generating.
|
|
211
|
+
* When omitted, a default animated skeleton is shown.
|
|
212
|
+
*/
|
|
213
|
+
loadingComponent?: React.ComponentType;
|
|
214
|
+
/**
|
|
215
|
+
* When true (the default), the full component schemas from the catalog are
|
|
216
|
+
* sent as agent context so the agent knows what components and props are
|
|
217
|
+
* available. The A2UI middleware can overwrite this with a server-side
|
|
218
|
+
* schema if configured. Set to false to disable.
|
|
219
|
+
*/
|
|
220
|
+
includeSchema?: boolean;
|
|
131
221
|
};
|
|
222
|
+
/**
|
|
223
|
+
* Default throttle interval (in milliseconds) for `useAgent` re-renders
|
|
224
|
+
* triggered by `OnMessagesChanged` notifications. This value is used as
|
|
225
|
+
* a fallback when neither the `useAgent()` hook nor `<CopilotChat>` /
|
|
226
|
+
* `<CopilotSidebar>` / `<CopilotPopup>` specify an explicit `throttleMs`.
|
|
227
|
+
*
|
|
228
|
+
* @default undefined (components/hooks without an explicit throttleMs will be unthrottled)
|
|
229
|
+
*/
|
|
230
|
+
defaultThrottleMs?: number;
|
|
231
|
+
/**
|
|
232
|
+
* Default anchor corner for the inspector button and window.
|
|
233
|
+
* Only used on first load before the user drags to a custom position.
|
|
234
|
+
* Defaults to `{ horizontal: "right", vertical: "top" }`.
|
|
235
|
+
*/
|
|
236
|
+
inspectorDefaultAnchor?: Anchor;
|
|
132
237
|
}
|
|
133
238
|
|
|
134
239
|
// Small helper to normalize array props to a stable reference and warn
|
|
@@ -171,13 +276,18 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
171
276
|
renderCustomMessages,
|
|
172
277
|
frontendTools,
|
|
173
278
|
humanInTheLoop,
|
|
279
|
+
openGenerativeUI,
|
|
174
280
|
showDevConsole = false,
|
|
175
|
-
useSingleEndpoint
|
|
281
|
+
useSingleEndpoint,
|
|
176
282
|
onError,
|
|
177
283
|
a2ui,
|
|
284
|
+
defaultThrottleMs,
|
|
285
|
+
inspectorDefaultAnchor,
|
|
178
286
|
}) => {
|
|
179
287
|
const [shouldRenderInspector, setShouldRenderInspector] = useState(false);
|
|
180
288
|
const [runtimeA2UIEnabled, setRuntimeA2UIEnabled] = useState(false);
|
|
289
|
+
const [runtimeOpenGenUIEnabled, setRuntimeOpenGenUIEnabled] = useState(false);
|
|
290
|
+
const openGenUIActive = runtimeOpenGenUIEnabled || !!openGenerativeUI;
|
|
181
291
|
const [runtimeLicenseStatus, setRuntimeLicenseStatus] = useState<
|
|
182
292
|
string | undefined
|
|
183
293
|
>(undefined);
|
|
@@ -245,14 +355,26 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
245
355
|
},
|
|
246
356
|
];
|
|
247
357
|
|
|
358
|
+
if (openGenUIActive) {
|
|
359
|
+
renderers.push({
|
|
360
|
+
activityType: OpenGenerativeUIActivityType,
|
|
361
|
+
content: OpenGenerativeUIContentSchema,
|
|
362
|
+
render: OpenGenerativeUIActivityRenderer,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
248
366
|
if (runtimeA2UIEnabled) {
|
|
249
367
|
renderers.unshift(
|
|
250
|
-
createA2UIMessageRenderer({
|
|
368
|
+
createA2UIMessageRenderer({
|
|
369
|
+
theme: a2ui?.theme ?? viewerTheme,
|
|
370
|
+
catalog: a2ui?.catalog,
|
|
371
|
+
loadingComponent: a2ui?.loadingComponent,
|
|
372
|
+
}),
|
|
251
373
|
);
|
|
252
374
|
}
|
|
253
375
|
|
|
254
376
|
return renderers;
|
|
255
|
-
}, [runtimeA2UIEnabled, a2ui]);
|
|
377
|
+
}, [runtimeA2UIEnabled, openGenUIActive, a2ui]);
|
|
256
378
|
|
|
257
379
|
// Combine user-provided activity renderers with built-in ones
|
|
258
380
|
// User-provided renderers take precedence (come first) so they can override built-ins
|
|
@@ -299,6 +421,10 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
299
421
|
humanInTheLoop,
|
|
300
422
|
"humanInTheLoop must be a stable array. If you want to dynamically add or remove human-in-the-loop tools, use `useHumanInTheLoop` instead.",
|
|
301
423
|
);
|
|
424
|
+
const sandboxFunctionsList = useStableArrayProp<SandboxFunction>(
|
|
425
|
+
openGenerativeUI?.sandboxFunctions,
|
|
426
|
+
"openGenerativeUI.sandboxFunctions must be a stable array.",
|
|
427
|
+
);
|
|
302
428
|
|
|
303
429
|
// Note: warnings for array identity changes are handled by useStableArrayProp
|
|
304
430
|
|
|
@@ -344,25 +470,41 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
344
470
|
return { tools: processedTools, renderToolCalls: processedRenderToolCalls };
|
|
345
471
|
}, [humanInTheLoopList]);
|
|
346
472
|
|
|
473
|
+
// Built-in frontend tool for generateSandboxedUi — registered only when the runtime has openGenerativeUI enabled
|
|
474
|
+
const builtInFrontendTools = useMemo<ReactFrontendTool[]>(() => {
|
|
475
|
+
if (!openGenUIActive) return [];
|
|
476
|
+
return [
|
|
477
|
+
{
|
|
478
|
+
name: "generateSandboxedUi",
|
|
479
|
+
description: GENERATE_SANDBOXED_UI_DESCRIPTION,
|
|
480
|
+
parameters: GenerateSandboxedUiArgsSchema,
|
|
481
|
+
handler: async () => "UI generated",
|
|
482
|
+
followUp: true,
|
|
483
|
+
render: OpenGenerativeUIToolRenderer,
|
|
484
|
+
},
|
|
485
|
+
];
|
|
486
|
+
}, [openGenUIActive]);
|
|
487
|
+
|
|
347
488
|
// Combine all tools for CopilotKitCore
|
|
348
489
|
const allTools = useMemo(() => {
|
|
349
490
|
const tools: FrontendTool[] = [];
|
|
350
491
|
|
|
351
|
-
// Add frontend tools
|
|
492
|
+
// Add frontend tools (user-provided + built-in)
|
|
352
493
|
tools.push(...frontendToolsList);
|
|
494
|
+
tools.push(...builtInFrontendTools);
|
|
353
495
|
|
|
354
496
|
// Add processed human-in-the-loop tools
|
|
355
497
|
tools.push(...processedHumanInTheLoopTools.tools);
|
|
356
498
|
|
|
357
499
|
return tools;
|
|
358
|
-
}, [frontendToolsList, processedHumanInTheLoopTools]);
|
|
500
|
+
}, [frontendToolsList, builtInFrontendTools, processedHumanInTheLoopTools]);
|
|
359
501
|
|
|
360
502
|
// Combine all render tool calls
|
|
361
503
|
const allRenderToolCalls = useMemo(() => {
|
|
362
504
|
const combined: ReactToolCallRenderer<unknown>[] = [...renderToolCallsList];
|
|
363
505
|
|
|
364
|
-
// Add render components from frontend tools
|
|
365
|
-
frontendToolsList.forEach((tool) => {
|
|
506
|
+
// Add render components from frontend tools (user-provided + built-in)
|
|
507
|
+
[...frontendToolsList, ...builtInFrontendTools].forEach((tool) => {
|
|
366
508
|
if (tool.render) {
|
|
367
509
|
// For wildcard tools without parameters, default to z.any()
|
|
368
510
|
const args =
|
|
@@ -381,7 +523,12 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
381
523
|
combined.push(...processedHumanInTheLoopTools.renderToolCalls);
|
|
382
524
|
|
|
383
525
|
return combined;
|
|
384
|
-
}, [
|
|
526
|
+
}, [
|
|
527
|
+
renderToolCallsList,
|
|
528
|
+
frontendToolsList,
|
|
529
|
+
builtInFrontendTools,
|
|
530
|
+
processedHumanInTheLoopTools,
|
|
531
|
+
]);
|
|
385
532
|
|
|
386
533
|
// Stable instance: created once for the provider lifetime.
|
|
387
534
|
// Updates are applied via setter effects below rather than recreating the instance.
|
|
@@ -389,7 +536,12 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
389
536
|
if (copilotkitRef.current === null) {
|
|
390
537
|
copilotkitRef.current = new CopilotKitCoreReact({
|
|
391
538
|
runtimeUrl: chatApiEndpoint,
|
|
392
|
-
runtimeTransport:
|
|
539
|
+
runtimeTransport:
|
|
540
|
+
useSingleEndpoint === true
|
|
541
|
+
? "single"
|
|
542
|
+
: useSingleEndpoint === false
|
|
543
|
+
? "rest"
|
|
544
|
+
: "auto",
|
|
393
545
|
headers: mergedHeaders,
|
|
394
546
|
credentials,
|
|
395
547
|
properties,
|
|
@@ -399,14 +551,22 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
399
551
|
renderActivityMessages: allActivityRenderers,
|
|
400
552
|
renderCustomMessages: renderCustomMessagesList,
|
|
401
553
|
});
|
|
554
|
+
// Set initial defaultThrottleMs synchronously so child hooks see the
|
|
555
|
+
// correct value on their first render (before useEffect fires).
|
|
556
|
+
if (defaultThrottleMs !== undefined) {
|
|
557
|
+
copilotkitRef.current.setDefaultThrottleMs(defaultThrottleMs);
|
|
558
|
+
}
|
|
402
559
|
}
|
|
403
560
|
const copilotkit = copilotkitRef.current;
|
|
404
561
|
|
|
405
|
-
// Sync runtime
|
|
562
|
+
// Sync runtime feature flags from the core once runtime info is fetched
|
|
406
563
|
useEffect(() => {
|
|
564
|
+
// Check current value immediately (may already be set before subscription)
|
|
565
|
+
setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
|
|
407
566
|
const subscription = copilotkit.subscribe({
|
|
408
567
|
onRuntimeConnectionStatusChanged: () => {
|
|
409
568
|
setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
|
|
569
|
+
setRuntimeOpenGenUIEnabled(copilotkit.openGenerativeUIEnabled);
|
|
410
570
|
setRuntimeLicenseStatus(copilotkit.licenseStatus);
|
|
411
571
|
},
|
|
412
572
|
});
|
|
@@ -490,7 +650,13 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
490
650
|
|
|
491
651
|
useEffect(() => {
|
|
492
652
|
copilotkit.setRuntimeUrl(chatApiEndpoint);
|
|
493
|
-
copilotkit.setRuntimeTransport(
|
|
653
|
+
copilotkit.setRuntimeTransport(
|
|
654
|
+
useSingleEndpoint === true
|
|
655
|
+
? "single"
|
|
656
|
+
: useSingleEndpoint === false
|
|
657
|
+
? "rest"
|
|
658
|
+
: "auto",
|
|
659
|
+
);
|
|
494
660
|
copilotkit.setHeaders(mergedHeaders);
|
|
495
661
|
copilotkit.setCredentials(credentials);
|
|
496
662
|
copilotkit.setProperties(properties);
|
|
@@ -540,6 +706,64 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
540
706
|
didMountRef.current = true;
|
|
541
707
|
}, []);
|
|
542
708
|
|
|
709
|
+
// Sync defaultThrottleMs to the core instance on prop changes.
|
|
710
|
+
// Initial value is set synchronously during instance creation (below the
|
|
711
|
+
// ref init) so child hooks see the correct value on their first render.
|
|
712
|
+
// This effect handles subsequent updates when the prop changes.
|
|
713
|
+
useEffect(() => {
|
|
714
|
+
if (
|
|
715
|
+
defaultThrottleMs !== undefined &&
|
|
716
|
+
(!Number.isFinite(defaultThrottleMs) || defaultThrottleMs < 0)
|
|
717
|
+
) {
|
|
718
|
+
console.error(
|
|
719
|
+
`CopilotKitProvider: defaultThrottleMs must be a non-negative finite number, got ${defaultThrottleMs}. ` +
|
|
720
|
+
`useAgent hooks without an explicit throttleMs will fall back to unthrottled.`,
|
|
721
|
+
);
|
|
722
|
+
}
|
|
723
|
+
copilotkit.setDefaultThrottleMs(defaultThrottleMs);
|
|
724
|
+
}, [copilotkit, defaultThrottleMs]);
|
|
725
|
+
|
|
726
|
+
// Register design skill as agent context for the generateSandboxedUi tool
|
|
727
|
+
const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
|
|
728
|
+
|
|
729
|
+
useLayoutEffect(() => {
|
|
730
|
+
if (!copilotkit || !openGenUIActive) return;
|
|
731
|
+
|
|
732
|
+
const id = copilotkit.addContext({
|
|
733
|
+
description:
|
|
734
|
+
"Design guidelines for the generateSandboxedUi tool. Follow these when building UI.",
|
|
735
|
+
value: designSkill,
|
|
736
|
+
});
|
|
737
|
+
return () => {
|
|
738
|
+
copilotkit.removeContext(id);
|
|
739
|
+
};
|
|
740
|
+
}, [copilotkit, designSkill, openGenUIActive]);
|
|
741
|
+
|
|
742
|
+
// Register sandbox functions as agent context so the LLM knows how to call them
|
|
743
|
+
const sandboxFunctionsDescriptors = useMemo(() => {
|
|
744
|
+
if (sandboxFunctionsList.length === 0) return null;
|
|
745
|
+
return JSON.stringify(
|
|
746
|
+
sandboxFunctionsList.map((fn) => ({
|
|
747
|
+
name: fn.name,
|
|
748
|
+
description: fn.description,
|
|
749
|
+
parameters: schemaToJsonSchema(fn.parameters, { zodToJsonSchema }),
|
|
750
|
+
})),
|
|
751
|
+
);
|
|
752
|
+
}, [sandboxFunctionsList]);
|
|
753
|
+
|
|
754
|
+
useLayoutEffect(() => {
|
|
755
|
+
if (!copilotkit || !sandboxFunctionsDescriptors || !openGenUIActive) return;
|
|
756
|
+
|
|
757
|
+
const id = copilotkit.addContext({
|
|
758
|
+
description:
|
|
759
|
+
"Sandbox functions available in generated sandboxed UI code. Call via: await Websandbox.connection.remote.<functionName>(args)",
|
|
760
|
+
value: sandboxFunctionsDescriptors,
|
|
761
|
+
});
|
|
762
|
+
return () => {
|
|
763
|
+
copilotkit.removeContext(id);
|
|
764
|
+
};
|
|
765
|
+
}, [copilotkit, sandboxFunctionsDescriptors, openGenUIActive]);
|
|
766
|
+
|
|
543
767
|
const contextValue = useMemo<CopilotKitContextValue>(
|
|
544
768
|
() => ({ copilotkit, executingToolCallIds }),
|
|
545
769
|
[copilotkit, executingToolCallIds],
|
|
@@ -552,27 +776,39 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
|
|
|
552
776
|
);
|
|
553
777
|
|
|
554
778
|
return (
|
|
555
|
-
<
|
|
556
|
-
<
|
|
557
|
-
{
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
779
|
+
<SandboxFunctionsContext.Provider value={sandboxFunctionsList}>
|
|
780
|
+
<CopilotKitContext.Provider value={contextValue}>
|
|
781
|
+
<LicenseContext.Provider value={licenseContextValue}>
|
|
782
|
+
{runtimeA2UIEnabled && <A2UIBuiltInToolCallRenderer />}
|
|
783
|
+
{runtimeA2UIEnabled && (
|
|
784
|
+
<A2UICatalogContext
|
|
785
|
+
catalog={a2ui?.catalog}
|
|
786
|
+
includeSchema={a2ui?.includeSchema}
|
|
787
|
+
/>
|
|
788
|
+
)}
|
|
789
|
+
{children}
|
|
790
|
+
{shouldRenderInspector ? (
|
|
791
|
+
<CopilotKitInspector
|
|
792
|
+
core={copilotkit}
|
|
793
|
+
defaultAnchor={inspectorDefaultAnchor}
|
|
794
|
+
/>
|
|
795
|
+
) : null}
|
|
796
|
+
{/* License warnings — driven by server-reported status */}
|
|
797
|
+
{runtimeLicenseStatus === "none" && !resolvedPublicKey && (
|
|
798
|
+
<LicenseWarningBanner type="no_license" />
|
|
799
|
+
)}
|
|
800
|
+
{runtimeLicenseStatus === "expired" && (
|
|
801
|
+
<LicenseWarningBanner type="expired" />
|
|
802
|
+
)}
|
|
803
|
+
{runtimeLicenseStatus === "invalid" && (
|
|
804
|
+
<LicenseWarningBanner type="invalid" />
|
|
805
|
+
)}
|
|
806
|
+
{runtimeLicenseStatus === "expiring" && (
|
|
807
|
+
<LicenseWarningBanner type="expiring" />
|
|
808
|
+
)}
|
|
809
|
+
</LicenseContext.Provider>
|
|
810
|
+
</CopilotKitContext.Provider>
|
|
811
|
+
</SandboxFunctionsContext.Provider>
|
|
576
812
|
);
|
|
577
813
|
};
|
|
578
814
|
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { createContext, useContext } from "react";
|
|
2
|
+
import type { SandboxFunction } from "../types/sandbox-function";
|
|
3
|
+
|
|
4
|
+
export const SandboxFunctionsContext = createContext<
|
|
5
|
+
readonly SandboxFunction[]
|
|
6
|
+
>([]);
|
|
7
|
+
|
|
8
|
+
export function useSandboxFunctions(): readonly SandboxFunction[] {
|
|
9
|
+
return useContext(SandboxFunctionsContext);
|
|
10
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { renderHook } from "@testing-library/react";
|
|
2
|
+
import React from "react";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { CopilotKitProvider, useCopilotKit } from "../CopilotKitProvider";
|
|
6
|
+
import { useSandboxFunctions } from "../SandboxFunctionsContext";
|
|
7
|
+
import type { SandboxFunction } from "../../types/sandbox-function";
|
|
8
|
+
|
|
9
|
+
describe("CopilotKitProvider — openGenerativeUI.sandboxFunctions", () => {
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
vi.spyOn(console, "error").mockImplementation(() => {});
|
|
12
|
+
vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
vi.restoreAllMocks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const makeSandboxFunction = (
|
|
20
|
+
name: string,
|
|
21
|
+
overrides?: Partial<SandboxFunction>,
|
|
22
|
+
): SandboxFunction => ({
|
|
23
|
+
name,
|
|
24
|
+
description: `${name} description`,
|
|
25
|
+
parameters: z.object({ value: z.string() }),
|
|
26
|
+
handler: vi.fn().mockResolvedValue(undefined),
|
|
27
|
+
...overrides,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
/** Helper: find the sandbox-functions context entry from the context record */
|
|
31
|
+
function findSandboxContext(
|
|
32
|
+
ctx: Record<string, { description: string; value: string }>,
|
|
33
|
+
) {
|
|
34
|
+
return Object.values(ctx).find((c) =>
|
|
35
|
+
c.description.includes("Sandbox functions"),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe("SandboxFunctionsContext", () => {
|
|
40
|
+
it("provides sandbox functions to children via context", () => {
|
|
41
|
+
const fns = [makeSandboxFunction("myFn")];
|
|
42
|
+
|
|
43
|
+
const { result } = renderHook(() => useSandboxFunctions(), {
|
|
44
|
+
wrapper: ({ children }) => (
|
|
45
|
+
<CopilotKitProvider openGenerativeUI={{ sandboxFunctions: fns }}>
|
|
46
|
+
{children}
|
|
47
|
+
</CopilotKitProvider>
|
|
48
|
+
),
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
expect(result.current).toHaveLength(1);
|
|
52
|
+
expect(result.current[0].name).toBe("myFn");
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("provides empty array when openGenerativeUI is not set", () => {
|
|
56
|
+
const { result } = renderHook(() => useSandboxFunctions(), {
|
|
57
|
+
wrapper: ({ children }) => (
|
|
58
|
+
<CopilotKitProvider>{children}</CopilotKitProvider>
|
|
59
|
+
),
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
expect(result.current).toHaveLength(0);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("provides empty array when sandboxFunctions is not set", () => {
|
|
66
|
+
const { result } = renderHook(() => useSandboxFunctions(), {
|
|
67
|
+
wrapper: ({ children }) => (
|
|
68
|
+
<CopilotKitProvider openGenerativeUI={{}}>
|
|
69
|
+
{children}
|
|
70
|
+
</CopilotKitProvider>
|
|
71
|
+
),
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
expect(result.current).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
describe("agent context registration", () => {
|
|
79
|
+
it("registers agent context when sandbox functions are provided", () => {
|
|
80
|
+
const fns = [makeSandboxFunction("addToCart")];
|
|
81
|
+
|
|
82
|
+
const { result } = renderHook(() => useCopilotKit(), {
|
|
83
|
+
wrapper: ({ children }) => (
|
|
84
|
+
<CopilotKitProvider openGenerativeUI={{ sandboxFunctions: fns }}>
|
|
85
|
+
{children}
|
|
86
|
+
</CopilotKitProvider>
|
|
87
|
+
),
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const sandboxContext = findSandboxContext(
|
|
91
|
+
result.current.copilotkit.context,
|
|
92
|
+
);
|
|
93
|
+
expect(sandboxContext).toBeDefined();
|
|
94
|
+
|
|
95
|
+
const parsed = JSON.parse(sandboxContext!.value);
|
|
96
|
+
expect(parsed).toHaveLength(1);
|
|
97
|
+
expect(parsed[0].name).toBe("addToCart");
|
|
98
|
+
expect(parsed[0].description).toBe("addToCart description");
|
|
99
|
+
expect(parsed[0].parameters).toBeDefined();
|
|
100
|
+
expect(parsed[0].parameters.type).toBe("object");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("does not register agent context when sandbox functions are empty", () => {
|
|
104
|
+
const { result } = renderHook(() => useCopilotKit(), {
|
|
105
|
+
wrapper: ({ children }) => (
|
|
106
|
+
<CopilotKitProvider openGenerativeUI={{ sandboxFunctions: [] }}>
|
|
107
|
+
{children}
|
|
108
|
+
</CopilotKitProvider>
|
|
109
|
+
),
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const sandboxContext = findSandboxContext(
|
|
113
|
+
result.current.copilotkit.context,
|
|
114
|
+
);
|
|
115
|
+
expect(sandboxContext).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("does not register agent context when openGenerativeUI is omitted", () => {
|
|
119
|
+
const { result } = renderHook(() => useCopilotKit(), {
|
|
120
|
+
wrapper: ({ children }) => (
|
|
121
|
+
<CopilotKitProvider>{children}</CopilotKitProvider>
|
|
122
|
+
),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const sandboxContext = findSandboxContext(
|
|
126
|
+
result.current.copilotkit.context,
|
|
127
|
+
);
|
|
128
|
+
expect(sandboxContext).toBeUndefined();
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("includes multiple functions in agent context", () => {
|
|
132
|
+
const fns = [makeSandboxFunction("fnA"), makeSandboxFunction("fnB")];
|
|
133
|
+
|
|
134
|
+
const { result } = renderHook(() => useCopilotKit(), {
|
|
135
|
+
wrapper: ({ children }) => (
|
|
136
|
+
<CopilotKitProvider openGenerativeUI={{ sandboxFunctions: fns }}>
|
|
137
|
+
{children}
|
|
138
|
+
</CopilotKitProvider>
|
|
139
|
+
),
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const sandboxContext = findSandboxContext(
|
|
143
|
+
result.current.copilotkit.context,
|
|
144
|
+
);
|
|
145
|
+
const parsed = JSON.parse(sandboxContext!.value);
|
|
146
|
+
expect(parsed).toHaveLength(2);
|
|
147
|
+
expect(parsed.map((f: any) => f.name)).toEqual(["fnA", "fnB"]);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it("converts parameters to JSON Schema in agent context", () => {
|
|
151
|
+
const fns = [
|
|
152
|
+
makeSandboxFunction("myFn", {
|
|
153
|
+
parameters: z.object({
|
|
154
|
+
itemId: z.string(),
|
|
155
|
+
quantity: z.number(),
|
|
156
|
+
}),
|
|
157
|
+
}),
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
const { result } = renderHook(() => useCopilotKit(), {
|
|
161
|
+
wrapper: ({ children }) => (
|
|
162
|
+
<CopilotKitProvider openGenerativeUI={{ sandboxFunctions: fns }}>
|
|
163
|
+
{children}
|
|
164
|
+
</CopilotKitProvider>
|
|
165
|
+
),
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const sandboxContext = findSandboxContext(
|
|
169
|
+
result.current.copilotkit.context,
|
|
170
|
+
);
|
|
171
|
+
const parsed = JSON.parse(sandboxContext!.value);
|
|
172
|
+
const params = parsed[0].parameters;
|
|
173
|
+
|
|
174
|
+
expect(params.type).toBe("object");
|
|
175
|
+
expect(params.properties.itemId).toEqual({ type: "string" });
|
|
176
|
+
expect(params.properties.quantity).toEqual({ type: "number" });
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("removes agent context on unmount", () => {
|
|
180
|
+
const fns = [makeSandboxFunction("myFn")];
|
|
181
|
+
|
|
182
|
+
const { result, unmount } = renderHook(() => useCopilotKit(), {
|
|
183
|
+
wrapper: ({ children }) => (
|
|
184
|
+
<CopilotKitProvider openGenerativeUI={{ sandboxFunctions: fns }}>
|
|
185
|
+
{children}
|
|
186
|
+
</CopilotKitProvider>
|
|
187
|
+
),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const copilotkit = result.current.copilotkit;
|
|
191
|
+
expect(findSandboxContext(copilotkit.context)).toBeDefined();
|
|
192
|
+
|
|
193
|
+
unmount();
|
|
194
|
+
|
|
195
|
+
expect(findSandboxContext(copilotkit.context)).toBeUndefined();
|
|
196
|
+
});
|
|
197
|
+
});
|
|
198
|
+
});
|