@copilotkit/react-core 1.71.0 → 1.71.1
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/dist/{copilotkit-B4Jb1QEy.cjs → copilotkit-BkVEkUS0.cjs} +5 -2
- package/dist/copilotkit-BkVEkUS0.cjs.map +1 -0
- package/dist/{copilotkit-snbJkMqQ.mjs → copilotkit-D5BTo0YG.mjs} +5 -2
- package/dist/copilotkit-D5BTo0YG.mjs.map +1 -0
- package/dist/index.cjs +1 -1
- package/dist/index.mjs +1 -1
- package/dist/index.umd.js +4 -1
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/headless.cjs +4 -1
- package/dist/v2/headless.cjs.map +1 -1
- package/dist/v2/headless.mjs +4 -1
- package/dist/v2/headless.mjs.map +1 -1
- package/dist/v2/index.cjs +1 -1
- package/dist/v2/index.mjs +1 -1
- package/dist/v2/index.umd.js +4 -1
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +8 -9
- package/dist/copilotkit-B4Jb1QEy.cjs.map +0 -1
- package/dist/copilotkit-snbJkMqQ.mjs.map +0 -1
- package/skills/react-core/SKILL.md +0 -110
- package/skills/react-core/references/agent-access.md +0 -398
- package/skills/react-core/references/attachments.md +0 -311
- package/skills/react-core/references/capabilities.md +0 -138
- package/skills/react-core/references/chat-components.md +0 -246
- package/skills/react-core/references/client-side-tools.md +0 -358
- package/skills/react-core/references/custom-message-renderers.md +0 -223
- package/skills/react-core/references/debug-mode.md +0 -140
- package/skills/react-core/references/human-in-the-loop.md +0 -312
- package/skills/react-core/references/provider-setup.md +0 -358
- package/skills/react-core/references/rendering-activity-messages.md +0 -201
- package/skills/react-core/references/rendering-tool-calls.md +0 -319
- package/skills/react-core/references/suggestions.md +0 -211
- package/skills/react-core/references/switching-agents-recipes.md +0 -161
- package/skills/react-core/references/switching-agents.md +0 -240
- package/skills/react-core/references/threads.md +0 -289
|
@@ -1,312 +0,0 @@
|
|
|
1
|
-
# CopilotKit Human-in-the-Loop (React)
|
|
2
|
-
|
|
3
|
-
This skill builds on `copilotkit/provider-setup`, `copilotkit/client-side-tools`,
|
|
4
|
-
and `copilotkit/rendering-tool-calls`.
|
|
5
|
-
|
|
6
|
-
`useHumanInTheLoop` is `useFrontendTool` minus the `handler` plus a
|
|
7
|
-
`render` that receives a `respond` function. The hook synthesizes a
|
|
8
|
-
Promise-based handler — the Promise resolves when `respond(result)` is
|
|
9
|
-
called. No `respond` call → infinite hang.
|
|
10
|
-
|
|
11
|
-
Status is camelCase: `"inProgress" | "executing" | "complete"`. `respond`
|
|
12
|
-
is `undefined` except during `"executing"`.
|
|
13
|
-
|
|
14
|
-
## UI-kit detection rule
|
|
15
|
-
|
|
16
|
-
Before writing the approval UI, check the consumer's `package.json` for a
|
|
17
|
-
UI kit (shadcn `AlertDialog`, MUI `Dialog`, Chakra `Modal`, Ant `Modal`,
|
|
18
|
-
Mantine `Modal`) and reuse it. Don't hand-roll an overlay.
|
|
19
|
-
|
|
20
|
-
## Setup
|
|
21
|
-
|
|
22
|
-
```tsx
|
|
23
|
-
"use client";
|
|
24
|
-
import { useHumanInTheLoop } from "@copilotkit/react-core/v2";
|
|
25
|
-
import { z } from "zod";
|
|
26
|
-
import {
|
|
27
|
-
AlertDialog,
|
|
28
|
-
AlertDialogAction,
|
|
29
|
-
AlertDialogCancel,
|
|
30
|
-
AlertDialogContent,
|
|
31
|
-
AlertDialogDescription,
|
|
32
|
-
AlertDialogFooter,
|
|
33
|
-
AlertDialogHeader,
|
|
34
|
-
AlertDialogTitle,
|
|
35
|
-
} from "@/components/ui/alert-dialog";
|
|
36
|
-
|
|
37
|
-
export function DeleteConfirmHITL() {
|
|
38
|
-
useHumanInTheLoop({
|
|
39
|
-
name: "confirmDelete",
|
|
40
|
-
description: "Confirm a destructive delete with the user",
|
|
41
|
-
parameters: z.object({ id: z.string(), label: z.string() }),
|
|
42
|
-
render: ({ status, args, respond }) => (
|
|
43
|
-
<AlertDialog open>
|
|
44
|
-
<AlertDialogContent>
|
|
45
|
-
<AlertDialogHeader>
|
|
46
|
-
<AlertDialogTitle>Delete {args.label}?</AlertDialogTitle>
|
|
47
|
-
<AlertDialogDescription>
|
|
48
|
-
This action cannot be undone.
|
|
49
|
-
</AlertDialogDescription>
|
|
50
|
-
</AlertDialogHeader>
|
|
51
|
-
<AlertDialogFooter>
|
|
52
|
-
<AlertDialogCancel
|
|
53
|
-
disabled={status !== "executing"}
|
|
54
|
-
onClick={() => respond?.("denied")}
|
|
55
|
-
>
|
|
56
|
-
Cancel
|
|
57
|
-
</AlertDialogCancel>
|
|
58
|
-
<AlertDialogAction
|
|
59
|
-
disabled={status !== "executing"}
|
|
60
|
-
onClick={() => respond?.("approved")}
|
|
61
|
-
>
|
|
62
|
-
Delete
|
|
63
|
-
</AlertDialogAction>
|
|
64
|
-
</AlertDialogFooter>
|
|
65
|
-
</AlertDialogContent>
|
|
66
|
-
</AlertDialog>
|
|
67
|
-
),
|
|
68
|
-
});
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
```
|
|
72
|
-
|
|
73
|
-
## Core Patterns
|
|
74
|
-
|
|
75
|
-
### Always call `respond` in every branch
|
|
76
|
-
|
|
77
|
-
```tsx
|
|
78
|
-
render: ({ status, args, respond }) => {
|
|
79
|
-
if (status !== "executing" || !respond) {
|
|
80
|
-
return <div>Awaiting decision…</div>;
|
|
81
|
-
}
|
|
82
|
-
return (
|
|
83
|
-
<div>
|
|
84
|
-
<button onClick={() => respond("approved")}>Approve</button>
|
|
85
|
-
<button onClick={() => respond("denied")}>Reject</button>
|
|
86
|
-
<button onClick={() => respond({ action: "skip", reason: "timeout" })}>
|
|
87
|
-
Skip
|
|
88
|
-
</button>
|
|
89
|
-
</div>
|
|
90
|
-
);
|
|
91
|
-
};
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Abort the run on unmount so threads unlock
|
|
95
|
-
|
|
96
|
-
```tsx
|
|
97
|
-
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core/v2";
|
|
98
|
-
import { useEffect, useRef } from "react";
|
|
99
|
-
|
|
100
|
-
function HITLHost() {
|
|
101
|
-
const { agent } = useAgent({
|
|
102
|
-
agentId: "default",
|
|
103
|
-
updates: [UseAgentUpdate.OnRunStatusChanged],
|
|
104
|
-
});
|
|
105
|
-
// Track isRunning in a ref so the unmount cleanup reads the latest value
|
|
106
|
-
// without re-firing on every transition.
|
|
107
|
-
const runningRef = useRef(false);
|
|
108
|
-
useEffect(() => {
|
|
109
|
-
runningRef.current = agent.isRunning;
|
|
110
|
-
}, [agent.isRunning]);
|
|
111
|
-
|
|
112
|
-
useEffect(() => {
|
|
113
|
-
return () => {
|
|
114
|
-
if (runningRef.current) agent.abortRun();
|
|
115
|
-
};
|
|
116
|
-
}, [agent]);
|
|
117
|
-
|
|
118
|
-
return <DeleteConfirmHITL />;
|
|
119
|
-
}
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
`useAgent` returns `{ agent }` only — run status lives on `agent.isRunning`.
|
|
123
|
-
Depending the cleanup effect directly on `agent.isRunning` would fire the
|
|
124
|
-
cleanup on every status flip (not just unmount), aborting active runs.
|
|
125
|
-
The ref pattern captures the latest value while the cleanup runs only
|
|
126
|
-
when the host component truly unmounts.
|
|
127
|
-
|
|
128
|
-
### Collect structured user input mid-run
|
|
129
|
-
|
|
130
|
-
```tsx
|
|
131
|
-
useHumanInTheLoop({
|
|
132
|
-
name: "askUserForPriority",
|
|
133
|
-
parameters: z.object({ taskId: z.string() }),
|
|
134
|
-
render: ({ status, args, respond }) => {
|
|
135
|
-
if (status !== "executing" || !respond) return <div>Waiting…</div>;
|
|
136
|
-
return (
|
|
137
|
-
<div>
|
|
138
|
-
{["low", "medium", "high"].map((p) => (
|
|
139
|
-
<button
|
|
140
|
-
key={p}
|
|
141
|
-
onClick={() => respond({ taskId: args.taskId, priority: p })}
|
|
142
|
-
>
|
|
143
|
-
{p}
|
|
144
|
-
</button>
|
|
145
|
-
))}
|
|
146
|
-
</div>
|
|
147
|
-
);
|
|
148
|
-
},
|
|
149
|
-
});
|
|
150
|
-
```
|
|
151
|
-
|
|
152
|
-
## Common Mistakes
|
|
153
|
-
|
|
154
|
-
### CRITICAL — Never calling `respond()`
|
|
155
|
-
|
|
156
|
-
Wrong:
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
useHumanInTheLoop({
|
|
160
|
-
name: "confirmDelete",
|
|
161
|
-
parameters: z.object({ id: z.string() }),
|
|
162
|
-
render: ({ args, status, respond }) => (
|
|
163
|
-
<div>
|
|
164
|
-
<p>Delete {args.id}?</p>
|
|
165
|
-
<button>OK</button>
|
|
166
|
-
</div>
|
|
167
|
-
),
|
|
168
|
-
});
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
Correct:
|
|
172
|
-
|
|
173
|
-
```tsx
|
|
174
|
-
useHumanInTheLoop({
|
|
175
|
-
name: "confirmDelete",
|
|
176
|
-
parameters: z.object({ id: z.string() }),
|
|
177
|
-
render: ({ args, status, respond }) => (
|
|
178
|
-
<div>
|
|
179
|
-
<p>Delete {args.id}?</p>
|
|
180
|
-
<button onClick={() => respond?.("approved")}>OK</button>
|
|
181
|
-
<button onClick={() => respond?.("denied")}>Cancel</button>
|
|
182
|
-
</div>
|
|
183
|
-
),
|
|
184
|
-
});
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
The synthesized handler returns a Promise that resolves only when `respond`
|
|
188
|
-
is called. Never calling it (including reject / cancel paths) hangs the
|
|
189
|
-
run indefinitely and leaves the thread locked on the server.
|
|
190
|
-
|
|
191
|
-
Source: `packages/react-core/src/v2/hooks/use-human-in-the-loop.tsx:13-26`
|
|
192
|
-
|
|
193
|
-
### CRITICAL — Writing a custom overlay when the app has a Dialog primitive
|
|
194
|
-
|
|
195
|
-
Wrong:
|
|
196
|
-
|
|
197
|
-
```tsx
|
|
198
|
-
render: ({ respond }) => (
|
|
199
|
-
<div style={{ position: "fixed", inset: 0, background: "rgba(0,0,0,0.5)" }}>
|
|
200
|
-
…
|
|
201
|
-
</div>
|
|
202
|
-
);
|
|
203
|
-
```
|
|
204
|
-
|
|
205
|
-
Correct:
|
|
206
|
-
|
|
207
|
-
```tsx
|
|
208
|
-
import {
|
|
209
|
-
AlertDialog,
|
|
210
|
-
AlertDialogContent,
|
|
211
|
-
AlertDialogAction,
|
|
212
|
-
} from "@/components/ui/alert-dialog";
|
|
213
|
-
|
|
214
|
-
render: ({ respond }) => (
|
|
215
|
-
<AlertDialog open>
|
|
216
|
-
<AlertDialogContent>
|
|
217
|
-
…
|
|
218
|
-
<AlertDialogAction onClick={() => respond?.("approved")}>
|
|
219
|
-
OK
|
|
220
|
-
</AlertDialogAction>
|
|
221
|
-
</AlertDialogContent>
|
|
222
|
-
</AlertDialog>
|
|
223
|
-
);
|
|
224
|
-
```
|
|
225
|
-
|
|
226
|
-
Check `package.json` for shadcn / MUI / Chakra / Ant / Mantine before
|
|
227
|
-
writing an overlay. Their dialog primitives handle focus trapping,
|
|
228
|
-
escape-to-close, and accessibility — raw JSX skips all of that.
|
|
229
|
-
|
|
230
|
-
Source: maintainer interview (Phase 2c)
|
|
231
|
-
|
|
232
|
-
### HIGH — Calling `respond` during `inProgress` or `complete`
|
|
233
|
-
|
|
234
|
-
Wrong:
|
|
235
|
-
|
|
236
|
-
```tsx
|
|
237
|
-
render: ({ status, respond }) => (
|
|
238
|
-
<button onClick={() => (respond as any)("yes")}>Yes</button>
|
|
239
|
-
);
|
|
240
|
-
```
|
|
241
|
-
|
|
242
|
-
Correct:
|
|
243
|
-
|
|
244
|
-
```tsx
|
|
245
|
-
render: ({ status, respond }) =>
|
|
246
|
-
status === "executing" && respond ? (
|
|
247
|
-
<button onClick={() => respond("yes")}>Yes</button>
|
|
248
|
-
) : (
|
|
249
|
-
<p>Waiting…</p>
|
|
250
|
-
);
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
`respond` is `undefined` outside `status === "executing"`. Widening it to
|
|
254
|
-
`any` silently no-ops — the button click appears to work, but nothing
|
|
255
|
-
resolves the Promise.
|
|
256
|
-
|
|
257
|
-
Source: `packages/react-core/src/v2/types/human-in-the-loop.ts:8-32`
|
|
258
|
-
|
|
259
|
-
### HIGH — Unmounting the render mid-executing
|
|
260
|
-
|
|
261
|
-
Wrong:
|
|
262
|
-
|
|
263
|
-
```tsx
|
|
264
|
-
// User clicks away to a different route while the agent is waiting on respond()
|
|
265
|
-
```
|
|
266
|
-
|
|
267
|
-
Correct:
|
|
268
|
-
|
|
269
|
-
```tsx
|
|
270
|
-
// Keep the HITL prompt at a layout level that persists across route changes, OR abort on unmount:
|
|
271
|
-
const { agent } = useAgent({
|
|
272
|
-
agentId: "default",
|
|
273
|
-
updates: [UseAgentUpdate.OnRunStatusChanged],
|
|
274
|
-
});
|
|
275
|
-
const runningRef = useRef(false);
|
|
276
|
-
useEffect(() => {
|
|
277
|
-
runningRef.current = agent.isRunning;
|
|
278
|
-
}, [agent.isRunning]);
|
|
279
|
-
useEffect(
|
|
280
|
-
() => () => {
|
|
281
|
-
if (runningRef.current) agent.abortRun();
|
|
282
|
-
},
|
|
283
|
-
[agent],
|
|
284
|
-
);
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
`useHumanInTheLoop` removes its renderer on unmount (unlike
|
|
288
|
-
`useFrontendTool`, which keeps renderers for history). If the renderer
|
|
289
|
-
unmounts mid-`executing`, the pending Promise is abandoned and the run
|
|
290
|
-
hangs. Either lift the HITL UI to a layout-level component, or abort the
|
|
291
|
-
run on unmount.
|
|
292
|
-
|
|
293
|
-
Source: `packages/react-core/src/v2/hooks/use-human-in-the-loop.tsx:76-80`
|
|
294
|
-
|
|
295
|
-
### MEDIUM — Using hyphenated `"in-progress"` status
|
|
296
|
-
|
|
297
|
-
Wrong:
|
|
298
|
-
|
|
299
|
-
```tsx
|
|
300
|
-
render: ({ status }) => (status === "in-progress" ? <Spinner /> : <Form />);
|
|
301
|
-
```
|
|
302
|
-
|
|
303
|
-
Correct:
|
|
304
|
-
|
|
305
|
-
```tsx
|
|
306
|
-
render: ({ status }) => (status === "inProgress" ? <Spinner /> : <Form />);
|
|
307
|
-
```
|
|
308
|
-
|
|
309
|
-
Same camelCase rule as `rendering-tool-calls`: the discriminated union
|
|
310
|
-
only matches `"inProgress" | "executing" | "complete"`.
|
|
311
|
-
|
|
312
|
-
Source: `packages/react-core/src/v2/types/human-in-the-loop.ts:8-32`
|
|
@@ -1,358 +0,0 @@
|
|
|
1
|
-
# CopilotKit Provider Setup (React)
|
|
2
|
-
|
|
3
|
-
Mount the `CopilotKit` provider (from `@copilotkit/react-core/v2`) once
|
|
4
|
-
near the root of the React tree. Every CopilotKit hook (`useAgent`,
|
|
5
|
-
`useFrontendTool`, `useRenderTool`, etc.) and every chat component
|
|
6
|
-
(`CopilotChat`, `CopilotPopup`, `CopilotSidebar`) must be rendered inside
|
|
7
|
-
this provider.
|
|
8
|
-
|
|
9
|
-
> **Which provider component?** Use `CopilotKit` imported from `@copilotkit/react-core/v2`. It is the compatibility bridge across v1 and v2 and a superset of `CopilotKitProvider`, which is also exported from `/v2` and is a perfectly good choice if you do not need the v1 bridge. Do **not** use `CopilotKit` from the package root (`@copilotkit/react-core`) — that is the legacy v1 entry point and will not work with v2 hooks or components.
|
|
10
|
-
|
|
11
|
-
## Transport
|
|
12
|
-
|
|
13
|
-
You do not normally configure the transport. Both providers leave
|
|
14
|
-
`useSingleEndpoint` unset by default, and the client then negotiates: it probes
|
|
15
|
-
`GET {runtimeUrl}/info` and falls back to the single-route `POST` envelope. That
|
|
16
|
-
works against a multi-route handler (the default for every `createCopilot*`
|
|
17
|
-
handler) and a single-route one alike.
|
|
18
|
-
|
|
19
|
-
Set the prop only to pin one mode deliberately:
|
|
20
|
-
|
|
21
|
-
| `useSingleEndpoint` | Transport | Requires |
|
|
22
|
-
| ------------------------- | ---------------------------- | --------------------------------------------- |
|
|
23
|
-
| omitted (**recommended**) | negotiated | either handler mode |
|
|
24
|
-
| `{true}` | single-route `POST` envelope | a handler mounted with `mode: "single-route"` |
|
|
25
|
-
| `{false}` | multi-route REST routes | a handler in the default multi-route mode |
|
|
26
|
-
|
|
27
|
-
Pinning the wrong one is the classic first-run failure: a single-route envelope
|
|
28
|
-
sent to a multi-route runtime matches no route, so the runtime 404s while
|
|
29
|
-
`GET /info` still returns 200 and the app looks connected. If you see that, drop
|
|
30
|
-
the prop rather than guessing the other value.
|
|
31
|
-
|
|
32
|
-
All v2 imports use the `@copilotkit/react-core/v2` subpath. Imports from the
|
|
33
|
-
package root are v1 and will not work with v2 hooks or components.
|
|
34
|
-
|
|
35
|
-
## Setup
|
|
36
|
-
|
|
37
|
-
### Next.js App Router (and any RSC-based framework)
|
|
38
|
-
|
|
39
|
-
`@copilotkit/react-core/v2` is marked `"use client"`. You must mount the
|
|
40
|
-
provider from a client component, not a server component. The cleanest
|
|
41
|
-
pattern is a dedicated client-only `providers.tsx`.
|
|
42
|
-
|
|
43
|
-
```tsx
|
|
44
|
-
// app/providers.tsx
|
|
45
|
-
"use client";
|
|
46
|
-
|
|
47
|
-
import { CopilotKit } from "@copilotkit/react-core/v2";
|
|
48
|
-
import "@copilotkit/react-core/v2/styles.css";
|
|
49
|
-
|
|
50
|
-
export function Providers({ children }: { children: React.ReactNode }) {
|
|
51
|
-
return (
|
|
52
|
-
<CopilotKit
|
|
53
|
-
runtimeUrl="/api/copilotkit"
|
|
54
|
-
credentials="include"
|
|
55
|
-
onError={({ code, error, context }) => {
|
|
56
|
-
console.error("[copilotkit]", code, error, context);
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
{children}
|
|
60
|
-
</CopilotKit>
|
|
61
|
-
);
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
For auth headers that change over the session (rotating bearer tokens,
|
|
66
|
-
refreshed cookies), see the "Stable headers for rotating auth tokens"
|
|
67
|
-
pattern below. Avoid putting a `useMemo(() => ({ Authorization: ... }),
|
|
68
|
-
[])` on the provider — an empty deps array captures the token at mount
|
|
69
|
-
and never refreshes.
|
|
70
|
-
|
|
71
|
-
```tsx
|
|
72
|
-
// app/layout.tsx — server component
|
|
73
|
-
import { Providers } from "./providers";
|
|
74
|
-
|
|
75
|
-
export default function RootLayout({
|
|
76
|
-
children,
|
|
77
|
-
}: {
|
|
78
|
-
children: React.ReactNode;
|
|
79
|
-
}) {
|
|
80
|
-
return (
|
|
81
|
-
<html lang="en">
|
|
82
|
-
<body>
|
|
83
|
-
<Providers>{children}</Providers>
|
|
84
|
-
</body>
|
|
85
|
-
</html>
|
|
86
|
-
);
|
|
87
|
-
}
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
### Vite / React Router v7 / SPA
|
|
91
|
-
|
|
92
|
-
```tsx
|
|
93
|
-
import { CopilotKit } from "@copilotkit/react-core/v2";
|
|
94
|
-
import "@copilotkit/react-core/v2/styles.css";
|
|
95
|
-
|
|
96
|
-
export function App({ children }: { children: React.ReactNode }) {
|
|
97
|
-
return <CopilotKit runtimeUrl="/api/copilotkit">{children}</CopilotKit>;
|
|
98
|
-
}
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
### SPA with CopilotKit Intelligence (no self-hosted runtime)
|
|
102
|
-
|
|
103
|
-
```tsx
|
|
104
|
-
<CopilotKit publicLicenseKey="ck_pub_..." />
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
`publicLicenseKey` is the canonical prop for running CopilotKit from a
|
|
108
|
-
pure client bundle. `publicApiKey` is a deprecated alias that resolves to
|
|
109
|
-
the same value — accept it in old code, but always write
|
|
110
|
-
`publicLicenseKey` in new code.
|
|
111
|
-
|
|
112
|
-
## Core Patterns
|
|
113
|
-
|
|
114
|
-
### Stable headers for rotating auth tokens
|
|
115
|
-
|
|
116
|
-
For tokens that change during the session, use the imperative setter instead
|
|
117
|
-
of re-rendering the provider with a new `headers` prop.
|
|
118
|
-
|
|
119
|
-
```tsx
|
|
120
|
-
"use client";
|
|
121
|
-
import { useCopilotKit } from "@copilotkit/react-core/v2";
|
|
122
|
-
import { useEffect } from "react";
|
|
123
|
-
|
|
124
|
-
export function AuthTokenSync({ token }: { token: string | null }) {
|
|
125
|
-
const { copilotkit } = useCopilotKit();
|
|
126
|
-
useEffect(() => {
|
|
127
|
-
// setHeaders is an overwrite, not a merge — spread the current headers so
|
|
128
|
-
// entries set elsewhere (e.g. the public license key) survive. A `null`
|
|
129
|
-
// value clears that header, so logging out removes `Authorization` instead
|
|
130
|
-
// of sending an empty one.
|
|
131
|
-
copilotkit.setHeaders({
|
|
132
|
-
...copilotkit.headers,
|
|
133
|
-
Authorization: token ? `Bearer ${token}` : null,
|
|
134
|
-
});
|
|
135
|
-
}, [copilotkit, token]);
|
|
136
|
-
return null;
|
|
137
|
-
}
|
|
138
|
-
```
|
|
139
|
-
|
|
140
|
-
`setHeaders` accepts `null`/`undefined` values and drops those keys, so passing
|
|
141
|
-
`Authorization: null` is the supported way to clear a header. Setting it to an
|
|
142
|
-
empty string would keep the header present with a blank value.
|
|
143
|
-
|
|
144
|
-
Do not set the same header through both the `headers` prop and imperative
|
|
145
|
-
`setHeaders`. Whenever any provider prop changes, the provider calls
|
|
146
|
-
`setHeaders` with its prop-derived headers — a full overwrite that drops every
|
|
147
|
-
imperatively-set header, not just keys the prop also defines. Keep rotating
|
|
148
|
-
values like the auth token out of the `headers` prop and manage them only
|
|
149
|
-
through `setHeaders` (as above).
|
|
150
|
-
|
|
151
|
-
### Global error handler
|
|
152
|
-
|
|
153
|
-
`onError` fires for every `CopilotKitCoreErrorCode` emitted by core. Keeps
|
|
154
|
-
UI from getting stuck in "connecting..." when the runtime URL is wrong or
|
|
155
|
-
CORS is misconfigured.
|
|
156
|
-
|
|
157
|
-
```tsx
|
|
158
|
-
<CopilotKit
|
|
159
|
-
runtimeUrl="/api/copilotkit"
|
|
160
|
-
onError={({ code, error, context }) => {
|
|
161
|
-
telemetry.capture({ code, message: error.message, context });
|
|
162
|
-
}}
|
|
163
|
-
/>
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
### Sharing app properties with every run
|
|
167
|
-
|
|
168
|
-
`properties` flows to the runtime on each agent run — useful for tenant IDs,
|
|
169
|
-
feature flags, or anything the server needs.
|
|
170
|
-
|
|
171
|
-
```tsx
|
|
172
|
-
const properties = useMemo(
|
|
173
|
-
() => ({ tenantId: user.tenantId, locale: user.locale }),
|
|
174
|
-
[user.tenantId, user.locale],
|
|
175
|
-
);
|
|
176
|
-
|
|
177
|
-
<CopilotKit runtimeUrl="/api/copilotkit" properties={properties} />;
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
## Common Mistakes
|
|
181
|
-
|
|
182
|
-
### CRITICAL — Mounting the provider from a Server Component
|
|
183
|
-
|
|
184
|
-
Wrong:
|
|
185
|
-
|
|
186
|
-
```tsx
|
|
187
|
-
// app/page.tsx (server component — no "use client")
|
|
188
|
-
import { CopilotKit } from "@copilotkit/react-core/v2";
|
|
189
|
-
|
|
190
|
-
export default function Page() {
|
|
191
|
-
return <CopilotKit runtimeUrl="/api/copilotkit">...</CopilotKit>;
|
|
192
|
-
}
|
|
193
|
-
```
|
|
194
|
-
|
|
195
|
-
Correct:
|
|
196
|
-
|
|
197
|
-
```tsx
|
|
198
|
-
// app/providers.tsx
|
|
199
|
-
"use client";
|
|
200
|
-
import { CopilotKit } from "@copilotkit/react-core/v2";
|
|
201
|
-
|
|
202
|
-
export function Providers({ children }: { children: React.ReactNode }) {
|
|
203
|
-
return <CopilotKit runtimeUrl="/api/copilotkit">{children}</CopilotKit>;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// app/layout.tsx imports <Providers>.
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
`@copilotkit/react-core/v2` begins with `"use client"`. Importing it from a
|
|
210
|
-
server component silently strips interactivity — the provider renders but
|
|
211
|
-
none of the hooks wire up.
|
|
212
|
-
|
|
213
|
-
Source: `packages/react-core/src/v2/index.ts:1`
|
|
214
|
-
|
|
215
|
-
### CRITICAL — Using `agents__unsafe_dev_only` or `selfManagedAgents` in production
|
|
216
|
-
|
|
217
|
-
Wrong:
|
|
218
|
-
|
|
219
|
-
```tsx
|
|
220
|
-
<CopilotKit
|
|
221
|
-
agents__unsafe_dev_only={{
|
|
222
|
-
default: new BuiltInAgent({ apiKey: process.env.OPENAI_KEY! }),
|
|
223
|
-
}}
|
|
224
|
-
/>
|
|
225
|
-
// or the alias (same thing):
|
|
226
|
-
<CopilotKit
|
|
227
|
-
selfManagedAgents={{ default: new BuiltInAgent({ apiKey: "..." }) }}
|
|
228
|
-
/>
|
|
229
|
-
```
|
|
230
|
-
|
|
231
|
-
Correct:
|
|
232
|
-
|
|
233
|
-
```tsx
|
|
234
|
-
// Route through a runtime that keeps secrets server-side:
|
|
235
|
-
<CopilotKit runtimeUrl="/api/copilotkit" />
|
|
236
|
-
|
|
237
|
-
// Or for a pure SPA, use CopilotKit Intelligence:
|
|
238
|
-
<CopilotKit publicLicenseKey="ck_pub_..." />
|
|
239
|
-
```
|
|
240
|
-
|
|
241
|
-
Both props are aliases for the same dev-only mechanism and ship any embedded
|
|
242
|
-
credentials to the browser bundle. Never use either for production agents.
|
|
243
|
-
|
|
244
|
-
Source: `packages/react-core/src/v2/providers/CopilotKitProvider.tsx:136-138,393`
|
|
245
|
-
|
|
246
|
-
### HIGH — Inline object props rebuilt every render
|
|
247
|
-
|
|
248
|
-
Wrong:
|
|
249
|
-
|
|
250
|
-
```tsx
|
|
251
|
-
<CopilotKit
|
|
252
|
-
runtimeUrl="/api/copilotkit"
|
|
253
|
-
headers={{ Authorization: `Bearer ${token}` }}
|
|
254
|
-
properties={{ tenantId: user.tenantId }}
|
|
255
|
-
/>
|
|
256
|
-
```
|
|
257
|
-
|
|
258
|
-
Correct:
|
|
259
|
-
|
|
260
|
-
```tsx
|
|
261
|
-
const headers = useMemo(() => ({ Authorization: `Bearer ${token}` }), [token]);
|
|
262
|
-
const properties = useMemo(
|
|
263
|
-
() => ({ tenantId: user.tenantId }),
|
|
264
|
-
[user.tenantId],
|
|
265
|
-
);
|
|
266
|
-
|
|
267
|
-
<CopilotKit
|
|
268
|
-
runtimeUrl="/api/copilotkit"
|
|
269
|
-
headers={headers}
|
|
270
|
-
properties={properties}
|
|
271
|
-
/>;
|
|
272
|
-
```
|
|
273
|
-
|
|
274
|
-
New object identity on every render causes the provider to diff-churn
|
|
275
|
-
internal state and may thrash tool/renderer registration. `useStableArrayProp`
|
|
276
|
-
also logs a `console.error` when array-prop shape changes without
|
|
277
|
-
memoization.
|
|
278
|
-
|
|
279
|
-
Source: `packages/react-core/src/v2/providers/CopilotKitProvider.tsx:324-340,399-410`
|
|
280
|
-
|
|
281
|
-
### HIGH — Missing `onError` leaves users stuck in "connecting..."
|
|
282
|
-
|
|
283
|
-
Wrong:
|
|
284
|
-
|
|
285
|
-
```tsx
|
|
286
|
-
<CopilotKit runtimeUrl="/api/copilotkit" />
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
Correct:
|
|
290
|
-
|
|
291
|
-
```tsx
|
|
292
|
-
<CopilotKit
|
|
293
|
-
runtimeUrl="/api/copilotkit"
|
|
294
|
-
onError={({ code, error, context }) => {
|
|
295
|
-
telemetry.capture({ code, error, context });
|
|
296
|
-
}}
|
|
297
|
-
/>
|
|
298
|
-
```
|
|
299
|
-
|
|
300
|
-
Without `onError`, connection failures (bad runtime URL, CORS, network) keep
|
|
301
|
-
the provider in a provisional state with `ProxiedCopilotRuntimeAgent`
|
|
302
|
-
instances that never resolve. The chat UI keeps showing "connecting..."
|
|
303
|
-
forever and users never see the actual error.
|
|
304
|
-
|
|
305
|
-
Source: `packages/react-core/src/v2/providers/CopilotKitProvider.tsx:638-660`
|
|
306
|
-
|
|
307
|
-
### HIGH — Writing `publicApiKey` in new code
|
|
308
|
-
|
|
309
|
-
Wrong:
|
|
310
|
-
|
|
311
|
-
```tsx
|
|
312
|
-
<CopilotKit publicApiKey="ck_pub_..." />
|
|
313
|
-
```
|
|
314
|
-
|
|
315
|
-
Correct:
|
|
316
|
-
|
|
317
|
-
```tsx
|
|
318
|
-
<CopilotKit publicLicenseKey="ck_pub_..." />
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
`publicApiKey` still works as a deprecated alias, but `publicLicenseKey`
|
|
322
|
-
is the canonical name. The `CopilotKit` provider resolves
|
|
323
|
-
`publicLicenseKey || publicApiKey`. Always write the canonical form in
|
|
324
|
-
new code.
|
|
325
|
-
|
|
326
|
-
Source: `packages/react-core/src/v1-deprecated/components/copilot-provider/copilotkit.tsx:172`
|
|
327
|
-
|
|
328
|
-
### MEDIUM — Putting the provider below a layout that uses CopilotKit
|
|
329
|
-
|
|
330
|
-
Wrong:
|
|
331
|
-
|
|
332
|
-
```tsx
|
|
333
|
-
<html>
|
|
334
|
-
<body>
|
|
335
|
-
<Header>{/* Header uses useFrontendTool internally */}</Header>
|
|
336
|
-
<CopilotKit>{children}</CopilotKit>
|
|
337
|
-
</body>
|
|
338
|
-
</html>
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
Correct:
|
|
342
|
-
|
|
343
|
-
```tsx
|
|
344
|
-
<html>
|
|
345
|
-
<body>
|
|
346
|
-
<CopilotKit>
|
|
347
|
-
<Header />
|
|
348
|
-
{children}
|
|
349
|
-
</CopilotKit>
|
|
350
|
-
</body>
|
|
351
|
-
</html>
|
|
352
|
-
```
|
|
353
|
-
|
|
354
|
-
Any component that calls `useCopilotKit`, `useFrontendTool`, `useAgent`, or
|
|
355
|
-
any other CopilotKit hook must be a descendant of the `CopilotKit`
|
|
356
|
-
provider. Placing the provider beside or below a consumer throws at mount.
|
|
357
|
-
|
|
358
|
-
Source: `packages/react-core/src/v2/providers/CopilotKitProvider.tsx` (context)
|