@copilotkit/react-core 1.57.0 → 1.57.1-canary.1778272612

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 (86) hide show
  1. package/dist/{copilotkit-CPe2-340.mjs → copilotkit-3XTEoVQO.mjs} +1367 -1200
  2. package/dist/copilotkit-3XTEoVQO.mjs.map +1 -0
  3. package/dist/{copilotkit-DFaI4j2r.d.mts → copilotkit-BCJ2yvV6.d.mts} +68 -8
  4. package/dist/copilotkit-BCJ2yvV6.d.mts.map +1 -0
  5. package/dist/{copilotkit-Dg4r4Gi_.d.cts → copilotkit-CBbSvze0.d.cts} +68 -8
  6. package/dist/copilotkit-CBbSvze0.d.cts.map +1 -0
  7. package/dist/{copilotkit-DGbvw8n2.cjs → copilotkit-Dnj9pi4m.cjs} +1369 -1196
  8. package/dist/copilotkit-Dnj9pi4m.cjs.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 +733 -610
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/v2/context.cjs +135 -0
  18. package/dist/v2/context.cjs.map +1 -0
  19. package/dist/v2/context.d.cts +148 -0
  20. package/dist/v2/context.d.cts.map +1 -0
  21. package/dist/v2/context.d.mts +148 -0
  22. package/dist/v2/context.d.mts.map +1 -0
  23. package/dist/v2/context.mjs +129 -0
  24. package/dist/v2/context.mjs.map +1 -0
  25. package/dist/v2/headless.cjs +1043 -0
  26. package/dist/v2/headless.cjs.map +1 -0
  27. package/dist/v2/headless.d.cts +605 -0
  28. package/dist/v2/headless.d.cts.map +1 -0
  29. package/dist/v2/headless.d.mts +512 -0
  30. package/dist/v2/headless.d.mts.map +1 -0
  31. package/dist/v2/headless.mjs +997 -0
  32. package/dist/v2/headless.mjs.map +1 -0
  33. package/dist/v2/index.cjs +2 -1
  34. package/dist/v2/index.css +1 -1
  35. package/dist/v2/index.d.cts +2 -2
  36. package/dist/v2/index.d.mts +2 -2
  37. package/dist/v2/index.mjs +2 -2
  38. package/dist/v2/index.umd.js +1385 -1204
  39. package/dist/v2/index.umd.js.map +1 -1
  40. package/package.json +14 -6
  41. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +5 -6
  42. package/src/hooks/use-copilot-chat_internal.ts +0 -1
  43. package/src/v2/components/chat/CopilotChat.tsx +2 -1
  44. package/src/v2/components/chat/CopilotChatMessageView.tsx +24 -9
  45. package/src/v2/components/chat/CopilotChatView.tsx +2 -2
  46. package/src/v2/components/chat/CopilotSidebar.tsx +5 -1
  47. package/src/v2/components/chat/CopilotSidebarView.tsx +24 -10
  48. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +1 -3
  49. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +29 -25
  50. package/src/v2/components/chat/__tests__/CopilotSidebarView.position.test.tsx +159 -0
  51. package/src/v2/components/chat/__tests__/MCPAppsUiMessage.e2e.test.tsx +5 -60
  52. package/src/v2/components/index.ts +1 -0
  53. package/src/v2/components/intelligence-indicator/IntelligenceIndicator.tsx +286 -0
  54. package/src/v2/components/intelligence-indicator/__tests__/IntelligenceIndicator.e2e.test.tsx +464 -0
  55. package/src/v2/components/intelligence-indicator/index.ts +2 -0
  56. package/src/v2/context.ts +62 -0
  57. package/src/v2/headless.ts +42 -0
  58. package/src/v2/hooks/__tests__/standard-schema.test.tsx +2 -2
  59. package/src/v2/hooks/__tests__/use-agent-context.test.tsx +3 -3
  60. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +3 -3
  61. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +85 -85
  62. package/src/v2/hooks/__tests__/use-interrupt.test.tsx +2 -2
  63. package/src/v2/hooks/__tests__/use-render-tool.test.tsx +2 -2
  64. package/src/v2/hooks/__tests__/use-threads.test.tsx +2 -2
  65. package/src/v2/hooks/__tests__/zod-regression.test.tsx +2 -2
  66. package/src/v2/hooks/use-agent-context.tsx +1 -1
  67. package/src/v2/hooks/use-agent.tsx +9 -118
  68. package/src/v2/hooks/use-configure-suggestions.tsx +1 -1
  69. package/src/v2/hooks/use-default-render-tool.tsx +18 -1
  70. package/src/v2/hooks/use-frontend-tool.tsx +2 -2
  71. package/src/v2/hooks/use-human-in-the-loop.tsx +1 -1
  72. package/src/v2/hooks/use-interrupt.tsx +1 -1
  73. package/src/v2/hooks/use-render-activity-message.tsx +3 -11
  74. package/src/v2/hooks/use-render-custom-messages.tsx +1 -6
  75. package/src/v2/hooks/use-render-tool-call.tsx +36 -6
  76. package/src/v2/hooks/use-render-tool.tsx +2 -2
  77. package/src/v2/hooks/use-suggestions.tsx +1 -1
  78. package/src/v2/hooks/use-threads.tsx +1 -1
  79. package/src/v2/providers/CopilotKitProvider.tsx +19 -59
  80. package/src/v2/styles/globals.css +118 -0
  81. package/tsdown.config.ts +75 -0
  82. package/dist/copilotkit-CPe2-340.mjs.map +0 -1
  83. package/dist/copilotkit-DFaI4j2r.d.mts.map +0 -1
  84. package/dist/copilotkit-DGbvw8n2.cjs.map +0 -1
  85. package/dist/copilotkit-Dg4r4Gi_.d.cts.map +0 -1
  86. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +0 -333
@@ -2,10 +2,10 @@ import React from "react";
2
2
  import { act, render, screen, waitFor } from "@testing-library/react";
3
3
  import { beforeEach, describe, expect, it, vi } from "vitest";
4
4
  import { useInterrupt } from "../use-interrupt";
5
- import { useCopilotKit } from "../../providers/CopilotKitProvider";
5
+ import { useCopilotKit } from "../../context";
6
6
  import { useAgent } from "../use-agent";
7
7
 
8
- vi.mock("../../providers/CopilotKitProvider", () => ({
8
+ vi.mock("../../context", () => ({
9
9
  useCopilotKit: vi.fn(),
10
10
  }));
11
11
 
@@ -3,10 +3,10 @@ import { render } from "@testing-library/react";
3
3
  import { describe, it, expect, beforeEach, vi } from "vitest";
4
4
  import { z } from "zod";
5
5
  import { useRenderTool, type RenderToolProps } from "../use-render-tool";
6
- import { useCopilotKit } from "../../providers/CopilotKitProvider";
6
+ import { useCopilotKit } from "../../context";
7
7
  import type { ReactToolCallRenderer } from "../../types/react-tool-call-renderer";
8
8
 
9
- vi.mock("../../providers/CopilotKitProvider", () => ({
9
+ vi.mock("../../context", () => ({
10
10
  useCopilotKit: vi.fn(),
11
11
  }));
12
12
 
@@ -1,13 +1,13 @@
1
1
  import React from "react";
2
2
  import { act, renderHook, waitFor } from "@testing-library/react";
3
3
  import { afterAll, beforeEach, describe, expect, it, vi } from "vitest";
4
- import { useCopilotKit } from "../../providers/CopilotKitProvider";
4
+ import { useCopilotKit } from "../../context";
5
5
  import {
6
6
  CopilotKitCoreRuntimeConnectionStatus,
7
7
  ɵMAX_SOCKET_RETRIES,
8
8
  } from "@copilotkit/core";
9
9
 
10
- vi.mock("../../providers/CopilotKitProvider", () => ({
10
+ vi.mock("../../context", () => ({
11
11
  useCopilotKit: vi.fn(),
12
12
  }));
13
13
 
@@ -15,11 +15,11 @@ import { z } from "zod";
15
15
  import { useRenderTool } from "../use-render-tool";
16
16
  import { useComponent } from "../use-component";
17
17
  import { defineToolCallRenderer } from "../../types/defineToolCallRenderer";
18
- import { useCopilotKit } from "../../providers/CopilotKitProvider";
18
+ import { useCopilotKit } from "../../context";
19
19
  import { useFrontendTool } from "../use-frontend-tool";
20
20
  import type { ReactToolCallRenderer } from "../../types/react-tool-call-renderer";
21
21
 
22
- vi.mock("../../providers/CopilotKitProvider", () => ({
22
+ vi.mock("../../context", () => ({
23
23
  useCopilotKit: vi.fn(),
24
24
  }));
25
25
 
@@ -1,4 +1,4 @@
1
- import { useCopilotKit } from "../providers/CopilotKitProvider";
1
+ import { useCopilotKit } from "../context";
2
2
  import { useLayoutEffect, useMemo } from "react";
3
3
 
4
4
  /**
@@ -1,5 +1,4 @@
1
- import { useCopilotKit } from "../providers/CopilotKitProvider";
2
- import { useCopilotChatConfiguration } from "../providers/CopilotChatConfigurationProvider";
1
+ import { useCopilotKit } from "../context";
3
2
  import { useMemo, useEffect, useReducer, useRef } from "react";
4
3
  import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
5
4
  import { AbstractAgent, HttpAgent } from "@ag-ui/client";
@@ -23,7 +22,6 @@ const ALL_UPDATES: UseAgentUpdate[] = [
23
22
 
24
23
  export interface UseAgentProps {
25
24
  agentId?: string;
26
- threadId?: string;
27
25
  updates?: UseAgentUpdate[];
28
26
  /**
29
27
  * Throttle interval (in milliseconds) for re-renders triggered by
@@ -50,80 +48,7 @@ export interface UseAgentProps {
50
48
  throttleMs?: number;
51
49
  }
52
50
 
53
- /**
54
- * Clone a registry agent for per-thread isolation.
55
- * Copies agent configuration (transport, headers, etc.) but resets conversation
56
- * state (messages, threadId, state) so each thread starts fresh.
57
- */
58
- function cloneForThread(
59
- source: AbstractAgent,
60
- threadId: string,
61
- headers: Record<string, string>,
62
- ): AbstractAgent {
63
- const clone = source.clone();
64
- if (clone === source) {
65
- throw new Error(
66
- `useAgent: ${source.constructor.name}.clone() returned the same instance. ` +
67
- `clone() must return a new, independent object.`,
68
- );
69
- }
70
- clone.threadId = threadId;
71
- clone.setMessages([]);
72
- clone.setState({});
73
- if (clone instanceof HttpAgent) {
74
- clone.headers = { ...headers };
75
- }
76
- return clone;
77
- }
78
-
79
- /**
80
- * Module-level WeakMap: registryAgent → (threadId → clone).
81
- * Shared across all useAgent() calls so that every component using the same
82
- * (agentId, threadId) pair receives the same agent instance. Using WeakMap
83
- * ensures the clone map is garbage-collected when the registry agent is
84
- * replaced (e.g. after reconnect or hot-reload).
85
- */
86
- export const globalThreadCloneMap = new WeakMap<
87
- AbstractAgent,
88
- Map<string, AbstractAgent>
89
- >();
90
-
91
- /**
92
- * Look up an existing per-thread clone without creating one.
93
- * Returns undefined when no clone has been created yet for this pair.
94
- */
95
- export function getThreadClone(
96
- registryAgent: AbstractAgent | undefined | null,
97
- threadId: string | undefined | null,
98
- ): AbstractAgent | undefined {
99
- if (!registryAgent || !threadId) return undefined;
100
- return globalThreadCloneMap.get(registryAgent)?.get(threadId);
101
- }
102
-
103
- function getOrCreateThreadClone(
104
- existing: AbstractAgent,
105
- threadId: string,
106
- headers: Record<string, string>,
107
- ): AbstractAgent {
108
- let byThread = globalThreadCloneMap.get(existing);
109
- if (!byThread) {
110
- byThread = new Map();
111
- globalThreadCloneMap.set(existing, byThread);
112
- }
113
- const cached = byThread.get(threadId);
114
- if (cached) return cached;
115
-
116
- const clone = cloneForThread(existing, threadId, headers);
117
- byThread.set(threadId, clone);
118
- return clone;
119
- }
120
-
121
- export function useAgent({
122
- agentId,
123
- threadId,
124
- updates,
125
- throttleMs,
126
- }: UseAgentProps = {}) {
51
+ export function useAgent({ agentId, updates, throttleMs }: UseAgentProps = {}) {
127
52
  agentId ??= DEFAULT_AGENT_ID;
128
53
 
129
54
  const { copilotkit } = useCopilotKit();
@@ -131,12 +56,6 @@ export function useAgent({
131
56
  // subscribeToAgentWithOptions reads it from the core instance, but React needs the dep
132
57
  // to know when to re-subscribe.
133
58
  const providerThrottleMs = copilotkit.defaultThrottleMs;
134
- // Fall back to the enclosing CopilotChatConfigurationProvider's threadId so
135
- // that useAgent() called without explicit threadId (e.g. inside a custom
136
- // message renderer) automatically uses the same per-thread clone as the
137
- // CopilotChat component it lives within.
138
- const chatConfig = useCopilotChatConfiguration();
139
- threadId ??= chatConfig?.threadId;
140
59
 
141
60
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
142
61
 
@@ -153,29 +72,11 @@ export function useAgent({
153
72
  );
154
73
 
155
74
  const agent: AbstractAgent = useMemo(() => {
156
- // Use a composite key when threadId is provided so that different threads
157
- // for the same agent get independent instances.
158
- const cacheKey = threadId ? `${agentId}:${threadId}` : agentId;
159
-
160
75
  const existing = copilotkit.getAgent(agentId);
161
76
  if (existing) {
162
- // Real agent found — clear any cached provisionals for this key and the
163
- // bare agentId key (handles the case where a provisional was created
164
- // before threadId was available, then the component re-renders with one).
165
- provisionalAgentCache.current.delete(cacheKey);
77
+ // Real agent found — clear any cached provisional for this ID
166
78
  provisionalAgentCache.current.delete(agentId);
167
-
168
- if (!threadId) {
169
- // No threadId — return the shared registry agent (original behavior)
170
- return existing;
171
- }
172
-
173
- // threadId provided — return the shared per-thread clone.
174
- // The global WeakMap ensures all components using the same
175
- // (registryAgent, threadId) pair receive the same instance, so state
176
- // mutations (addMessage, setState) are visible everywhere. The WeakMap
177
- // entry is GC-collected automatically when the registry agent is replaced.
178
- return getOrCreateThreadClone(existing, threadId, copilotkit.headers);
79
+ return existing;
179
80
  }
180
81
 
181
82
  const isRuntimeConfigured = copilotkit.runtimeUrl !== undefined;
@@ -188,7 +89,7 @@ export function useAgent({
188
89
  status === CopilotKitCoreRuntimeConnectionStatus.Connecting)
189
90
  ) {
190
91
  // Return cached provisional if available (keeps reference stable)
191
- const cached = provisionalAgentCache.current.get(cacheKey);
92
+ const cached = provisionalAgentCache.current.get(agentId);
192
93
  if (cached) {
193
94
  // Update headers on the cached agent in case they changed
194
95
  cached.headers = { ...copilotkit.headers };
@@ -203,10 +104,7 @@ export function useAgent({
203
104
  });
204
105
  // Apply current headers so runs/connects inherit them
205
106
  provisional.headers = { ...copilotkit.headers };
206
- if (threadId) {
207
- provisional.threadId = threadId;
208
- }
209
- provisionalAgentCache.current.set(cacheKey, provisional);
107
+ provisionalAgentCache.current.set(agentId, provisional);
210
108
  return provisional;
211
109
  }
212
110
 
@@ -219,10 +117,7 @@ export function useAgent({
219
117
  isRuntimeConfigured &&
220
118
  status === CopilotKitCoreRuntimeConnectionStatus.Error
221
119
  ) {
222
- // Cache the provisional so that dep changes while in Error state (e.g.
223
- // headers update) return the same agent reference, matching the
224
- // Disconnected/Connecting path and preventing spurious re-subscriptions.
225
- const cached = provisionalAgentCache.current.get(cacheKey);
120
+ const cached = provisionalAgentCache.current.get(agentId);
226
121
  if (cached) {
227
122
  cached.headers = { ...copilotkit.headers };
228
123
  return cached;
@@ -234,10 +129,7 @@ export function useAgent({
234
129
  runtimeMode: "pending",
235
130
  });
236
131
  provisional.headers = { ...copilotkit.headers };
237
- if (threadId) {
238
- provisional.threadId = threadId;
239
- }
240
- provisionalAgentCache.current.set(cacheKey, provisional);
132
+ provisionalAgentCache.current.set(agentId, provisional);
241
133
  return provisional;
242
134
  }
243
135
 
@@ -256,7 +148,6 @@ export function useAgent({
256
148
  // eslint-disable-next-line react-hooks/exhaustive-deps
257
149
  }, [
258
150
  agentId,
259
- threadId,
260
151
  copilotkit.agents,
261
152
  copilotkit.runtimeConnectionStatus,
262
153
  copilotkit.runtimeUrl,
@@ -290,7 +181,7 @@ export function useAgent({
290
181
  };
291
182
 
292
183
  if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
293
- handlers.onMessagesChanged = forceUpdate;
184
+ handlers.onMessagesChanged = batchedForceUpdate;
294
185
  }
295
186
 
296
187
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) {
@@ -1,5 +1,5 @@
1
1
  import { useCallback, useEffect, useMemo, useRef } from "react";
2
- import { useCopilotKit } from "../providers/CopilotKitProvider";
2
+ import { useCopilotKit } from "../context";
3
3
  import { useCopilotChatConfiguration } from "../providers/CopilotChatConfigurationProvider";
4
4
  import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
5
5
  import type {
@@ -63,7 +63,7 @@ export function useDefaultRenderTool(
63
63
  );
64
64
  }
65
65
 
66
- function DefaultToolCallRenderer({
66
+ export function DefaultToolCallRenderer({
67
67
  name,
68
68
  parameters,
69
69
  status,
@@ -86,6 +86,11 @@ function DefaultToolCallRenderer({
86
86
 
87
87
  return (
88
88
  <div
89
+ data-testid="copilot-tool-render"
90
+ data-tool-name={name}
91
+ data-status={statusString}
92
+ data-args={safeStringifyForAttr(parameters)}
93
+ data-result={safeStringifyForAttr(result)}
89
94
  style={{
90
95
  marginTop: "8px",
91
96
  paddingBottom: "8px",
@@ -150,6 +155,7 @@ function DefaultToolCallRenderer({
150
155
  }}
151
156
  />
152
157
  <span
158
+ data-testid="copilot-tool-render-name"
153
159
  style={{
154
160
  fontSize: "13px",
155
161
  fontWeight: 600,
@@ -164,6 +170,7 @@ function DefaultToolCallRenderer({
164
170
  </div>
165
171
 
166
172
  <span
173
+ data-testid="copilot-tool-render-status"
167
174
  style={{
168
175
  display: "inline-flex",
169
176
  alignItems: "center",
@@ -252,3 +259,13 @@ function DefaultToolCallRenderer({
252
259
  </div>
253
260
  );
254
261
  }
262
+
263
+ function safeStringifyForAttr(value: unknown): string {
264
+ if (value === undefined || value === null) return "";
265
+ if (typeof value === "string") return value;
266
+ try {
267
+ return JSON.stringify(value);
268
+ } catch {
269
+ return String(value);
270
+ }
271
+ }
@@ -1,5 +1,5 @@
1
1
  import { useEffect } from "react";
2
- import { useCopilotKit } from "../providers/CopilotKitProvider";
2
+ import { useCopilotKit } from "../context";
3
3
  import type { ReactFrontendTool } from "../types/frontend-tool";
4
4
 
5
5
  const EMPTY_DEPS: ReadonlyArray<unknown> = [];
@@ -42,5 +42,5 @@ export function useFrontendTool<
42
42
  // Depend on stable keys by default and allow callers to opt into
43
43
  // additional dependencies for dynamic tool configuration.
44
44
  // tool.available is included so toggling availability re-registers the tool.
45
- }, [tool.name, tool.available, copilotkit, extraDeps.length, ...extraDeps]);
45
+ }, [tool.name, tool.available, copilotkit, JSON.stringify(extraDeps)]);
46
46
  }
@@ -1,4 +1,4 @@
1
- import { useCopilotKit } from "../providers/CopilotKitProvider";
1
+ import { useCopilotKit } from "../context";
2
2
  import type { ReactFrontendTool } from "../types/frontend-tool";
3
3
  import type { ReactHumanInTheLoop } from "../types/human-in-the-loop";
4
4
  import type { ReactToolCallRenderer } from "../types/react-tool-call-renderer";
@@ -5,7 +5,7 @@ import React, {
5
5
  useMemo,
6
6
  useRef,
7
7
  } from "react";
8
- import { useCopilotKit } from "../providers/CopilotKitProvider";
8
+ import { useCopilotKit } from "../context";
9
9
  import { useAgent } from "./use-agent";
10
10
  import type {
11
11
  InterruptEvent,
@@ -3,12 +3,10 @@ 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";
7
6
 
8
7
  export function useRenderActivityMessage() {
9
8
  const { copilotkit } = useCopilotKit();
10
- const config = useCopilotChatConfiguration();
11
- const agentId = config?.agentId ?? DEFAULT_AGENT_ID;
9
+ const agentId = useCopilotChatConfiguration()?.agentId ?? DEFAULT_AGENT_ID;
12
10
 
13
11
  const renderers = copilotkit.renderActivityMessages;
14
12
 
@@ -52,13 +50,7 @@ export function useRenderActivityMessage() {
52
50
  }
53
51
 
54
52
  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;
53
+ const agent = copilotkit.getAgent(agentId);
62
54
 
63
55
  return (
64
56
  <Component
@@ -70,7 +62,7 @@ export function useRenderActivityMessage() {
70
62
  />
71
63
  );
72
64
  },
73
- [agentId, config?.threadId, copilotkit, findRenderer],
65
+ [agentId, copilotkit, findRenderer],
74
66
  );
75
67
 
76
68
  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
  return null;
49
44
  }
@@ -1,11 +1,12 @@
1
1
  import React, { useCallback, useMemo, useSyncExternalStore } from "react";
2
2
  import { ToolCall, ToolMessage } from "@ag-ui/core";
3
3
  import { ToolCallStatus } from "@copilotkit/core";
4
- import { useCopilotKit } from "../providers/CopilotKitProvider";
4
+ import { useCopilotKit } from "../context";
5
5
  import { useCopilotChatConfiguration } from "../providers/CopilotChatConfigurationProvider";
6
6
  import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
7
7
  import { partialJSONParse } from "@copilotkit/shared";
8
8
  import { ReactToolCallRenderer } from "../types/react-tool-call-renderer";
9
+ import { DefaultToolCallRenderer } from "./use-default-render-tool";
9
10
 
10
11
  export interface UseRenderToolCallProps {
11
12
  toolCall: ToolCall;
@@ -153,11 +154,12 @@ export function useRenderToolCall() {
153
154
  exactMatches[0] ||
154
155
  renderToolCalls.find((rc) => rc.name === "*");
155
156
 
156
- if (!renderConfig) {
157
- return null;
158
- }
159
-
160
- const RenderComponent = renderConfig.render;
157
+ // Fall back to the framework's built-in default tool-call renderer
158
+ // when neither a per-tool nor a wildcard renderer has been
159
+ // registered. This makes "zero custom renderers" demos paint tool
160
+ // calls out-of-the-box instead of going invisible.
161
+ const RenderComponent = (renderConfig?.render ??
162
+ defaultToolCallRenderAdapter) as ReactToolCallRenderer<unknown>["render"];
161
163
  const isExecuting = executingToolCallIds.has(toolCall.id);
162
164
 
163
165
  // Use the memoized ToolCallRenderer component to prevent unnecessary re-renders
@@ -176,3 +178,31 @@ export function useRenderToolCall() {
176
178
 
177
179
  return renderToolCall;
178
180
  }
181
+
182
+ // Adapter that bridges the ReactToolCallRenderer signature
183
+ // (`{ name, args, status, result, toolCallId }`) to the
184
+ // `DefaultToolCallRenderer` signature (`{ name, parameters, status,
185
+ // result }`) so the latter can be used as a zero-config fallback when
186
+ // no `*` renderer is registered.
187
+ function defaultToolCallRenderAdapter(props: {
188
+ name: string;
189
+ args: unknown;
190
+ status: ToolCallStatus;
191
+ result: string | undefined;
192
+ toolCallId: string;
193
+ }): React.ReactElement {
194
+ const status =
195
+ props.status === ToolCallStatus.Complete
196
+ ? "complete"
197
+ : props.status === ToolCallStatus.Executing
198
+ ? "executing"
199
+ : "inProgress";
200
+ return (
201
+ <DefaultToolCallRenderer
202
+ name={props.name}
203
+ parameters={props.args}
204
+ status={status}
205
+ result={props.result}
206
+ />
207
+ );
208
+ }
@@ -1,6 +1,6 @@
1
1
  import { useEffect } from "react";
2
2
  import type { StandardSchemaV1, InferSchemaOutput } from "@copilotkit/shared";
3
- import { useCopilotKit } from "../providers/CopilotKitProvider";
3
+ import { useCopilotKit } from "../context";
4
4
  import { defineToolCallRenderer } from "../types/defineToolCallRenderer";
5
5
 
6
6
  const EMPTY_DEPS: ReadonlyArray<unknown> = [];
@@ -180,5 +180,5 @@ export function useRenderTool<S extends StandardSchemaV1>(
180
180
  copilotkit.addHookRenderToolCall(renderer);
181
181
 
182
182
  // No cleanup removal — keeps renderer for chat history, same as useFrontendTool
183
- }, [config.name, copilotkit, extraDeps.length, ...extraDeps]);
183
+ }, [config.name, copilotkit, JSON.stringify(extraDeps)]);
184
184
  }
@@ -1,6 +1,6 @@
1
1
  import { useCallback, useEffect, useMemo, useState } from "react";
2
2
  import { Suggestion } from "@copilotkit/core";
3
- import { useCopilotKit } from "../providers/CopilotKitProvider";
3
+ import { useCopilotKit } from "../context";
4
4
  import { useCopilotChatConfiguration } from "../providers/CopilotChatConfigurationProvider";
5
5
  import { DEFAULT_AGENT_ID } from "@copilotkit/shared";
6
6
 
@@ -1,4 +1,4 @@
1
- import { useCopilotKit } from "../providers/CopilotKitProvider";
1
+ import { useCopilotKit } from "../context";
2
2
  import {
3
3
  CopilotKitCoreRuntimeConnectionStatus,
4
4
  ɵcreateThreadStore,
@@ -4,8 +4,6 @@ import type { AbstractAgent } from "@ag-ui/client";
4
4
  import type { FrontendTool } from "@copilotkit/core";
5
5
  import type React from "react";
6
6
  import {
7
- createContext,
8
- useContext,
9
7
  type ReactNode,
10
8
  useMemo,
11
9
  useEffect,
@@ -14,6 +12,14 @@ import {
14
12
  useRef,
15
13
  useState,
16
14
  } from "react";
15
+ // Context extracted to ../context.ts for cross-platform reuse (React Native)
16
+ import {
17
+ CopilotKitContext,
18
+ type CopilotKitContextValue,
19
+ LicenseContext,
20
+ } from "../context";
21
+ export type { CopilotKitContextValue } from "../context";
22
+ export { CopilotKitContext, useLicenseContext } from "../context";
17
23
  import { z } from "zod";
18
24
  import { CopilotKitInspector } from "../components/CopilotKitInspector";
19
25
  import type { Anchor } from "@copilotkit/web-inspector";
@@ -81,34 +87,6 @@ const GENERATE_SANDBOXED_UI_DESCRIPTION =
81
87
  "3. html (streams in live — the user watches the UI build as HTML is generated)\n" +
82
88
  "4. jsFunctions (reusable helper functions)\n" +
83
89
  "5. jsExpressions (applied one-by-one — the user sees each expression take effect)";
84
-
85
- // Define the context value interface - idiomatic React naming
86
- export interface CopilotKitContextValue {
87
- copilotkit: CopilotKitCoreReact;
88
- /**
89
- * Set of tool call IDs currently being executed.
90
- * This is tracked at the provider level to ensure tool execution events
91
- * are captured even before child components mount.
92
- */
93
- executingToolCallIds: ReadonlySet<string>;
94
- }
95
-
96
- // Empty set for default context value
97
- const EMPTY_SET: ReadonlySet<string> = new Set();
98
-
99
- // Create the CopilotKit context
100
- const CopilotKitContext = createContext<CopilotKitContextValue>({
101
- copilotkit: null!,
102
- executingToolCallIds: EMPTY_SET,
103
- });
104
-
105
- const LicenseContext = createContext<LicenseContextValue>(
106
- createLicenseContextValue(null),
107
- );
108
-
109
- export const useLicenseContext = (): LicenseContextValue =>
110
- useContext(LicenseContext);
111
-
112
90
  // Provider props interface
113
91
  export interface CopilotKitProviderProps {
114
92
  children: ReactNode;
@@ -642,15 +620,17 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
642
620
  }, [onError]);
643
621
 
644
622
  useEffect(() => {
645
- if (!onErrorRef.current) return;
646
-
647
623
  const subscription = copilotkit.subscribe({
648
624
  onError: (event) => {
649
- onErrorRef.current?.({
650
- error: event.error,
651
- code: event.code,
652
- context: event.context,
653
- });
625
+ if (onErrorRef.current) {
626
+ onErrorRef.current(event);
627
+ } else {
628
+ console.error(
629
+ `[CopilotKit] Error (${event.code}):`,
630
+ event.error,
631
+ event.context ?? {},
632
+ );
633
+ }
654
634
  },
655
635
  });
656
636
 
@@ -816,25 +796,5 @@ export const CopilotKitProvider: React.FC<CopilotKitProviderProps> = ({
816
796
  );
817
797
  };
818
798
 
819
- // Hook to use the CopilotKit instance - returns the full context value
820
- export const useCopilotKit = (): CopilotKitContextValue => {
821
- const context = useContext(CopilotKitContext);
822
- const [, forceUpdate] = useReducer((x) => x + 1, 0);
823
-
824
- if (!context) {
825
- throw new Error("useCopilotKit must be used within CopilotKitProvider");
826
- }
827
- useEffect(() => {
828
- const subscription = context.copilotkit.subscribe({
829
- onRuntimeConnectionStatusChanged: () => {
830
- forceUpdate();
831
- },
832
- });
833
- return () => {
834
- subscription.unsubscribe();
835
- };
836
- // eslint-disable-next-line react-hooks/exhaustive-deps
837
- }, []);
838
-
839
- return context;
840
- };
799
+ // Re-export useCopilotKit from context for backward compatibility
800
+ export { useCopilotKit } from "../context";