@copilotkit/react-core 1.55.3-canary.1776243725 → 1.55.3-canary.1776979102

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