@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.
- package/dist/{copilotkit-opur-20s.d.mts → copilotkit-3mXoM0Hd.d.mts} +9 -29
- package/dist/copilotkit-3mXoM0Hd.d.mts.map +1 -0
- package/dist/{copilotkit-EfopO2gn.d.cts → copilotkit-BDDjvB-p.d.cts} +9 -29
- package/dist/copilotkit-BDDjvB-p.d.cts.map +1 -0
- package/dist/{copilotkit-BoOnQHlE.cjs → copilotkit-BkcqmpWt.cjs} +162 -280
- package/dist/copilotkit-BkcqmpWt.cjs.map +1 -0
- package/dist/{copilotkit-Bm4ox8G0.mjs → copilotkit-C7n8Umv9.mjs} +164 -276
- package/dist/copilotkit-C7n8Umv9.mjs.map +1 -0
- package/dist/index.cjs +4 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +4 -9
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +143 -230
- package/dist/index.umd.js.map +1 -1
- package/dist/v2/index.cjs +1 -2
- package/dist/v2/index.d.cts +2 -2
- package/dist/v2/index.d.mts +2 -2
- package/dist/v2/index.mjs +2 -2
- package/dist/v2/index.umd.js +165 -279
- package/dist/v2/index.umd.js.map +1 -1
- package/package.json +6 -6
- package/src/components/copilot-provider/copilot-messages.tsx +24 -39
- package/src/components/copilot-provider/copilotkit-props.tsx +5 -9
- package/src/components/copilot-provider/copilotkit.tsx +1 -4
- package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +16 -27
- package/src/hooks/use-copilot-chat_internal.ts +4 -15
- package/src/v2/__tests__/utils/test-helpers.tsx +7 -40
- package/src/v2/components/chat/CopilotChat.tsx +1 -1
- package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +15 -18
- package/src/v2/components/chat/CopilotChatMessageView.tsx +2 -7
- package/src/v2/components/chat/CopilotChatReasoningMessage.tsx +4 -17
- package/src/v2/components/chat/CopilotChatUserMessage.tsx +10 -13
- package/src/v2/components/chat/__tests__/CopilotChat.e2e.test.tsx +5 -131
- package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +0 -60
- package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.test.tsx +1 -1
- package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +2 -5
- package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +2 -5
- package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +1 -55
- package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +0 -8
- package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +10 -10
- package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +2 -13
- package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +4 -23
- package/src/v2/hooks/index.ts +0 -1
- package/src/v2/hooks/use-agent.tsx +10 -157
- package/src/v2/hooks/use-render-activity-message.tsx +3 -9
- package/src/v2/hooks/use-render-custom-messages.tsx +1 -6
- package/src/v2/providers/CopilotKitProvider.tsx +2 -6
- package/dist/copilotkit-Bm4ox8G0.mjs.map +0 -1
- package/dist/copilotkit-BoOnQHlE.cjs.map +0 -1
- package/dist/copilotkit-EfopO2gn.d.cts.map +0 -1
- package/dist/copilotkit-opur-20s.d.mts.map +0 -1
- package/src/components/copilot-provider/__tests__/error-visibility-prod.test.tsx +0 -70
- package/src/v2/components/chat/__tests__/CopilotChatCopyButton.clipboard.test.tsx +0 -241
- package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +0 -327
- package/src/v2/hooks/__tests__/use-capabilities.test.tsx +0 -76
- package/src/v2/hooks/use-capabilities.tsx +0 -25
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/react-core",
|
|
3
|
-
"version": "1.55.3-canary.
|
|
3
|
+
"version": "1.55.3-canary.1776979102",
|
|
4
4
|
"private": false,
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -73,11 +73,11 @@
|
|
|
73
73
|
"untruncate-json": "^0.0.1",
|
|
74
74
|
"use-stick-to-bottom": "^1.1.1",
|
|
75
75
|
"zod-to-json-schema": "^3.24.5",
|
|
76
|
-
"@copilotkit/a2ui-renderer": "1.55.3-canary.
|
|
77
|
-
"@copilotkit/core": "1.55.3-canary.
|
|
78
|
-
"@copilotkit/
|
|
79
|
-
"@copilotkit/
|
|
80
|
-
"@copilotkit/web-inspector": "1.55.3-canary.
|
|
76
|
+
"@copilotkit/a2ui-renderer": "1.55.3-canary.1776979102",
|
|
77
|
+
"@copilotkit/core": "1.55.3-canary.1776979102",
|
|
78
|
+
"@copilotkit/runtime-client-gql": "1.55.3-canary.1776979102",
|
|
79
|
+
"@copilotkit/shared": "1.55.3-canary.1776979102",
|
|
80
|
+
"@copilotkit/web-inspector": "1.55.3-canary.1776979102"
|
|
81
81
|
},
|
|
82
82
|
"devDependencies": {
|
|
83
83
|
"@tailwindcss/cli": "^4.1.11",
|
|
@@ -31,33 +31,6 @@ import {
|
|
|
31
31
|
} from "@copilotkit/shared";
|
|
32
32
|
import { Suggestion } from "@copilotkit/core";
|
|
33
33
|
|
|
34
|
-
/**
|
|
35
|
-
* Determine whether a GraphQL error should be suppressed based on its visibility
|
|
36
|
-
* and whether the dev console is active.
|
|
37
|
-
*
|
|
38
|
-
* Returns `null` when the error should be surfaced to the UI, or a log prefix
|
|
39
|
-
* string when the error should be suppressed (logged to console only).
|
|
40
|
-
*
|
|
41
|
-
* Exported for unit testing.
|
|
42
|
-
*/
|
|
43
|
-
export function getErrorSuppression(
|
|
44
|
-
visibility: ErrorVisibility | undefined,
|
|
45
|
-
isDev: boolean,
|
|
46
|
-
): string | null {
|
|
47
|
-
// Silent errors are always suppressed
|
|
48
|
-
if (visibility === ErrorVisibility.SILENT) {
|
|
49
|
-
return "CopilotKit Silent Error:";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// DEV_ONLY errors are suppressed in production
|
|
53
|
-
if (!isDev && visibility === ErrorVisibility.DEV_ONLY) {
|
|
54
|
-
return "CopilotKit Error (hidden in production):";
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// All other visibilities (TOAST, BANNER, undefined) are always surfaced
|
|
58
|
-
return null;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
34
|
// Helper to determine if error should show as banner based on visibility and legacy patterns
|
|
62
35
|
function shouldShowAsBanner(gqlError: GraphQLError): boolean {
|
|
63
36
|
const extensions = gqlError.extensions;
|
|
@@ -251,13 +224,20 @@ export function CopilotMessages({ children }: { children: ReactNode }) {
|
|
|
251
224
|
const visibility = extensions?.visibility as ErrorVisibility;
|
|
252
225
|
const isDev = shouldShowDevConsole(showDevConsole);
|
|
253
226
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
227
|
+
if (!isDev) {
|
|
228
|
+
console.error(
|
|
229
|
+
"CopilotKit Error (hidden in production):",
|
|
230
|
+
gqlError.message,
|
|
231
|
+
);
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Silent errors - just log
|
|
236
|
+
if (visibility === ErrorVisibility.SILENT) {
|
|
237
|
+
console.error("CopilotKit Silent Error:", gqlError.message);
|
|
257
238
|
return;
|
|
258
239
|
}
|
|
259
240
|
|
|
260
|
-
// TOAST and BANNER errors are always surfaced, even in production
|
|
261
241
|
// All other errors (including DEV_ONLY) show as banners for consistency
|
|
262
242
|
const ckError = createStructuredError(gqlError);
|
|
263
243
|
if (ckError) {
|
|
@@ -279,14 +259,19 @@ export function CopilotMessages({ children }: { children: ReactNode }) {
|
|
|
279
259
|
// Process all errors as banners
|
|
280
260
|
graphQLErrors.forEach(routeError);
|
|
281
261
|
} else {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
262
|
+
const isDev = shouldShowDevConsole(showDevConsole);
|
|
263
|
+
if (!isDev) {
|
|
264
|
+
console.error("CopilotKit Error (hidden in production):", error);
|
|
265
|
+
} else {
|
|
266
|
+
// Route non-GraphQL errors to banner as well
|
|
267
|
+
const fallbackError = new CopilotKitError({
|
|
268
|
+
message: error?.message || String(error),
|
|
269
|
+
code: CopilotKitErrorCode.UNKNOWN,
|
|
270
|
+
});
|
|
271
|
+
setBannerError(fallbackError);
|
|
272
|
+
// Trace the non-GraphQL error
|
|
273
|
+
traceUIError(fallbackError, error);
|
|
274
|
+
}
|
|
290
275
|
}
|
|
291
276
|
},
|
|
292
277
|
[setBannerError, showDevConsole, traceUIError],
|
|
@@ -68,19 +68,15 @@ export interface CopilotKitProps extends Omit<
|
|
|
68
68
|
|
|
69
69
|
/**
|
|
70
70
|
* Additional headers to be sent with the request.
|
|
71
|
-
* Can be a static object or a function that returns headers dynamically
|
|
72
|
-
* (useful for refreshing auth tokens).
|
|
73
71
|
*
|
|
74
72
|
* For example:
|
|
75
|
-
* ```
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* // Dynamic headers (re-evaluated on each render)
|
|
80
|
-
* headers={() => ({ "Authorization": `Bearer ${getToken()}` })}
|
|
73
|
+
* ```json
|
|
74
|
+
* {
|
|
75
|
+
* "Authorization": "Bearer X"
|
|
76
|
+
* }
|
|
81
77
|
* ```
|
|
82
78
|
*/
|
|
83
|
-
headers?: Record<string, string
|
|
79
|
+
headers?: Record<string, string>;
|
|
84
80
|
|
|
85
81
|
/**
|
|
86
82
|
* The children to be rendered within the CopilotKit.
|
|
@@ -311,10 +311,7 @@ export function CopilotKitInternal(cpkProps: CopilotKitProps) {
|
|
|
311
311
|
publicApiKey: publicApiKey,
|
|
312
312
|
...(cloud ? { cloud } : {}),
|
|
313
313
|
chatApiEndpoint: chatApiEndpoint,
|
|
314
|
-
headers:
|
|
315
|
-
typeof props.headers === "function"
|
|
316
|
-
? props.headers()
|
|
317
|
-
: props.headers || {},
|
|
314
|
+
headers: props.headers || {},
|
|
318
315
|
properties: props.properties || {},
|
|
319
316
|
transcribeAudioUrl: props.transcribeAudioUrl,
|
|
320
317
|
textToSpeechUrl: props.textToSpeechUrl,
|
|
@@ -178,32 +178,18 @@ describe("useCopilotChatInternal – connectAgent guard", () => {
|
|
|
178
178
|
});
|
|
179
179
|
});
|
|
180
180
|
|
|
181
|
-
it("does not call connectAgent when threadId matches
|
|
182
|
-
// useAgent now returns a per-thread clone, so the wrapper guards via
|
|
183
|
-
// lastConnectedAgentRef: connect fires once per agent instance, not once
|
|
184
|
-
// per render. After the first connect, further re-renders with the same
|
|
185
|
-
// agent do not trigger another connect.
|
|
181
|
+
it("does not call connectAgent when threadId matches agent's threadId", () => {
|
|
186
182
|
mockRuntimeConnectionStatus =
|
|
187
183
|
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
188
|
-
mockAgent.threadId = "config-thread-id";
|
|
184
|
+
mockAgent.threadId = "config-thread-id"; // same as mockConfigThreadId
|
|
189
185
|
applyMocks();
|
|
190
186
|
|
|
191
|
-
|
|
192
|
-
wrapper: createWrapper(),
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
await vi.waitFor(() => {
|
|
196
|
-
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
197
|
-
});
|
|
187
|
+
renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
|
|
198
188
|
|
|
199
|
-
|
|
200
|
-
rerender();
|
|
201
|
-
await vi.waitFor(() => {
|
|
202
|
-
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
203
|
-
});
|
|
189
|
+
expect(mockConnectAgent).not.toHaveBeenCalled();
|
|
204
190
|
});
|
|
205
191
|
|
|
206
|
-
it("
|
|
192
|
+
it("does not call connectAgent when config threadId is missing", () => {
|
|
207
193
|
mockRuntimeConnectionStatus =
|
|
208
194
|
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
209
195
|
mockConfigThreadId = undefined;
|
|
@@ -211,12 +197,10 @@ describe("useCopilotChatInternal – connectAgent guard", () => {
|
|
|
211
197
|
|
|
212
198
|
renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
|
|
213
199
|
|
|
214
|
-
|
|
215
|
-
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
216
|
-
});
|
|
200
|
+
expect(mockConnectAgent).not.toHaveBeenCalled();
|
|
217
201
|
});
|
|
218
202
|
|
|
219
|
-
it("calls connectAgent when
|
|
203
|
+
it("calls connectAgent when all guard conditions are met", async () => {
|
|
220
204
|
mockRuntimeConnectionStatus =
|
|
221
205
|
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
222
206
|
mockAgent.threadId = "old-thread-id"; // differs from config
|
|
@@ -230,13 +214,18 @@ describe("useCopilotChatInternal – connectAgent guard", () => {
|
|
|
230
214
|
});
|
|
231
215
|
});
|
|
232
216
|
|
|
233
|
-
it("
|
|
217
|
+
it("sets agent.threadId to config threadId before calling connectAgent", async () => {
|
|
218
|
+
mockRuntimeConnectionStatus =
|
|
219
|
+
CopilotKitCoreRuntimeConnectionStatus.Connected;
|
|
220
|
+
mockAgent.threadId = "old-thread-id";
|
|
234
221
|
applyMocks();
|
|
235
222
|
|
|
236
223
|
renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
|
|
237
224
|
|
|
238
|
-
|
|
239
|
-
expect.
|
|
240
|
-
);
|
|
225
|
+
await vi.waitFor(() => {
|
|
226
|
+
expect(mockConnectAgent).toHaveBeenCalledTimes(1);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
expect(mockAgent.threadId).toBe("config-thread-id");
|
|
241
230
|
});
|
|
242
231
|
});
|
|
@@ -337,17 +337,7 @@ export function useCopilotChatInternal({
|
|
|
337
337
|
|
|
338
338
|
// Apply priority: props > existing config > defaults
|
|
339
339
|
const resolvedAgentId = existingConfig?.agentId ?? "default";
|
|
340
|
-
const { agent } = useAgent({
|
|
341
|
-
agentId: resolvedAgentId,
|
|
342
|
-
threadId: existingConfig?.threadId,
|
|
343
|
-
});
|
|
344
|
-
|
|
345
|
-
// Track the last agent instance we called connect() on. Without this,
|
|
346
|
-
// connect() fires on every render where status is Connected — including
|
|
347
|
-
// unrelated context re-renders and StrictMode double-invocations.
|
|
348
|
-
// The ref is reset in the cleanup so that remounts (StrictMode, real
|
|
349
|
-
// unmount/remount) always trigger a fresh connect.
|
|
350
|
-
const lastConnectedAgentRef = useRef<AbstractAgent | null>(null);
|
|
340
|
+
const { agent } = useAgent({ agentId: resolvedAgentId });
|
|
351
341
|
|
|
352
342
|
useEffect(() => {
|
|
353
343
|
let detached = false;
|
|
@@ -382,19 +372,18 @@ export function useCopilotChatInternal({
|
|
|
382
372
|
};
|
|
383
373
|
if (
|
|
384
374
|
agent &&
|
|
385
|
-
|
|
375
|
+
existingConfig?.threadId &&
|
|
376
|
+
agent.threadId !== existingConfig.threadId &&
|
|
386
377
|
copilotkit.runtimeConnectionStatus ===
|
|
387
378
|
CopilotKitCoreRuntimeConnectionStatus.Connected
|
|
388
379
|
) {
|
|
389
|
-
|
|
380
|
+
agent.threadId = existingConfig.threadId;
|
|
390
381
|
connect(agent);
|
|
391
382
|
}
|
|
392
383
|
return () => {
|
|
393
384
|
// Abort the HTTP request and detach the active run.
|
|
394
385
|
// This is critical for React StrictMode which unmounts+remounts in dev,
|
|
395
386
|
// preventing duplicate /connect requests from reaching the server.
|
|
396
|
-
// Reset the ref so remounts always trigger a fresh connect.
|
|
397
|
-
lastConnectedAgentRef.current = null;
|
|
398
387
|
detached = true;
|
|
399
388
|
connectAbortController.abort();
|
|
400
389
|
agent?.detachActiveRun();
|
|
@@ -51,18 +51,9 @@ export class MockStepwiseAgent extends AbstractAgent {
|
|
|
51
51
|
});
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
clone():
|
|
55
|
-
//
|
|
56
|
-
|
|
57
|
-
// clone() contract (must return a distinct object).
|
|
58
|
-
// Use the concrete constructor so subclasses (e.g. FailingConnectAgent)
|
|
59
|
-
// preserve their overridden methods.
|
|
60
|
-
const cloned = new (this
|
|
61
|
-
.constructor as new () => MockStepwiseAgent)() as this;
|
|
62
|
-
cloned.agentId = this.agentId;
|
|
63
|
-
(cloned as unknown as { subject: Subject<BaseEvent> }).subject =
|
|
64
|
-
this.subject;
|
|
65
|
-
return cloned;
|
|
54
|
+
clone(): MockStepwiseAgent {
|
|
55
|
+
// For tests, return same instance so we can keep controlling it
|
|
56
|
+
return this;
|
|
66
57
|
}
|
|
67
58
|
|
|
68
59
|
// No-op: prevent the base class from tearing down the Subject
|
|
@@ -119,21 +110,7 @@ export class MockReconnectableAgent extends AbstractAgent {
|
|
|
119
110
|
}
|
|
120
111
|
|
|
121
112
|
clone(): MockReconnectableAgent {
|
|
122
|
-
|
|
123
|
-
cloned.agentId = this.agentId;
|
|
124
|
-
(
|
|
125
|
-
cloned as unknown as {
|
|
126
|
-
subject: Subject<BaseEvent>;
|
|
127
|
-
storedEvents: BaseEvent[];
|
|
128
|
-
}
|
|
129
|
-
).subject = this.subject;
|
|
130
|
-
(
|
|
131
|
-
cloned as unknown as {
|
|
132
|
-
subject: Subject<BaseEvent>;
|
|
133
|
-
storedEvents: BaseEvent[];
|
|
134
|
-
}
|
|
135
|
-
).storedEvents = this.storedEvents;
|
|
136
|
-
return cloned;
|
|
113
|
+
return this;
|
|
137
114
|
}
|
|
138
115
|
|
|
139
116
|
// No-op: prevent the base class from tearing down the Subject
|
|
@@ -530,20 +507,10 @@ export function emitSuggestionToolCall(
|
|
|
530
507
|
* A MockStepwiseAgent that emits suggestion events when run() is called
|
|
531
508
|
*/
|
|
532
509
|
export class SuggestionsProviderAgent extends MockStepwiseAgent {
|
|
533
|
-
|
|
534
|
-
// when setSuggestions() is called after the clone is created.
|
|
535
|
-
private _shared: { suggestions: Array<{ title: string; message: string }> } =
|
|
536
|
-
{ suggestions: [] };
|
|
510
|
+
private _suggestions: Array<{ title: string; message: string }> = [];
|
|
537
511
|
|
|
538
512
|
setSuggestions(suggestions: Array<{ title: string; message: string }>) {
|
|
539
|
-
this.
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
clone(): this {
|
|
543
|
-
const cloned = super.clone();
|
|
544
|
-
(cloned as unknown as { _shared: typeof this._shared })._shared =
|
|
545
|
-
this._shared;
|
|
546
|
-
return cloned;
|
|
513
|
+
this._suggestions = suggestions;
|
|
547
514
|
}
|
|
548
515
|
|
|
549
516
|
run(_input: RunAgentInput): Observable<BaseEvent> {
|
|
@@ -558,7 +525,7 @@ export class SuggestionsProviderAgent extends MockStepwiseAgent {
|
|
|
558
525
|
emitSuggestionToolCall(this, {
|
|
559
526
|
toolCallId: testId("tc"),
|
|
560
527
|
parentMessageId: messageId,
|
|
561
|
-
suggestions: this.
|
|
528
|
+
suggestions: this._suggestions,
|
|
562
529
|
});
|
|
563
530
|
|
|
564
531
|
this.emit({ type: EventType.RUN_FINISHED } as BaseEvent);
|
|
@@ -104,7 +104,6 @@ export function CopilotChat({
|
|
|
104
104
|
|
|
105
105
|
const { agent } = useAgent({
|
|
106
106
|
agentId: resolvedAgentId,
|
|
107
|
-
threadId: resolvedThreadId,
|
|
108
107
|
throttleMs,
|
|
109
108
|
});
|
|
110
109
|
const { copilotkit } = useCopilotKit();
|
|
@@ -214,6 +213,7 @@ export function CopilotChat({
|
|
|
214
213
|
console.error("CopilotChat: connectAgent failed", error);
|
|
215
214
|
}
|
|
216
215
|
};
|
|
216
|
+
agent.threadId = resolvedThreadId;
|
|
217
217
|
connect(agent);
|
|
218
218
|
return () => {
|
|
219
219
|
// Abort the HTTP request and detach the active run.
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
import { useKatexStyles } from "../../hooks/useKatexStyles";
|
|
23
23
|
import { WithSlots, renderSlot } from "../../lib/slots";
|
|
24
24
|
import { Streamdown } from "streamdown";
|
|
25
|
-
import { copyToClipboard } from "@copilotkit/shared";
|
|
26
25
|
import CopilotChatToolCallsView from "./CopilotChatToolCallsView";
|
|
27
26
|
|
|
28
27
|
export type CopilotChatAssistantMessageProps = WithSlots<
|
|
@@ -87,9 +86,12 @@ export function CopilotChatAssistantMessage({
|
|
|
87
86
|
{
|
|
88
87
|
onClick: async () => {
|
|
89
88
|
if (message.content) {
|
|
90
|
-
|
|
89
|
+
try {
|
|
90
|
+
await navigator.clipboard.writeText(message.content);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
console.error("Failed to copy message:", err);
|
|
93
|
+
}
|
|
91
94
|
}
|
|
92
|
-
return false;
|
|
93
95
|
},
|
|
94
96
|
},
|
|
95
97
|
);
|
|
@@ -273,23 +275,18 @@ export namespace CopilotChatAssistantMessage {
|
|
|
273
275
|
};
|
|
274
276
|
}, []);
|
|
275
277
|
|
|
276
|
-
const handleClick =
|
|
277
|
-
|
|
278
|
-
if (
|
|
279
|
-
|
|
280
|
-
const result = await Promise.resolve(onClick(event));
|
|
281
|
-
success = result === true;
|
|
278
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
279
|
+
setCopied(true);
|
|
280
|
+
if (timerRef.current !== null) {
|
|
281
|
+
clearTimeout(timerRef.current);
|
|
282
282
|
}
|
|
283
|
+
timerRef.current = setTimeout(() => {
|
|
284
|
+
timerRef.current = null;
|
|
285
|
+
setCopied(false);
|
|
286
|
+
}, 2000);
|
|
283
287
|
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
if (timerRef.current !== null) {
|
|
287
|
-
clearTimeout(timerRef.current);
|
|
288
|
-
}
|
|
289
|
-
timerRef.current = setTimeout(() => {
|
|
290
|
-
timerRef.current = null;
|
|
291
|
-
setCopied(false);
|
|
292
|
-
}, 2000);
|
|
288
|
+
if (onClick) {
|
|
289
|
+
onClick(event);
|
|
293
290
|
}
|
|
294
291
|
};
|
|
295
292
|
|
|
@@ -22,7 +22,6 @@ import {
|
|
|
22
22
|
} from "@ag-ui/core";
|
|
23
23
|
import { twMerge } from "tailwind-merge";
|
|
24
24
|
import { useRenderActivityMessage, useRenderCustomMessages } from "../../hooks";
|
|
25
|
-
import { getThreadClone } from "../../hooks/use-agent";
|
|
26
25
|
import { useCopilotKit } from "../../providers/CopilotKitProvider";
|
|
27
26
|
import { useCopilotChatConfiguration } from "../../providers/CopilotChatConfigurationProvider";
|
|
28
27
|
|
|
@@ -392,18 +391,14 @@ export function CopilotChatMessageView({
|
|
|
392
391
|
// Subscribe to state changes so custom message renderers re-render when state updates.
|
|
393
392
|
useEffect(() => {
|
|
394
393
|
if (!config?.agentId) return;
|
|
395
|
-
const
|
|
396
|
-
// Prefer the per-thread clone so that state changes from the running agent
|
|
397
|
-
// (which is the clone, not the registry) trigger re-renders.
|
|
398
|
-
const agent =
|
|
399
|
-
getThreadClone(registryAgent, config.threadId) ?? registryAgent;
|
|
394
|
+
const agent = copilotkit.getAgent(config.agentId);
|
|
400
395
|
if (!agent) return;
|
|
401
396
|
|
|
402
397
|
const subscription = agent.subscribe({
|
|
403
398
|
onStateChanged: forceUpdate,
|
|
404
399
|
});
|
|
405
400
|
return () => subscription.unsubscribe();
|
|
406
|
-
}, [config?.agentId,
|
|
401
|
+
}, [config?.agentId, copilotkit, forceUpdate]);
|
|
407
402
|
|
|
408
403
|
// Subscribe to interrupt element changes for in-chat rendering.
|
|
409
404
|
const [interruptElement, setInterruptElement] =
|
|
@@ -71,31 +71,18 @@ export function CopilotChatReasoningMessage({
|
|
|
71
71
|
return () => clearInterval(timer);
|
|
72
72
|
}, [isStreaming]);
|
|
73
73
|
|
|
74
|
-
// Default to open while streaming, auto-collapse when streaming ends
|
|
75
|
-
// Track whether the user has manually toggled so auto-collapse doesn't
|
|
76
|
-
// override their explicit intent (prevents flaky test failures on CI
|
|
77
|
-
// where async forceUpdate timing can race with click handlers).
|
|
74
|
+
// Default to open while streaming, auto-collapse when streaming ends
|
|
78
75
|
const [isOpen, setIsOpen] = useState(isStreaming);
|
|
79
|
-
const userToggledRef = useRef(false);
|
|
80
76
|
|
|
81
77
|
useEffect(() => {
|
|
82
78
|
if (isStreaming) {
|
|
83
|
-
// Reset user-toggle tracking when a new streaming session starts
|
|
84
|
-
userToggledRef.current = false;
|
|
85
79
|
setIsOpen(true);
|
|
86
|
-
} else
|
|
87
|
-
// Auto-collapse
|
|
80
|
+
} else {
|
|
81
|
+
// Auto-collapse when reasoning finishes
|
|
88
82
|
setIsOpen(false);
|
|
89
83
|
}
|
|
90
84
|
}, [isStreaming]);
|
|
91
85
|
|
|
92
|
-
const handleToggle = hasContent
|
|
93
|
-
? () => {
|
|
94
|
-
userToggledRef.current = true;
|
|
95
|
-
setIsOpen((prev) => !prev);
|
|
96
|
-
}
|
|
97
|
-
: undefined;
|
|
98
|
-
|
|
99
86
|
const label = isStreaming
|
|
100
87
|
? "Thinking…"
|
|
101
88
|
: `Thought for ${formatDuration(elapsed)}`;
|
|
@@ -105,7 +92,7 @@ export function CopilotChatReasoningMessage({
|
|
|
105
92
|
label,
|
|
106
93
|
hasContent,
|
|
107
94
|
isStreaming,
|
|
108
|
-
onClick:
|
|
95
|
+
onClick: hasContent ? () => setIsOpen((prev) => !prev) : undefined,
|
|
109
96
|
});
|
|
110
97
|
|
|
111
98
|
const boundContent = renderSlot(
|
|
@@ -18,7 +18,6 @@ import {
|
|
|
18
18
|
type AudioInputPart,
|
|
19
19
|
type VideoInputPart,
|
|
20
20
|
type DocumentInputPart,
|
|
21
|
-
copyToClipboard,
|
|
22
21
|
} from "@copilotkit/shared";
|
|
23
22
|
import { CopilotChatAttachmentRenderer } from "./CopilotChatAttachmentRenderer";
|
|
24
23
|
|
|
@@ -148,9 +147,12 @@ export function CopilotChatUserMessage({
|
|
|
148
147
|
{
|
|
149
148
|
onClick: async () => {
|
|
150
149
|
if (flattenedContent) {
|
|
151
|
-
|
|
150
|
+
try {
|
|
151
|
+
await navigator.clipboard.writeText(flattenedContent);
|
|
152
|
+
} catch (err) {
|
|
153
|
+
console.error("Failed to copy message:", err);
|
|
154
|
+
}
|
|
152
155
|
}
|
|
153
|
-
return false;
|
|
154
156
|
},
|
|
155
157
|
},
|
|
156
158
|
);
|
|
@@ -312,17 +314,12 @@ export namespace CopilotChatUserMessage {
|
|
|
312
314
|
const labels = config?.labels ?? CopilotChatDefaultLabels;
|
|
313
315
|
const [copied, setCopied] = useState(false);
|
|
314
316
|
|
|
315
|
-
const handleClick =
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
// onClick may return a boolean indicating copy success
|
|
319
|
-
const result = await Promise.resolve(onClick(event));
|
|
320
|
-
success = result === true;
|
|
321
|
-
}
|
|
317
|
+
const handleClick = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
318
|
+
setCopied(true);
|
|
319
|
+
setTimeout(() => setCopied(false), 2000);
|
|
322
320
|
|
|
323
|
-
if (
|
|
324
|
-
|
|
325
|
-
setTimeout(() => setCopied(false), 2000);
|
|
321
|
+
if (onClick) {
|
|
322
|
+
onClick(event);
|
|
326
323
|
}
|
|
327
324
|
};
|
|
328
325
|
|