@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,211 +0,0 @@
|
|
|
1
|
-
# CopilotKit Suggestions (React)
|
|
2
|
-
|
|
3
|
-
This skill builds on `copilotkit/provider-setup` and
|
|
4
|
-
`copilotkit/chat-components`. Suggestions render via
|
|
5
|
-
`CopilotChatSuggestionView` which `<CopilotChat>` mounts automatically.
|
|
6
|
-
|
|
7
|
-
Two sides:
|
|
8
|
-
|
|
9
|
-
- `useConfigureSuggestions(config, deps?)` — register dynamic (LLM) or
|
|
10
|
-
static suggestions.
|
|
11
|
-
- `useSuggestions({ agentId })` — read current suggestions + trigger
|
|
12
|
-
reload / clear.
|
|
13
|
-
|
|
14
|
-
## Setup
|
|
15
|
-
|
|
16
|
-
### Dynamic suggestions (LLM-generated)
|
|
17
|
-
|
|
18
|
-
```tsx
|
|
19
|
-
"use client";
|
|
20
|
-
import { useConfigureSuggestions } from "@copilotkit/react-core/v2";
|
|
21
|
-
import { useMemo } from "react";
|
|
22
|
-
|
|
23
|
-
export function DynamicSuggestionsHost({ page }: { page: string }) {
|
|
24
|
-
const instructions = useMemo(
|
|
25
|
-
() => `Suggest 3 follow-up questions about the "${page}" page.`,
|
|
26
|
-
[page],
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
useConfigureSuggestions(
|
|
30
|
-
{
|
|
31
|
-
instructions,
|
|
32
|
-
minSuggestions: 2,
|
|
33
|
-
maxSuggestions: 4,
|
|
34
|
-
available: "always",
|
|
35
|
-
},
|
|
36
|
-
[page],
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
### Static suggestions
|
|
44
|
-
|
|
45
|
-
```tsx
|
|
46
|
-
"use client";
|
|
47
|
-
import { useConfigureSuggestions } from "@copilotkit/react-core/v2";
|
|
48
|
-
|
|
49
|
-
export function StaticStarters() {
|
|
50
|
-
useConfigureSuggestions({
|
|
51
|
-
suggestions: [
|
|
52
|
-
{ title: "Summarize this page", message: "Summarize the current page." },
|
|
53
|
-
{ title: "Explain like I'm 5", message: "Explain this in simple terms." },
|
|
54
|
-
],
|
|
55
|
-
available: "before-first-message",
|
|
56
|
-
});
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
## Core Patterns
|
|
62
|
-
|
|
63
|
-
### Read and refresh suggestions from any component
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
import { useSuggestions } from "@copilotkit/react-core/v2";
|
|
67
|
-
|
|
68
|
-
export function RefreshButton() {
|
|
69
|
-
const { suggestions, reloadSuggestions, clearSuggestions, isLoading } =
|
|
70
|
-
useSuggestions({ agentId: "default" });
|
|
71
|
-
return (
|
|
72
|
-
<div>
|
|
73
|
-
<button onClick={reloadSuggestions} disabled={isLoading}>
|
|
74
|
-
{isLoading ? "Loading…" : "Refresh"}
|
|
75
|
-
</button>
|
|
76
|
-
<button onClick={clearSuggestions}>Clear</button>
|
|
77
|
-
<span>{suggestions.length} suggestions</span>
|
|
78
|
-
</div>
|
|
79
|
-
);
|
|
80
|
-
}
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
### Feature-flag the suggestions config
|
|
84
|
-
|
|
85
|
-
```tsx
|
|
86
|
-
const enabled = useFeatureFlag("suggestions");
|
|
87
|
-
useConfigureSuggestions(
|
|
88
|
-
enabled ? { instructions: "Suggest 3 follow-ups" } : null,
|
|
89
|
-
);
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
### Agent-scoped dynamic suggestions
|
|
93
|
-
|
|
94
|
-
```tsx
|
|
95
|
-
useConfigureSuggestions({
|
|
96
|
-
instructions: "Suggest follow-ups for the research agent.",
|
|
97
|
-
consumerAgentId: "research",
|
|
98
|
-
});
|
|
99
|
-
```
|
|
100
|
-
|
|
101
|
-
## Common Mistakes
|
|
102
|
-
|
|
103
|
-
### MEDIUM — Using `available: "disabled"` expecting reload to still fire
|
|
104
|
-
|
|
105
|
-
Wrong:
|
|
106
|
-
|
|
107
|
-
```tsx
|
|
108
|
-
useConfigureSuggestions({
|
|
109
|
-
instructions: "Suggest 3 follow-ups",
|
|
110
|
-
available: "disabled",
|
|
111
|
-
});
|
|
112
|
-
// Then calling reloadSuggestions() — no-op
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
Correct:
|
|
116
|
-
|
|
117
|
-
```tsx
|
|
118
|
-
const enabled = useFeatureFlag("suggestions");
|
|
119
|
-
useConfigureSuggestions(
|
|
120
|
-
enabled ? { instructions: "Suggest 3 follow-ups" } : null,
|
|
121
|
-
);
|
|
122
|
-
```
|
|
123
|
-
|
|
124
|
-
`available: "disabled"` is normalized to a `null` config — the same as
|
|
125
|
-
passing `null`/`undefined`. Reloads become no-ops. Pass `null` (or gate on
|
|
126
|
-
a condition) when you want to fully disable suggestions.
|
|
127
|
-
|
|
128
|
-
Source: `packages/react-core/src/v2/hooks/use-configure-suggestions.tsx:59-62`
|
|
129
|
-
|
|
130
|
-
### MEDIUM — Inline config object without `deps`
|
|
131
|
-
|
|
132
|
-
Wrong:
|
|
133
|
-
|
|
134
|
-
```tsx
|
|
135
|
-
useConfigureSuggestions({ instructions: `about ${currentPage}` });
|
|
136
|
-
// Config re-serialized every render — reload may or may not fire depending on cache equality
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
Correct:
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
useConfigureSuggestions({ instructions: `about ${currentPage}` }, [
|
|
143
|
-
currentPage,
|
|
144
|
-
]);
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
`useConfigureSuggestions` uses a serialized-config cache keyed off the
|
|
148
|
-
JSON-stringified value. Without `deps`, React invariance + inline objects
|
|
149
|
-
produce unstable identities that thrash the cache. Always pass `deps` that
|
|
150
|
-
cover the values interpolated into the config.
|
|
151
|
-
|
|
152
|
-
Source: `packages/react-core/src/v2/hooks/use-configure-suggestions.tsx:166-171`
|
|
153
|
-
|
|
154
|
-
### MEDIUM — Calling `reloadSuggestions` mid-run
|
|
155
|
-
|
|
156
|
-
Wrong:
|
|
157
|
-
|
|
158
|
-
```tsx
|
|
159
|
-
<button onClick={() => reloadSuggestions()}>Refresh</button>
|
|
160
|
-
// Fires during agent streaming → competes with the running agent
|
|
161
|
-
```
|
|
162
|
-
|
|
163
|
-
Correct:
|
|
164
|
-
|
|
165
|
-
```tsx
|
|
166
|
-
import { useAgent, UseAgentUpdate } from "@copilotkit/react-core/v2";
|
|
167
|
-
|
|
168
|
-
const { agent } = useAgent({
|
|
169
|
-
agentId: "default",
|
|
170
|
-
updates: [UseAgentUpdate.OnRunStatusChanged],
|
|
171
|
-
});
|
|
172
|
-
const isRunning = agent.isRunning;
|
|
173
|
-
<button
|
|
174
|
-
disabled={isRunning}
|
|
175
|
-
onClick={() => {
|
|
176
|
-
if (!isRunning) reloadSuggestions();
|
|
177
|
-
}}
|
|
178
|
-
>
|
|
179
|
-
Refresh
|
|
180
|
-
</button>;
|
|
181
|
-
```
|
|
182
|
-
|
|
183
|
-
The internal auto-reload skips when `isRunning`, but the user-triggered
|
|
184
|
-
`reloadSuggestions()` does not guard itself. Guard the caller, or the
|
|
185
|
-
suggestion generation races the active agent run.
|
|
186
|
-
|
|
187
|
-
Source: `packages/react-core/src/v2/hooks/use-configure-suggestions.tsx:121-124`
|
|
188
|
-
|
|
189
|
-
### MEDIUM — Expecting `maxSuggestions` above 3 without setting it
|
|
190
|
-
|
|
191
|
-
Wrong:
|
|
192
|
-
|
|
193
|
-
```tsx
|
|
194
|
-
useConfigureSuggestions({ instructions: "…" });
|
|
195
|
-
// Then surprised the UI only shows 3 pills even when the LLM returned 8
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Correct:
|
|
199
|
-
|
|
200
|
-
```tsx
|
|
201
|
-
useConfigureSuggestions({
|
|
202
|
-
instructions: "…",
|
|
203
|
-
minSuggestions: 1,
|
|
204
|
-
maxSuggestions: 6,
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
`minSuggestions` and `maxSuggestions` default to 1 and 3 respectively. Set
|
|
209
|
-
them explicitly when you want a different count.
|
|
210
|
-
|
|
211
|
-
Source: `packages/core/src/types.ts` (DynamicSuggestionsConfig defaults)
|
|
@@ -1,161 +0,0 @@
|
|
|
1
|
-
# Agent Switcher Recipes
|
|
2
|
-
|
|
3
|
-
Three copy-paste patterns for multi-agent UIs. All subscribe to
|
|
4
|
-
`copilotkit.subscribe({ onAgentsChanged })` for live agent discovery — there
|
|
5
|
-
is no `useAgents()` hook.
|
|
6
|
-
|
|
7
|
-
## Recipe 1 — Dropdown switcher
|
|
8
|
-
|
|
9
|
-
```tsx
|
|
10
|
-
"use client";
|
|
11
|
-
import { CopilotChat, useCopilotKit } from "@copilotkit/react-core/v2";
|
|
12
|
-
import { useEffect, useState } from "react";
|
|
13
|
-
|
|
14
|
-
export function DropdownAgentSwitcher() {
|
|
15
|
-
const { copilotkit } = useCopilotKit();
|
|
16
|
-
const [agentIds, setAgentIds] = useState<string[]>(() =>
|
|
17
|
-
Object.keys(copilotkit.agents ?? {}),
|
|
18
|
-
);
|
|
19
|
-
const [activeAgent, setActiveAgent] = useState<string>(
|
|
20
|
-
() => Object.keys(copilotkit.agents ?? {})[0] ?? "default",
|
|
21
|
-
);
|
|
22
|
-
|
|
23
|
-
useEffect(() => {
|
|
24
|
-
const sub = copilotkit.subscribe({
|
|
25
|
-
onAgentsChanged: ({ agents }) => {
|
|
26
|
-
setAgentIds(Object.keys(agents ?? {}));
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
return () => sub.unsubscribe();
|
|
30
|
-
}, [copilotkit]);
|
|
31
|
-
|
|
32
|
-
return (
|
|
33
|
-
<div className="flex flex-col gap-3">
|
|
34
|
-
<select
|
|
35
|
-
value={activeAgent}
|
|
36
|
-
onChange={(e) => setActiveAgent(e.target.value)}
|
|
37
|
-
>
|
|
38
|
-
{agentIds.map((id) => (
|
|
39
|
-
<option key={id} value={id}>
|
|
40
|
-
{id}
|
|
41
|
-
</option>
|
|
42
|
-
))}
|
|
43
|
-
</select>
|
|
44
|
-
<CopilotChat key={activeAgent} agentId={activeAgent} />
|
|
45
|
-
</div>
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
```
|
|
49
|
-
|
|
50
|
-
## Recipe 2 — Tabs switcher
|
|
51
|
-
|
|
52
|
-
```tsx
|
|
53
|
-
"use client";
|
|
54
|
-
import { CopilotChat, useCopilotKit } from "@copilotkit/react-core/v2";
|
|
55
|
-
import { useEffect, useRef, useState } from "react";
|
|
56
|
-
|
|
57
|
-
export function TabsAgentSwitcher() {
|
|
58
|
-
const { copilotkit } = useCopilotKit();
|
|
59
|
-
const [agentIds, setAgentIds] = useState<string[]>(() =>
|
|
60
|
-
Object.keys(copilotkit.agents ?? {}),
|
|
61
|
-
);
|
|
62
|
-
const [activeAgent, setActiveAgent] = useState<string>(
|
|
63
|
-
() => agentIds[0] ?? "default",
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
// Hold activeAgent in a ref so the subscribe effect only re-binds when
|
|
67
|
-
// `copilotkit` changes. Depending on `activeAgent` would tear down and
|
|
68
|
-
// re-establish the subscription on every tab click.
|
|
69
|
-
const activeAgentRef = useRef(activeAgent);
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
activeAgentRef.current = activeAgent;
|
|
72
|
-
}, [activeAgent]);
|
|
73
|
-
|
|
74
|
-
useEffect(() => {
|
|
75
|
-
const sub = copilotkit.subscribe({
|
|
76
|
-
onAgentsChanged: ({ agents }) => {
|
|
77
|
-
const ids = Object.keys(agents ?? {});
|
|
78
|
-
setAgentIds(ids);
|
|
79
|
-
if (!ids.includes(activeAgentRef.current) && ids.length > 0) {
|
|
80
|
-
setActiveAgent(ids[0]);
|
|
81
|
-
}
|
|
82
|
-
},
|
|
83
|
-
});
|
|
84
|
-
return () => sub.unsubscribe();
|
|
85
|
-
}, [copilotkit]);
|
|
86
|
-
|
|
87
|
-
return (
|
|
88
|
-
<div>
|
|
89
|
-
<div role="tablist" className="flex gap-2 border-b">
|
|
90
|
-
{agentIds.map((id) => (
|
|
91
|
-
<button
|
|
92
|
-
key={id}
|
|
93
|
-
role="tab"
|
|
94
|
-
aria-selected={id === activeAgent}
|
|
95
|
-
onClick={() => setActiveAgent(id)}
|
|
96
|
-
>
|
|
97
|
-
{id}
|
|
98
|
-
</button>
|
|
99
|
-
))}
|
|
100
|
-
</div>
|
|
101
|
-
<CopilotChat key={activeAgent} agentId={activeAgent} />
|
|
102
|
-
</div>
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Recipe 3 — Keyboard shortcut switcher
|
|
108
|
-
|
|
109
|
-
Cycles through agents with `Cmd/Ctrl + Shift + A`.
|
|
110
|
-
|
|
111
|
-
```tsx
|
|
112
|
-
"use client";
|
|
113
|
-
import { CopilotChat, useCopilotKit } from "@copilotkit/react-core/v2";
|
|
114
|
-
import { useEffect, useState } from "react";
|
|
115
|
-
|
|
116
|
-
export function KeyboardAgentSwitcher() {
|
|
117
|
-
const { copilotkit } = useCopilotKit();
|
|
118
|
-
const [agentIds, setAgentIds] = useState<string[]>(() =>
|
|
119
|
-
Object.keys(copilotkit.agents ?? {}),
|
|
120
|
-
);
|
|
121
|
-
const [activeAgent, setActiveAgent] = useState<string>(
|
|
122
|
-
() => agentIds[0] ?? "default",
|
|
123
|
-
);
|
|
124
|
-
|
|
125
|
-
useEffect(() => {
|
|
126
|
-
const sub = copilotkit.subscribe({
|
|
127
|
-
onAgentsChanged: ({ agents }) => setAgentIds(Object.keys(agents ?? {})),
|
|
128
|
-
});
|
|
129
|
-
return () => sub.unsubscribe();
|
|
130
|
-
}, [copilotkit]);
|
|
131
|
-
|
|
132
|
-
useEffect(() => {
|
|
133
|
-
function onKey(e: KeyboardEvent) {
|
|
134
|
-
const isCombo = (e.metaKey || e.ctrlKey) && e.shiftKey && e.key === "A";
|
|
135
|
-
if (!isCombo || agentIds.length === 0) return;
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
const idx = agentIds.indexOf(activeAgent);
|
|
138
|
-
const next = agentIds[(idx + 1) % agentIds.length];
|
|
139
|
-
setActiveAgent(next);
|
|
140
|
-
}
|
|
141
|
-
window.addEventListener("keydown", onKey);
|
|
142
|
-
return () => window.removeEventListener("keydown", onKey);
|
|
143
|
-
}, [agentIds, activeAgent]);
|
|
144
|
-
|
|
145
|
-
return (
|
|
146
|
-
<div>
|
|
147
|
-
<div className="text-xs opacity-60">
|
|
148
|
-
Active: {activeAgent} — press ⌘/Ctrl+Shift+A to cycle
|
|
149
|
-
</div>
|
|
150
|
-
<CopilotChat key={activeAgent} agentId={activeAgent} />
|
|
151
|
-
</div>
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
## Key rules across all three recipes
|
|
157
|
-
|
|
158
|
-
- Use `copilotkit.subscribe({ onAgentsChanged })` — there is no `useAgents()` hook.
|
|
159
|
-
- Always `key={activeAgent}` on `<CopilotChat>` so thread state doesn't leak when swapping agents in the same slot.
|
|
160
|
-
- Keep that `key` on `<CopilotChat>` and nowhere higher. A `key` discards every bit of state beneath it, so on a wrapper or a layout provider it silently wipes app state too.
|
|
161
|
-
- Clean up the subscription with `sub.unsubscribe()` in the effect cleanup.
|
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
# CopilotKit Switching Agents (React)
|
|
2
|
-
|
|
3
|
-
This skill builds on `copilotkit/agent-access`, `copilotkit/client-side-tools`,
|
|
4
|
-
and `copilotkit/rendering-tool-calls`.
|
|
5
|
-
|
|
6
|
-
Three main patterns:
|
|
7
|
-
|
|
8
|
-
1. **Parallel panels** — one `useAgent({ agentId })` per surface.
|
|
9
|
-
2. **Slot swap** — `<CopilotChat key={agentId} agentId={agentId} />`.
|
|
10
|
-
3. **Discovery** — subscribe to `onAgentsChanged` (no `useAgents()` hook).
|
|
11
|
-
|
|
12
|
-
## Setup
|
|
13
|
-
|
|
14
|
-
```tsx
|
|
15
|
-
"use client";
|
|
16
|
-
import { CopilotChat } from "@copilotkit/react-core/v2";
|
|
17
|
-
import { useState } from "react";
|
|
18
|
-
|
|
19
|
-
export function AgentSwitcherChat() {
|
|
20
|
-
const [activeAgent, setActiveAgent] = useState("research");
|
|
21
|
-
|
|
22
|
-
return (
|
|
23
|
-
<div>
|
|
24
|
-
<div>
|
|
25
|
-
<button onClick={() => setActiveAgent("research")}>Research</button>
|
|
26
|
-
<button onClick={() => setActiveAgent("coding")}>Coding</button>
|
|
27
|
-
</div>
|
|
28
|
-
|
|
29
|
-
{/* key={activeAgent} forces remount so thread state doesn't leak */}
|
|
30
|
-
<CopilotChat key={activeAgent} agentId={activeAgent} />
|
|
31
|
-
</div>
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
## Core Patterns
|
|
37
|
-
|
|
38
|
-
### Side-by-side chat panels
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
<div className="grid grid-cols-2 gap-4">
|
|
42
|
-
<CopilotChat agentId="research" threadId="research-main" />
|
|
43
|
-
<CopilotChat agentId="coding" threadId="coding-main" />
|
|
44
|
-
</div>
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
### Agent-scoped tool
|
|
48
|
-
|
|
49
|
-
```tsx
|
|
50
|
-
import { useFrontendTool } from "@copilotkit/react-core/v2";
|
|
51
|
-
import { z } from "zod";
|
|
52
|
-
|
|
53
|
-
useFrontendTool({
|
|
54
|
-
name: "saveFindings",
|
|
55
|
-
agentId: "research", // ← only the research agent sees this tool
|
|
56
|
-
parameters: z.object({ summary: z.string() }),
|
|
57
|
-
handler: async ({ summary }) => {
|
|
58
|
-
await fetch("/api/findings", { method: "POST", body: summary });
|
|
59
|
-
},
|
|
60
|
-
});
|
|
61
|
-
```
|
|
62
|
-
|
|
63
|
-
### Agent-scoped renderer
|
|
64
|
-
|
|
65
|
-
```tsx
|
|
66
|
-
import { useRenderTool } from "@copilotkit/react-core/v2";
|
|
67
|
-
import { z } from "zod";
|
|
68
|
-
|
|
69
|
-
useRenderTool({
|
|
70
|
-
name: "search",
|
|
71
|
-
agentId: "research", // ← only applies to research's "search" tool
|
|
72
|
-
parameters: z.object({ q: z.string() }),
|
|
73
|
-
render: ({ status, parameters, result }) => {
|
|
74
|
-
if (status === "inProgress") return <div>Preparing...</div>;
|
|
75
|
-
if (status === "executing") return <div>Searching {parameters.q}</div>;
|
|
76
|
-
return <div>{result}</div>;
|
|
77
|
-
},
|
|
78
|
-
});
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Discover available agents (no `useAgents` hook)
|
|
82
|
-
|
|
83
|
-
```tsx
|
|
84
|
-
"use client";
|
|
85
|
-
import { useCopilotKit } from "@copilotkit/react-core/v2";
|
|
86
|
-
import { useEffect, useState } from "react";
|
|
87
|
-
|
|
88
|
-
export function useAvailableAgents() {
|
|
89
|
-
const { copilotkit } = useCopilotKit();
|
|
90
|
-
const [ids, setIds] = useState<string[]>(() =>
|
|
91
|
-
Object.keys(copilotkit.agents ?? {}),
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
const subscription = copilotkit.subscribe({
|
|
96
|
-
onAgentsChanged: ({ agents }) => {
|
|
97
|
-
setIds(Object.keys(agents ?? {}));
|
|
98
|
-
},
|
|
99
|
-
});
|
|
100
|
-
return () => subscription.unsubscribe();
|
|
101
|
-
}, [copilotkit]);
|
|
102
|
-
|
|
103
|
-
return ids;
|
|
104
|
-
}
|
|
105
|
-
```
|
|
106
|
-
|
|
107
|
-
## Common Mistakes
|
|
108
|
-
|
|
109
|
-
### HIGH — Switching `agentId` on a persisted `<CopilotChat>` without `key`
|
|
110
|
-
|
|
111
|
-
Wrong:
|
|
112
|
-
|
|
113
|
-
```tsx
|
|
114
|
-
<CopilotChat agentId={activeAgent} />
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
Correct:
|
|
118
|
-
|
|
119
|
-
```tsx
|
|
120
|
-
<CopilotChat key={activeAgent} agentId={activeAgent} />
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
Without remount via `key`, prior thread state and in-flight runs leak into
|
|
124
|
-
the new agent's view. The remount pattern gives each agent a clean slate.
|
|
125
|
-
|
|
126
|
-
Keep the `key` on `<CopilotChat>` itself. It discards all state below it, so
|
|
127
|
-
hoisting it onto a wrapper or a layout-level provider also destroys app
|
|
128
|
-
state that has nothing to do with the agent — correlation maps, in-flight
|
|
129
|
-
request records, refs — with no error and no warning. If a component
|
|
130
|
-
dispatches requests and matches the responses back, it must sit outside the
|
|
131
|
-
keyed subtree. See "Keying a subtree on the active thread id above app
|
|
132
|
-
state" in `references/threads.md` for the thread-switching version of the
|
|
133
|
-
same trap.
|
|
134
|
-
|
|
135
|
-
Source: `examples/v2/react-router/app/routes/_index.tsx:38-39`
|
|
136
|
-
|
|
137
|
-
### MEDIUM — Omitting `agentId` when multiple agents share a tool name
|
|
138
|
-
|
|
139
|
-
Wrong:
|
|
140
|
-
|
|
141
|
-
```tsx
|
|
142
|
-
// Both research and coding agents have a "search" tool — unscoped wins globally
|
|
143
|
-
useRenderToolCall({
|
|
144
|
-
name: "search",
|
|
145
|
-
args: z.object({ q: z.string() }),
|
|
146
|
-
render,
|
|
147
|
-
});
|
|
148
|
-
```
|
|
149
|
-
|
|
150
|
-
Correct:
|
|
151
|
-
|
|
152
|
-
```tsx
|
|
153
|
-
useRenderTool({
|
|
154
|
-
name: "search",
|
|
155
|
-
agentId: "research",
|
|
156
|
-
parameters: z.object({ q: z.string() }),
|
|
157
|
-
render: researchSearchRender,
|
|
158
|
-
});
|
|
159
|
-
useRenderTool({
|
|
160
|
-
name: "search",
|
|
161
|
-
agentId: "coding",
|
|
162
|
-
parameters: z.object({ q: z.string() }),
|
|
163
|
-
render: codingSearchRender,
|
|
164
|
-
});
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Unscoped renderers apply to every agent. When two agents have a tool with
|
|
168
|
-
the same name and only one has a renderer, the unscoped renderer wins
|
|
169
|
-
globally and the other agent never gets its intended renderer.
|
|
170
|
-
|
|
171
|
-
Source: `packages/react-core/src/v2/hooks/use-render-tool-call.tsx:145-154`
|
|
172
|
-
|
|
173
|
-
### MEDIUM — Tools registered without `agentId` leak across panels
|
|
174
|
-
|
|
175
|
-
Wrong:
|
|
176
|
-
|
|
177
|
-
```tsx
|
|
178
|
-
useFrontendTool({
|
|
179
|
-
name: "saveFindings",
|
|
180
|
-
parameters: z.object({ summary: z.string() }),
|
|
181
|
-
handler,
|
|
182
|
-
});
|
|
183
|
-
// Both research and coding agents now see saveFindings.
|
|
184
|
-
```
|
|
185
|
-
|
|
186
|
-
Correct:
|
|
187
|
-
|
|
188
|
-
```tsx
|
|
189
|
-
useFrontendTool({
|
|
190
|
-
name: "saveFindings",
|
|
191
|
-
agentId: "research",
|
|
192
|
-
parameters: z.object({ summary: z.string() }),
|
|
193
|
-
handler,
|
|
194
|
-
});
|
|
195
|
-
```
|
|
196
|
-
|
|
197
|
-
Omitting `agentId` attaches the tool to every agent. In a multi-agent UI
|
|
198
|
-
this leaks the handler across panels. Scope tools explicitly when they
|
|
199
|
-
should only apply to one agent.
|
|
200
|
-
|
|
201
|
-
Source: `packages/react-core/src/v2/hooks/use-frontend-tool.tsx`
|
|
202
|
-
|
|
203
|
-
### MEDIUM — Using `useAgents()` (does not exist)
|
|
204
|
-
|
|
205
|
-
Wrong:
|
|
206
|
-
|
|
207
|
-
```tsx
|
|
208
|
-
import { useAgents } from "@copilotkit/react-core/v2"; // not exported
|
|
209
|
-
const agents = useAgents();
|
|
210
|
-
```
|
|
211
|
-
|
|
212
|
-
Correct:
|
|
213
|
-
|
|
214
|
-
```tsx
|
|
215
|
-
import { useCopilotKit } from "@copilotkit/react-core/v2";
|
|
216
|
-
import { useEffect, useState } from "react";
|
|
217
|
-
|
|
218
|
-
function useAvailableAgents() {
|
|
219
|
-
const { copilotkit } = useCopilotKit();
|
|
220
|
-
const [ids, setIds] = useState<string[]>(() =>
|
|
221
|
-
Object.keys(copilotkit.agents ?? {}),
|
|
222
|
-
);
|
|
223
|
-
useEffect(() => {
|
|
224
|
-
const sub = copilotkit.subscribe({
|
|
225
|
-
onAgentsChanged: ({ agents }) => setIds(Object.keys(agents ?? {})),
|
|
226
|
-
});
|
|
227
|
-
return () => sub.unsubscribe();
|
|
228
|
-
}, [copilotkit]);
|
|
229
|
-
return ids;
|
|
230
|
-
}
|
|
231
|
-
```
|
|
232
|
-
|
|
233
|
-
There is no `useAgents` hook in v2. Discover agents by subscribing to
|
|
234
|
-
`onAgentsChanged` on the core client.
|
|
235
|
-
|
|
236
|
-
Source: `packages/react-core/src/v2/hooks/index.ts` (no `useAgents` export)
|
|
237
|
-
|
|
238
|
-
## References
|
|
239
|
-
|
|
240
|
-
- [Agent switcher recipes](switching-agents-recipes.md) — dropdown, tabs, keyboard shortcuts
|