@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,8 +1,7 @@
1
1
  import { useCopilotKit } from "../providers/CopilotKitProvider";
2
- import { useCopilotChatConfiguration } from "../providers/CopilotChatConfigurationProvider";
3
2
  import { useMemo, useEffect, useReducer, useRef } from "react";
4
3
  import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
5
- import { AbstractAgent, HttpAgent } from "@ag-ui/client";
4
+ import { AbstractAgent } from "@ag-ui/client";
6
5
  import {
7
6
  ProxiedCopilotRuntimeAgent,
8
7
  CopilotKitCoreRuntimeConnectionStatus,
@@ -22,7 +21,6 @@ const ALL_UPDATES: UseAgentUpdate[] = [
22
21
 
23
22
  export interface UseAgentProps {
24
23
  agentId?: string;
25
- threadId?: string;
26
24
  updates?: UseAgentUpdate[];
27
25
  /**
28
26
  * Throttle interval (in milliseconds) for React re-renders triggered by
@@ -46,96 +44,15 @@ export interface UseAgentProps {
46
44
  throttleMs?: number;
47
45
  }
48
46
 
49
- /**
50
- * Clone a registry agent for per-thread isolation.
51
- * Copies agent configuration (transport, headers, etc.) but resets conversation
52
- * state (messages, threadId, state) so each thread starts fresh.
53
- */
54
- function cloneForThread(
55
- source: AbstractAgent,
56
- threadId: string,
57
- headers: Record<string, string>,
58
- ): AbstractAgent {
59
- const clone = source.clone();
60
- if (clone === source) {
61
- throw new Error(
62
- `useAgent: ${source.constructor.name}.clone() returned the same instance. ` +
63
- `clone() must return a new, independent object.`,
64
- );
65
- }
66
- clone.threadId = threadId;
67
- clone.setMessages([]);
68
- clone.setState({});
69
- if (clone instanceof HttpAgent) {
70
- clone.headers = { ...headers };
71
- }
72
- return clone;
73
- }
74
-
75
- /**
76
- * Module-level WeakMap: registryAgent → (threadId → clone).
77
- * Shared across all useAgent() calls so that every component using the same
78
- * (agentId, threadId) pair receives the same agent instance. Using WeakMap
79
- * ensures the clone map is garbage-collected when the registry agent is
80
- * replaced (e.g. after reconnect or hot-reload).
81
- */
82
- export const globalThreadCloneMap = new WeakMap<
83
- AbstractAgent,
84
- Map<string, AbstractAgent>
85
- >();
86
-
87
- /**
88
- * Look up an existing per-thread clone without creating one.
89
- * Returns undefined when no clone has been created yet for this pair.
90
- */
91
- export function getThreadClone(
92
- registryAgent: AbstractAgent | undefined | null,
93
- threadId: string | undefined | null,
94
- ): AbstractAgent | undefined {
95
- if (!registryAgent || !threadId) return undefined;
96
- return globalThreadCloneMap.get(registryAgent)?.get(threadId);
97
- }
98
-
99
- function getOrCreateThreadClone(
100
- existing: AbstractAgent,
101
- threadId: string,
102
- headers: Record<string, string>,
103
- ): AbstractAgent {
104
- let byThread = globalThreadCloneMap.get(existing);
105
- if (!byThread) {
106
- byThread = new Map();
107
- globalThreadCloneMap.set(existing, byThread);
108
- }
109
- const cached = byThread.get(threadId);
110
- if (cached) return cached;
111
-
112
- const clone = cloneForThread(existing, threadId, headers);
113
- byThread.set(threadId, clone);
114
- return clone;
115
- }
116
-
117
- export function useAgent({
118
- agentId,
119
- threadId,
120
- updates,
121
- throttleMs,
122
- }: UseAgentProps = {}) {
47
+ export function useAgent({ agentId, updates, throttleMs }: UseAgentProps = {}) {
123
48
  agentId ??= DEFAULT_AGENT_ID;
124
49
 
125
50
  const { copilotkit } = useCopilotKit();
126
51
  const providerThrottleMs = copilotkit.defaultThrottleMs;
127
- // Fall back to the enclosing CopilotChatConfigurationProvider's threadId so
128
- // that useAgent() called without explicit threadId (e.g. inside a custom
129
- // message renderer) automatically uses the same per-thread clone as the
130
- // CopilotChat component it lives within.
131
- const chatConfig = useCopilotChatConfiguration();
132
- threadId ??= chatConfig?.threadId;
133
52
 
134
53
  const effectiveThrottleMs = useMemo(() => {
135
54
  const resolved = throttleMs ?? providerThrottleMs ?? 0;
136
55
  if (!Number.isFinite(resolved) || resolved < 0) {
137
- // When both throttleMs and providerThrottleMs are undefined, resolved
138
- // is 0 which passes validation — so one of them must be defined here.
139
56
  const source =
140
57
  throttleMs !== undefined
141
58
  ? "hook-level throttleMs"
@@ -147,7 +64,6 @@ export function useAgent({
147
64
  }
148
65
  return resolved;
149
66
  }, [throttleMs, providerThrottleMs]);
150
-
151
67
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
152
68
 
153
69
  const updateFlags = useMemo(
@@ -163,29 +79,11 @@ export function useAgent({
163
79
  );
164
80
 
165
81
  const agent: AbstractAgent = useMemo(() => {
166
- // Use a composite key when threadId is provided so that different threads
167
- // for the same agent get independent instances.
168
- const cacheKey = threadId ? `${agentId}:${threadId}` : agentId;
169
-
170
82
  const existing = copilotkit.getAgent(agentId);
171
83
  if (existing) {
172
- // Real agent found — clear any cached provisionals for this key and the
173
- // bare agentId key (handles the case where a provisional was created
174
- // before threadId was available, then the component re-renders with one).
175
- provisionalAgentCache.current.delete(cacheKey);
84
+ // Real agent found — clear any cached provisional for this ID
176
85
  provisionalAgentCache.current.delete(agentId);
177
-
178
- if (!threadId) {
179
- // No threadId — return the shared registry agent (original behavior)
180
- return existing;
181
- }
182
-
183
- // threadId provided — return the shared per-thread clone.
184
- // The global WeakMap ensures all components using the same
185
- // (registryAgent, threadId) pair receive the same instance, so state
186
- // mutations (addMessage, setState) are visible everywhere. The WeakMap
187
- // entry is GC-collected automatically when the registry agent is replaced.
188
- return getOrCreateThreadClone(existing, threadId, copilotkit.headers);
86
+ return existing;
189
87
  }
190
88
 
191
89
  const isRuntimeConfigured = copilotkit.runtimeUrl !== undefined;
@@ -198,7 +96,7 @@ export function useAgent({
198
96
  status === CopilotKitCoreRuntimeConnectionStatus.Connecting)
199
97
  ) {
200
98
  // Return cached provisional if available (keeps reference stable)
201
- const cached = provisionalAgentCache.current.get(cacheKey);
99
+ const cached = provisionalAgentCache.current.get(agentId);
202
100
  if (cached) {
203
101
  // Update headers on the cached agent in case they changed
204
102
  cached.headers = { ...copilotkit.headers };
@@ -213,10 +111,7 @@ export function useAgent({
213
111
  });
214
112
  // Apply current headers so runs/connects inherit them
215
113
  provisional.headers = { ...copilotkit.headers };
216
- if (threadId) {
217
- provisional.threadId = threadId;
218
- }
219
- provisionalAgentCache.current.set(cacheKey, provisional);
114
+ provisionalAgentCache.current.set(agentId, provisional);
220
115
  return provisional;
221
116
  }
222
117
 
@@ -229,14 +124,6 @@ export function useAgent({
229
124
  isRuntimeConfigured &&
230
125
  status === CopilotKitCoreRuntimeConnectionStatus.Error
231
126
  ) {
232
- // Cache the provisional so that dep changes while in Error state (e.g.
233
- // headers update) return the same agent reference, matching the
234
- // Disconnected/Connecting path and preventing spurious re-subscriptions.
235
- const cached = provisionalAgentCache.current.get(cacheKey);
236
- if (cached) {
237
- cached.headers = { ...copilotkit.headers };
238
- return cached;
239
- }
240
127
  const provisional = new ProxiedCopilotRuntimeAgent({
241
128
  runtimeUrl: copilotkit.runtimeUrl,
242
129
  agentId,
@@ -244,10 +131,6 @@ export function useAgent({
244
131
  runtimeMode: "pending",
245
132
  });
246
133
  provisional.headers = { ...copilotkit.headers };
247
- if (threadId) {
248
- provisional.threadId = threadId;
249
- }
250
- provisionalAgentCache.current.set(cacheKey, provisional);
251
134
  return provisional;
252
135
  }
253
136
 
@@ -266,7 +149,6 @@ export function useAgent({
266
149
  // eslint-disable-next-line react-hooks/exhaustive-deps
267
150
  }, [
268
151
  agentId,
269
- threadId,
270
152
  copilotkit.agents,
271
153
  copilotkit.runtimeConnectionStatus,
272
154
  copilotkit.runtimeUrl,
@@ -281,25 +163,6 @@ export function useAgent({
281
163
  let timerId: ReturnType<typeof setTimeout> | null = null;
282
164
  let active = true;
283
165
 
284
- // Microtask-batched forceUpdate: coalesces multiple synchronous
285
- // notifications (e.g. OnStateChanged + OnRunStatusChanged firing in the
286
- // same tick) into a single React re-render. This prevents the scroll
287
- // jumping described in #3499 where rapid unbatched forceUpdate calls
288
- // cause brief content height fluctuations during streaming.
289
- let batchScheduled = false;
290
- const batchedForceUpdate = () => {
291
- if (!active) return;
292
- if (!batchScheduled) {
293
- batchScheduled = true;
294
- queueMicrotask(() => {
295
- batchScheduled = false;
296
- if (active) {
297
- forceUpdate();
298
- }
299
- });
300
- }
301
- };
302
-
303
166
  if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
304
167
  const ms = effectiveThrottleMs;
305
168
  if (ms > 0) {
@@ -343,13 +206,13 @@ export function useAgent({
343
206
  }
344
207
 
345
208
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) {
346
- handlers.onStateChanged = batchedForceUpdate;
209
+ handlers.onStateChanged = forceUpdate;
347
210
  }
348
211
 
349
212
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
350
- handlers.onRunInitialized = batchedForceUpdate;
351
- handlers.onRunFinalized = batchedForceUpdate;
352
- handlers.onRunFailed = batchedForceUpdate;
213
+ handlers.onRunInitialized = forceUpdate;
214
+ handlers.onRunFinalized = forceUpdate;
215
+ handlers.onRunFailed = forceUpdate;
353
216
  }
354
217
 
355
218
  const subscription = agent.subscribe(handlers);
@@ -363,16 +226,6 @@ export function useAgent({
363
226
  // eslint-disable-next-line react-hooks/exhaustive-deps
364
227
  }, [agent, forceUpdate, effectiveThrottleMs, updateFlags]);
365
228
 
366
- // Keep HttpAgent headers fresh without mutating inside useMemo, which is
367
- // unsafe in concurrent mode (React may invoke useMemo multiple times and
368
- // discard intermediate results, but mutations always land).
369
- useEffect(() => {
370
- if (agent instanceof HttpAgent) {
371
- agent.headers = { ...copilotkit.headers };
372
- }
373
- // eslint-disable-next-line react-hooks/exhaustive-deps
374
- }, [agent, JSON.stringify(copilotkit.headers)]);
375
-
376
229
  return {
377
230
  agent,
378
231
  };
@@ -3,7 +3,7 @@ import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
3
3
  import { useCopilotKit, useCopilotChatConfiguration } from "../providers";
4
4
  import { useCallback, useMemo } from "react";
5
5
  import { ReactActivityMessageRenderer } from "../types";
6
- import { getThreadClone } from "./use-agent";
6
+
7
7
 
8
8
  export function useRenderActivityMessage() {
9
9
  const { copilotkit } = useCopilotKit();
@@ -52,13 +52,7 @@ export function useRenderActivityMessage() {
52
52
  }
53
53
 
54
54
  const Component = renderer.render;
55
- // Prefer the per-thread clone so that handleAction in ReactSurfaceHost
56
- // calls runAgent on the same agent instance that CopilotChat renders from.
57
- // Without this, button clicks accumulate messages on the registry agent
58
- // while CopilotChat displays from the clone — responses appear to vanish.
59
- const registryAgent = copilotkit.getAgent(agentId);
60
- const agent =
61
- getThreadClone(registryAgent, config?.threadId) ?? registryAgent;
55
+ const agent = copilotkit.getAgent(agentId);
62
56
 
63
57
  return (
64
58
  <Component
@@ -70,7 +64,7 @@ export function useRenderActivityMessage() {
70
64
  />
71
65
  );
72
66
  },
73
- [agentId, config?.threadId, copilotkit, findRenderer],
67
+ [agentId, copilotkit, findRenderer],
74
68
  );
75
69
 
76
70
  return useMemo(
@@ -1,5 +1,4 @@
1
1
  import { useCopilotChatConfiguration, useCopilotKit } from "../providers";
2
- import { getThreadClone } from "./use-agent";
3
2
  import { ReactCustomMessageRendererPosition } from "../types/react-custom-message-renderer";
4
3
  import { Message } from "@ag-ui/core";
5
4
 
@@ -39,11 +38,7 @@ export function useRenderCustomMessages() {
39
38
  copilotkit.getRunIdForMessage(agentId, threadId, message.id) ??
40
39
  copilotkit.getRunIdsForThread(agentId, threadId).slice(-1)[0];
41
40
  const runId = resolvedRunId ?? `missing-run-id:${message.id}`;
42
- // Prefer the per-thread clone so that agent.messages reflects the actual
43
- // conversation state (messages live on the clone, not the registry agent).
44
- // Fall back to the registry agent when no clone exists (no threadId).
45
- const registryAgent = copilotkit.getAgent(agentId);
46
- const agent = getThreadClone(registryAgent, threadId) ?? registryAgent;
41
+ const agent = copilotkit.getAgent(agentId);
47
42
  if (!agent) {
48
43
  throw new Error("Agent not found");
49
44
  }
@@ -112,7 +112,7 @@ export const useLicenseContext = (): LicenseContextValue =>
112
112
  export interface CopilotKitProviderProps {
113
113
  children: ReactNode;
114
114
  runtimeUrl?: string;
115
- headers?: Record<string, string> | (() => Record<string, string>);
115
+ headers?: Record<string, string>;
116
116
  /**
117
117
  * Credentials mode for fetch requests (e.g., "include" for HTTP-only cookies in cross-origin requests).
118
118
  */
@@ -263,7 +263,7 @@ function useStableArrayProp<T>(
263
263
  export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
264
264
  children,
265
265
  runtimeUrl,
266
- headers: headersProp = {},
266
+ headers = {},
267
267
  credentials,
268
268
  publicApiKey,
269
269
  publicLicenseKey,
@@ -389,10 +389,6 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
389
389
  );
390
390
  const hasLocalAgents = mergedAgents && Object.keys(mergedAgents).length > 0;
391
391
 
392
- // Resolve headers from function or static object
393
- const headers =
394
- typeof headersProp === "function" ? headersProp() : headersProp;
395
-
396
392
  // Merge a provided publicApiKey into headers (without overwriting an explicit header).
397
393
  const mergedHeaders = useMemo(() => {
398
394
  if (!resolvedPublicKey) return headers;