@copilotkit/react-core 1.56.5 → 1.57.0-canary.1778082736

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.
Files changed (47) hide show
  1. package/dist/{copilotkit-DFaI4j2r.d.mts → copilotkit-BNlJq5UO.d.mts} +60 -6
  2. package/dist/copilotkit-BNlJq5UO.d.mts.map +1 -0
  3. package/dist/{copilotkit-DMFu29Kx.cjs → copilotkit-B_k0HSNz.cjs} +322 -175
  4. package/dist/copilotkit-B_k0HSNz.cjs.map +1 -0
  5. package/dist/{copilotkit-Dg4r4Gi_.d.cts → copilotkit-DgC5oCFO.d.cts} +60 -6
  6. package/dist/copilotkit-DgC5oCFO.d.cts.map +1 -0
  7. package/dist/{copilotkit-OmIUrWym.mjs → copilotkit-ak8sGvQr.mjs} +317 -176
  8. package/dist/copilotkit-ak8sGvQr.mjs.map +1 -0
  9. package/dist/index.cjs +2 -5
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.mjs +2 -5
  14. package/dist/index.mjs.map +1 -1
  15. package/dist/index.umd.js +117 -172
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/v2/index.cjs +2 -1
  18. package/dist/v2/index.css +1 -1
  19. package/dist/v2/index.d.cts +2 -2
  20. package/dist/v2/index.d.mts +2 -2
  21. package/dist/v2/index.mjs +2 -2
  22. package/dist/v2/index.umd.js +332 -181
  23. package/dist/v2/index.umd.js.map +1 -1
  24. package/package.json +6 -6
  25. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +5 -6
  26. package/src/hooks/use-copilot-chat_internal.ts +0 -1
  27. package/src/v2/components/chat/CopilotChat.tsx +2 -1
  28. package/src/v2/components/chat/CopilotChatMessageView.tsx +24 -9
  29. package/src/v2/components/chat/CopilotChatView.tsx +2 -2
  30. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +1 -3
  31. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +29 -25
  32. package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.tsx +5 -60
  33. package/src/v2/components/index.ts +1 -0
  34. package/src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx +286 -0
  35. package/src/v2/components/intelligence-indicator/__tests__/IntelligenceIndicator.e2e.test.tsx +464 -0
  36. package/src/v2/components/intelligence-indicator/index.ts +2 -0
  37. package/src/v2/hooks/__tests__/use-threads.test.tsx +229 -27
  38. package/src/v2/hooks/use-agent.tsx +7 -116
  39. package/src/v2/hooks/use-render-activity-message.tsx +3 -11
  40. package/src/v2/hooks/use-render-custom-messages.tsx +1 -6
  41. package/src/v2/hooks/use-threads.tsx +7 -1
  42. package/src/v2/styles/globals.css +118 -0
  43. package/dist/copilotkit-DFaI4j2r.d.mts.map +0 -1
  44. package/dist/copilotkit-DMFu29Kx.cjs.map +0 -1
  45. package/dist/copilotkit-Dg4r4Gi_.d.cts.map +0 -1
  46. package/dist/copilotkit-OmIUrWym.mjs.map +0 -1
  47. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +0 -333
@@ -1,333 +0,0 @@
1
- import React from "react";
2
- import { render } from "@testing-library/react";
3
- import { renderHook } from "@testing-library/react";
4
- import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
5
- import {
6
- AbstractAgent,
7
- type BaseEvent,
8
- type RunAgentInput,
9
- } from "@ag-ui/client";
10
- import { useCopilotKit } from "../../providers/CopilotKitProvider";
11
- import { useAgent } from "../use-agent";
12
- import { CopilotKitCoreRuntimeConnectionStatus } from "@copilotkit/core";
13
- import { Observable } from "rxjs";
14
-
15
- vi.mock("../../providers/CopilotKitProvider", () => ({
16
- useCopilotKit: vi.fn(),
17
- }));
18
-
19
- const mockUseCopilotKit = useCopilotKit as ReturnType<typeof vi.fn>;
20
-
21
- /**
22
- * A minimal mock agent whose clone() returns a NEW instance and copies
23
- * messages from the source. This is essential for testing per-thread
24
- * isolation — each clone must be a distinct object that starts with the
25
- * source's state so that cloneForThread's setMessages([]) / setState({})
26
- * calls are meaningful (not vacuously true on an already-empty clone).
27
- */
28
- class CloneableAgent extends AbstractAgent {
29
- clone(): CloneableAgent {
30
- const cloned = new CloneableAgent();
31
- cloned.agentId = this.agentId;
32
- // Copy messages so cloneForThread's setMessages([]) actually clears state
33
- cloned.setMessages([...this.messages]);
34
- return cloned;
35
- }
36
-
37
- run(_input: RunAgentInput): Observable<BaseEvent> {
38
- return new Observable();
39
- }
40
- }
41
-
42
- describe("useAgent thread isolation", () => {
43
- let mockCopilotkit: {
44
- getAgent: ReturnType<typeof vi.fn>;
45
- runtimeUrl: string | undefined;
46
- runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus;
47
- runtimeTransport: string;
48
- headers: Record<string, string>;
49
- agents: Record<string, AbstractAgent>;
50
- subscribeToAgentWithOptions: (
51
- agent: AbstractAgent,
52
- subscriber: any,
53
- ) => { unsubscribe: () => void };
54
- };
55
-
56
- let registeredAgent: CloneableAgent;
57
-
58
- beforeEach(() => {
59
- registeredAgent = new CloneableAgent();
60
- registeredAgent.agentId = "my-agent";
61
-
62
- mockCopilotkit = {
63
- getAgent: vi.fn((id: string) =>
64
- id === "my-agent" ? registeredAgent : undefined,
65
- ),
66
- runtimeUrl: "http://localhost:3000/api/copilotkit",
67
- runtimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus.Connected,
68
- runtimeTransport: "rest",
69
- headers: {},
70
- agents: { "my-agent": registeredAgent },
71
- subscribeToAgentWithOptions: (agent, subscriber) =>
72
- agent.subscribe(subscriber),
73
- };
74
-
75
- mockUseCopilotKit.mockReturnValue({
76
- copilotkit: mockCopilotkit,
77
- executingToolCallIds: new Set(),
78
- });
79
- });
80
-
81
- afterEach(() => {
82
- vi.restoreAllMocks();
83
- });
84
-
85
- it("returns different agent instances for different threadIds with the same agentId", () => {
86
- const agents: Record<string, AbstractAgent> = {};
87
-
88
- function TrackerA() {
89
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-a" });
90
- agents["a"] = agent;
91
- return null;
92
- }
93
-
94
- function TrackerB() {
95
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-b" });
96
- agents["b"] = agent;
97
- return null;
98
- }
99
-
100
- render(
101
- <>
102
- <TrackerA />
103
- <TrackerB />
104
- </>,
105
- );
106
-
107
- expect(agents["a"]).toBeDefined();
108
- expect(agents["b"]).toBeDefined();
109
- expect(agents["a"]).not.toBe(agents["b"]);
110
- });
111
-
112
- it("returns the same cached instance for the same (agentId, threadId) across re-renders", () => {
113
- const instances: AbstractAgent[] = [];
114
-
115
- function Tracker() {
116
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-x" });
117
- instances.push(agent);
118
- return null;
119
- }
120
-
121
- const { rerender } = render(<Tracker />);
122
- rerender(<Tracker />);
123
-
124
- expect(instances.length).toBe(2);
125
- expect(instances[0]).toBe(instances[1]);
126
- });
127
-
128
- it("returns the shared registry agent when no threadId is provided (backward compat)", () => {
129
- let captured: AbstractAgent | undefined;
130
-
131
- function Tracker() {
132
- const { agent } = useAgent({ agentId: "my-agent" });
133
- captured = agent;
134
- return null;
135
- }
136
-
137
- render(<Tracker />);
138
- expect(captured).toBe(registeredAgent);
139
- });
140
-
141
- it("isolates messages between thread-specific agents", () => {
142
- // Pre-populate the source agent so CloneableAgent.clone() copies the
143
- // message into each clone — this makes cloneForThread's setMessages([])
144
- // meaningful rather than vacuously true on an already-empty clone.
145
- registeredAgent.addMessage({
146
- id: "source-msg",
147
- role: "user",
148
- content: "pre-existing on source",
149
- });
150
-
151
- const agents: Record<string, AbstractAgent> = {};
152
-
153
- function TrackerA() {
154
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-a" });
155
- agents["a"] = agent;
156
- return null;
157
- }
158
-
159
- function TrackerB() {
160
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-b" });
161
- agents["b"] = agent;
162
- return null;
163
- }
164
-
165
- render(
166
- <>
167
- <TrackerA />
168
- <TrackerB />
169
- </>,
170
- );
171
-
172
- // Both clones should start empty even though the source had a message —
173
- // cloneForThread must have called setMessages([]) on each clone.
174
- expect(agents["a"]!.messages).toHaveLength(0);
175
- expect(agents["b"]!.messages).toHaveLength(0);
176
-
177
- // Adding a message to thread A must not affect thread B
178
- agents["a"]!.addMessage({
179
- id: "msg-1",
180
- role: "user",
181
- content: "hello from thread A",
182
- });
183
-
184
- expect(agents["a"]!.messages).toHaveLength(1);
185
- expect(agents["b"]!.messages).toHaveLength(0);
186
- });
187
-
188
- it("sets threadId on cloned agents", () => {
189
- const agents: Record<string, AbstractAgent> = {};
190
-
191
- function TrackerA() {
192
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-a" });
193
- agents["a"] = agent;
194
- return null;
195
- }
196
-
197
- function TrackerB() {
198
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-b" });
199
- agents["b"] = agent;
200
- return null;
201
- }
202
-
203
- render(
204
- <>
205
- <TrackerA />
206
- <TrackerB />
207
- </>,
208
- );
209
-
210
- expect(agents["a"]!.threadId).toBe("thread-a");
211
- expect(agents["b"]!.threadId).toBe("thread-b");
212
- });
213
-
214
- it("invalidates stale clone when the registry agent is replaced", () => {
215
- // Simulates reconnect / hot-reload: copilotkit.agents holds a new object.
216
- const { result, rerender } = renderHook(
217
- ({ tid }: { tid: string }) =>
218
- useAgent({ agentId: "my-agent", threadId: tid }),
219
- { initialProps: { tid: "thread-a" } },
220
- );
221
-
222
- const firstClone = result.current.agent;
223
- expect(firstClone).not.toBe(registeredAgent); // it's a clone
224
-
225
- // Replace the registry agent
226
- const replacementAgent = new CloneableAgent();
227
- replacementAgent.agentId = "my-agent";
228
-
229
- mockCopilotkit.agents = { "my-agent": replacementAgent };
230
- mockCopilotkit.getAgent.mockImplementation((id: string) =>
231
- id === "my-agent" ? replacementAgent : undefined,
232
- );
233
- mockUseCopilotKit.mockReturnValue({
234
- copilotkit: { ...mockCopilotkit },
235
- executingToolCallIds: new Set(),
236
- });
237
-
238
- rerender({ tid: "thread-a" });
239
-
240
- const secondClone = result.current.agent;
241
- expect(secondClone).not.toBe(firstClone); // stale clone was invalidated
242
- expect(secondClone).not.toBe(replacementAgent); // still a clone, not the source
243
- });
244
-
245
- it("switching threadId returns a fresh clone; switching back returns the cached one", () => {
246
- const { result, rerender } = renderHook(
247
- ({ tid }: { tid: string }) =>
248
- useAgent({ agentId: "my-agent", threadId: tid }),
249
- { initialProps: { tid: "thread-a" } },
250
- );
251
-
252
- const cloneA = result.current.agent;
253
-
254
- rerender({ tid: "thread-b" });
255
- const cloneB = result.current.agent;
256
- expect(cloneB).not.toBe(cloneA);
257
-
258
- // Switching back to thread-a should return the originally cached clone
259
- rerender({ tid: "thread-a" });
260
- expect(result.current.agent).toBe(cloneA);
261
- });
262
-
263
- it("uses a fresh clone with correct threadId when provisional transitions to real agent", () => {
264
- // Start in Disconnected state — a provisional is created
265
- mockCopilotkit.runtimeConnectionStatus =
266
- CopilotKitCoreRuntimeConnectionStatus.Disconnected;
267
- mockCopilotkit.getAgent.mockReturnValue(undefined);
268
- mockCopilotkit.agents = {};
269
- mockUseCopilotKit.mockReturnValue({
270
- copilotkit: { ...mockCopilotkit },
271
- executingToolCallIds: new Set(),
272
- });
273
-
274
- const { result, rerender } = renderHook(() =>
275
- useAgent({ agentId: "my-agent", threadId: "thread-a" }),
276
- );
277
-
278
- const provisional = result.current.agent;
279
- expect(provisional.threadId).toBe("thread-a");
280
-
281
- // Real agent appears (runtime connected and agent registered)
282
- mockCopilotkit.runtimeConnectionStatus =
283
- CopilotKitCoreRuntimeConnectionStatus.Connected;
284
- mockCopilotkit.getAgent.mockImplementation((id: string) =>
285
- id === "my-agent" ? registeredAgent : undefined,
286
- );
287
- mockCopilotkit.agents = { "my-agent": registeredAgent };
288
- mockUseCopilotKit.mockReturnValue({
289
- copilotkit: { ...mockCopilotkit },
290
- executingToolCallIds: new Set(),
291
- });
292
-
293
- rerender();
294
-
295
- const realClone = result.current.agent;
296
- expect(realClone).not.toBe(provisional); // provisional replaced by real clone
297
- expect(realClone).not.toBe(registeredAgent); // it's a clone, not the source
298
- expect(realClone.threadId).toBe("thread-a");
299
- });
300
-
301
- it("uses composite key for provisional agents when threadId is provided", () => {
302
- // Put runtime in Disconnected state so provisionals are created
303
- mockCopilotkit.runtimeConnectionStatus =
304
- CopilotKitCoreRuntimeConnectionStatus.Disconnected;
305
- mockCopilotkit.getAgent.mockReturnValue(undefined);
306
- mockCopilotkit.agents = {};
307
-
308
- const agents: Record<string, AbstractAgent> = {};
309
-
310
- function TrackerA() {
311
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-a" });
312
- agents["a"] = agent;
313
- return null;
314
- }
315
-
316
- function TrackerB() {
317
- const { agent } = useAgent({ agentId: "my-agent", threadId: "thread-b" });
318
- agents["b"] = agent;
319
- return null;
320
- }
321
-
322
- render(
323
- <>
324
- <TrackerA />
325
- <TrackerB />
326
- </>,
327
- );
328
-
329
- expect(agents["a"]).not.toBe(agents["b"]);
330
- expect(agents["a"]!.threadId).toBe("thread-a");
331
- expect(agents["b"]!.threadId).toBe("thread-b");
332
- });
333
- });