@copilotkit/react-core 1.52.2-next.2 → 1.52.2-next.3

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/package.json CHANGED
@@ -10,7 +10,7 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "version": "1.52.2-next.2",
13
+ "version": "1.52.2-next.3",
14
14
  "sideEffects": [
15
15
  "**/*.css"
16
16
  ],
@@ -55,14 +55,14 @@
55
55
  "tsconfig": "1.4.10"
56
56
  },
57
57
  "dependencies": {
58
- "@ag-ui/client": "^0.0.46",
58
+ "@ag-ui/client": "^0.0.47",
59
59
  "@scarf/scarf": "^1.3.0",
60
60
  "react-markdown": "^8.0.7",
61
61
  "untruncate-json": "^0.0.1",
62
- "@copilotkit/runtime-client-gql": "1.52.2-next.2",
63
- "@copilotkit/shared": "1.52.2-next.2",
64
- "@copilotkitnext/core": "1.52.2-next.2",
65
- "@copilotkitnext/react": "1.52.2-next.2"
62
+ "@copilotkit/runtime-client-gql": "1.52.2-next.3",
63
+ "@copilotkit/shared": "1.52.2-next.3",
64
+ "@copilotkitnext/core": "1.52.2-next.3",
65
+ "@copilotkitnext/react": "1.52.2-next.3"
66
66
  },
67
67
  "keywords": [
68
68
  "copilotkit",
@@ -637,6 +637,117 @@ export function CopilotKitInternal(cpkProps: CopilotKitProps) {
637
637
  );
638
638
  }, [registeredActionConfigs]);
639
639
 
640
+ const copilotContextValue = useMemo(
641
+ () => ({
642
+ actions,
643
+ chatComponentsCache,
644
+ getFunctionCallHandler,
645
+ setAction,
646
+ removeAction,
647
+ setRegisteredActions: handleSetRegisteredActions,
648
+ removeRegisteredAction: handleRemoveRegisteredAction,
649
+ getContextString,
650
+ addContext,
651
+ removeContext,
652
+ getAllContext,
653
+ getDocumentsContext,
654
+ addDocumentContext,
655
+ removeDocumentContext,
656
+ copilotApiConfig: copilotApiConfig,
657
+ isLoading,
658
+ setIsLoading,
659
+ chatSuggestionConfiguration,
660
+ addChatSuggestionConfiguration,
661
+ removeChatSuggestionConfiguration,
662
+ chatInstructions,
663
+ setChatInstructions,
664
+ additionalInstructions,
665
+ setAdditionalInstructions,
666
+ showDevConsole,
667
+ coagentStates,
668
+ setCoagentStates,
669
+ coagentStatesRef,
670
+ setCoagentStatesWithRef,
671
+ agentSession,
672
+ setAgentSession,
673
+ forwardedParameters,
674
+ agentLock,
675
+ threadId,
676
+ setThreadId,
677
+ runId,
678
+ setRunId,
679
+ chatAbortControllerRef,
680
+ availableAgents,
681
+ authConfig_c: props.authConfig_c,
682
+ authStates_c: authStates,
683
+ setAuthStates_c: updateAuthStates,
684
+ extensions,
685
+ setExtensions: updateExtensions,
686
+ interruptActions,
687
+ setInterruptAction,
688
+ removeInterruptAction,
689
+ interruptEventQueue,
690
+ addInterruptEvent,
691
+ resolveInterruptEvent,
692
+ bannerError,
693
+ setBannerError,
694
+ onError: handleErrors,
695
+ internalErrorHandlers,
696
+ setInternalErrorHandler,
697
+ removeInternalErrorHandler,
698
+ }),
699
+ [
700
+ actions,
701
+ chatComponentsCache,
702
+ getFunctionCallHandler,
703
+ setAction,
704
+ removeAction,
705
+ handleSetRegisteredActions,
706
+ handleRemoveRegisteredAction,
707
+ getContextString,
708
+ addContext,
709
+ removeContext,
710
+ getAllContext,
711
+ getDocumentsContext,
712
+ addDocumentContext,
713
+ removeDocumentContext,
714
+ copilotApiConfig,
715
+ isLoading,
716
+ chatSuggestionConfiguration,
717
+ addChatSuggestionConfiguration,
718
+ removeChatSuggestionConfiguration,
719
+ chatInstructions,
720
+ additionalInstructions,
721
+ showDevConsole,
722
+ coagentStates,
723
+ setCoagentStatesWithRef,
724
+ agentSession,
725
+ setAgentSession,
726
+ forwardedParameters,
727
+ agentLock,
728
+ threadId,
729
+ setThreadId,
730
+ runId,
731
+ availableAgents,
732
+ props.authConfig_c,
733
+ authStates,
734
+ updateAuthStates,
735
+ extensions,
736
+ updateExtensions,
737
+ interruptActions,
738
+ setInterruptAction,
739
+ removeInterruptAction,
740
+ interruptEventQueue,
741
+ addInterruptEvent,
742
+ resolveInterruptEvent,
743
+ bannerError,
744
+ handleErrors,
745
+ internalErrorHandlers,
746
+ setInternalErrorHandler,
747
+ removeInternalErrorHandler,
748
+ ],
749
+ );
750
+
640
751
  return (
641
752
  <CopilotChatConfigurationProvider
642
753
  // labels={labels}
@@ -644,66 +755,7 @@ export function CopilotKitInternal(cpkProps: CopilotKitProps) {
644
755
  agentId={props.agent ?? "default"}
645
756
  threadId={threadId}
646
757
  >
647
- <CopilotContext.Provider
648
- value={{
649
- actions,
650
- chatComponentsCache,
651
- getFunctionCallHandler,
652
- setAction,
653
- removeAction,
654
- setRegisteredActions: handleSetRegisteredActions,
655
- removeRegisteredAction: handleRemoveRegisteredAction,
656
- getContextString,
657
- addContext,
658
- removeContext,
659
- getAllContext,
660
- getDocumentsContext,
661
- addDocumentContext,
662
- removeDocumentContext,
663
- copilotApiConfig: copilotApiConfig,
664
- isLoading,
665
- setIsLoading,
666
- chatSuggestionConfiguration,
667
- addChatSuggestionConfiguration,
668
- removeChatSuggestionConfiguration,
669
- chatInstructions,
670
- setChatInstructions,
671
- additionalInstructions,
672
- setAdditionalInstructions,
673
- showDevConsole,
674
- coagentStates,
675
- setCoagentStates,
676
- coagentStatesRef,
677
- setCoagentStatesWithRef,
678
- agentSession,
679
- setAgentSession,
680
- forwardedParameters,
681
- agentLock,
682
- threadId,
683
- setThreadId,
684
- runId,
685
- setRunId,
686
- chatAbortControllerRef,
687
- availableAgents,
688
- authConfig_c: props.authConfig_c,
689
- authStates_c: authStates,
690
- setAuthStates_c: updateAuthStates,
691
- extensions,
692
- setExtensions: updateExtensions,
693
- interruptActions,
694
- setInterruptAction,
695
- removeInterruptAction,
696
- interruptEventQueue,
697
- addInterruptEvent,
698
- resolveInterruptEvent,
699
- bannerError,
700
- setBannerError,
701
- onError: handleErrors,
702
- internalErrorHandlers,
703
- setInternalErrorHandler,
704
- removeInternalErrorHandler,
705
- }}
706
- >
758
+ <CopilotContext.Provider value={copilotContextValue}>
707
759
  <CopilotListeners />
708
760
  <CopilotKitErrorBridge />
709
761
  <CoAgentStateRendersProvider>
@@ -0,0 +1,234 @@
1
+ import { vi, describe, it, expect, beforeEach } from "vitest";
2
+ import React from "react";
3
+ import { renderHook, act } from "@testing-library/react";
4
+ import { useCopilotChatInternal } from "../use-copilot-chat_internal";
5
+ import { CoAgentStateRendersProvider, CopilotContext } from "../../context";
6
+ import { createTestCopilotContext } from "../../test-helpers/copilot-context";
7
+ import {
8
+ useAgent,
9
+ useCopilotKit,
10
+ useCopilotChatConfiguration,
11
+ } from "@copilotkitnext/react";
12
+ import { CopilotKitCoreRuntimeConnectionStatus } from "@copilotkitnext/core";
13
+
14
+ // ---------------------------------------------------------------------------
15
+ // Mutable state that tests can tweak between renders
16
+ // ---------------------------------------------------------------------------
17
+ let mockRuntimeConnectionStatus: CopilotKitCoreRuntimeConnectionStatus =
18
+ CopilotKitCoreRuntimeConnectionStatus.Disconnected;
19
+ const mockConnectAgent = vi.fn().mockResolvedValue(undefined);
20
+
21
+ const mockAgent: Record<string, unknown> = {
22
+ messages: [],
23
+ state: {},
24
+ isRunning: false,
25
+ subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
26
+ setMessages: vi.fn(),
27
+ setState: vi.fn(),
28
+ addMessage: vi.fn(),
29
+ abortRun: vi.fn(),
30
+ runAgent: vi.fn(),
31
+ threadId: undefined as string | undefined,
32
+ };
33
+
34
+ let mockConfigThreadId: string | undefined = "config-thread-id";
35
+
36
+ // ---------------------------------------------------------------------------
37
+ // Module mocks
38
+ // ---------------------------------------------------------------------------
39
+ vi.mock("@copilotkitnext/react", () => ({
40
+ useAgent: vi.fn(() => ({ agent: mockAgent })),
41
+ useCopilotKit: vi.fn(() => ({
42
+ copilotkit: {
43
+ connectAgent: mockConnectAgent,
44
+ runtimeConnectionStatus: mockRuntimeConnectionStatus,
45
+ getRunIdForMessage: vi.fn(),
46
+ runAgent: vi.fn(),
47
+ clearSuggestions: vi.fn(),
48
+ addSuggestionsConfig: vi.fn(),
49
+ reloadSuggestions: vi.fn(),
50
+ subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
51
+ interruptElement: null,
52
+ },
53
+ })),
54
+ useCopilotChatConfiguration: vi.fn(() => ({
55
+ agentId: "test-agent",
56
+ threadId: mockConfigThreadId,
57
+ })),
58
+ useRenderCustomMessages: vi.fn(() => undefined),
59
+ useSuggestions: vi.fn(() => ({ suggestions: [], isLoading: false })),
60
+ }));
61
+
62
+ vi.mock("../../components/toast/toast-provider", () => ({
63
+ useToast: () => ({
64
+ setBannerError: vi.fn(),
65
+ addToast: vi.fn(),
66
+ }),
67
+ }));
68
+
69
+ vi.mock("../../components/error-boundary/error-utils", () => ({
70
+ useAsyncCallback: <T extends (...args: unknown[]) => unknown>(fn: T) => fn,
71
+ }));
72
+
73
+ vi.mock("../use-langgraph-interrupt-render", () => ({
74
+ useLangGraphInterruptRender: vi.fn(() => null),
75
+ }));
76
+
77
+ vi.mock("../use-lazy-tool-renderer", () => ({
78
+ useLazyToolRenderer: vi.fn(() => () => null),
79
+ }));
80
+
81
+ // ---------------------------------------------------------------------------
82
+ // Helpers
83
+ // ---------------------------------------------------------------------------
84
+
85
+ /**
86
+ * Re-apply the mock return values. Because `vi.mock` factory runs once at
87
+ * module load, we need to update the mock implementations to pick up the
88
+ * mutable variables that tests change.
89
+ */
90
+ function applyMocks() {
91
+ vi.mocked(useCopilotKit).mockReturnValue({
92
+ copilotkit: {
93
+ connectAgent: mockConnectAgent,
94
+ runtimeConnectionStatus: mockRuntimeConnectionStatus,
95
+ getRunIdForMessage: vi.fn(),
96
+ runAgent: vi.fn(),
97
+ clearSuggestions: vi.fn(),
98
+ addSuggestionsConfig: vi.fn(),
99
+ reloadSuggestions: vi.fn(),
100
+ subscribe: vi.fn(() => ({ unsubscribe: vi.fn() })),
101
+ interruptElement: null,
102
+ },
103
+ } as any);
104
+
105
+ vi.mocked(useCopilotChatConfiguration).mockReturnValue({
106
+ agentId: "test-agent",
107
+ threadId: mockConfigThreadId,
108
+ } as any);
109
+
110
+ vi.mocked(useAgent).mockReturnValue({ agent: mockAgent } as any);
111
+ }
112
+
113
+ function createWrapper() {
114
+ const copilotContextValue = createTestCopilotContext();
115
+ return function Wrapper({ children }: { children: React.ReactNode }) {
116
+ return (
117
+ <CopilotContext.Provider value={copilotContextValue}>
118
+ <CoAgentStateRendersProvider>{children}</CoAgentStateRendersProvider>
119
+ </CopilotContext.Provider>
120
+ );
121
+ };
122
+ }
123
+
124
+ // ---------------------------------------------------------------------------
125
+ // Tests
126
+ // ---------------------------------------------------------------------------
127
+ describe("useCopilotChatInternal – connectAgent guard", () => {
128
+ beforeEach(() => {
129
+ vi.clearAllMocks();
130
+ mockRuntimeConnectionStatus =
131
+ CopilotKitCoreRuntimeConnectionStatus.Disconnected;
132
+ mockConnectAgent.mockResolvedValue(undefined);
133
+ mockAgent.threadId = undefined;
134
+ mockAgent.messages = [];
135
+ mockAgent.state = {};
136
+ mockAgent.isRunning = false;
137
+ mockConfigThreadId = "config-thread-id";
138
+ });
139
+
140
+ it("does not call connectAgent when status is Disconnected", () => {
141
+ mockRuntimeConnectionStatus =
142
+ CopilotKitCoreRuntimeConnectionStatus.Disconnected;
143
+ applyMocks();
144
+
145
+ renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
146
+
147
+ expect(mockConnectAgent).not.toHaveBeenCalled();
148
+ });
149
+
150
+ it("does not call connectAgent when status is Connecting", () => {
151
+ mockRuntimeConnectionStatus =
152
+ CopilotKitCoreRuntimeConnectionStatus.Connecting;
153
+ applyMocks();
154
+
155
+ renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
156
+
157
+ expect(mockConnectAgent).not.toHaveBeenCalled();
158
+ });
159
+
160
+ it("calls connectAgent once when status transitions to Connected", async () => {
161
+ mockRuntimeConnectionStatus =
162
+ CopilotKitCoreRuntimeConnectionStatus.Disconnected;
163
+ applyMocks();
164
+
165
+ const { rerender } = renderHook(() => useCopilotChatInternal(), {
166
+ wrapper: createWrapper(),
167
+ });
168
+
169
+ expect(mockConnectAgent).not.toHaveBeenCalled();
170
+
171
+ // Transition to Connected
172
+ mockRuntimeConnectionStatus =
173
+ CopilotKitCoreRuntimeConnectionStatus.Connected;
174
+ applyMocks();
175
+
176
+ rerender();
177
+
178
+ // Wait for the async connectAgent call
179
+ await vi.waitFor(() => {
180
+ expect(mockConnectAgent).toHaveBeenCalledTimes(1);
181
+ });
182
+ });
183
+
184
+ it("does not call connectAgent when threadId matches agent's threadId", () => {
185
+ mockRuntimeConnectionStatus =
186
+ CopilotKitCoreRuntimeConnectionStatus.Connected;
187
+ mockAgent.threadId = "config-thread-id"; // same as mockConfigThreadId
188
+ applyMocks();
189
+
190
+ renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
191
+
192
+ expect(mockConnectAgent).not.toHaveBeenCalled();
193
+ });
194
+
195
+ it("does not call connectAgent when config threadId is missing", () => {
196
+ mockRuntimeConnectionStatus =
197
+ CopilotKitCoreRuntimeConnectionStatus.Connected;
198
+ mockConfigThreadId = undefined;
199
+ applyMocks();
200
+
201
+ renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
202
+
203
+ expect(mockConnectAgent).not.toHaveBeenCalled();
204
+ });
205
+
206
+ it("calls connectAgent when all guard conditions are met", async () => {
207
+ mockRuntimeConnectionStatus =
208
+ CopilotKitCoreRuntimeConnectionStatus.Connected;
209
+ mockAgent.threadId = "old-thread-id"; // differs from config
210
+ applyMocks();
211
+
212
+ renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
213
+
214
+ await vi.waitFor(() => {
215
+ expect(mockConnectAgent).toHaveBeenCalledTimes(1);
216
+ expect(mockConnectAgent).toHaveBeenCalledWith({ agent: mockAgent });
217
+ });
218
+ });
219
+
220
+ it("sets agent.threadId to config threadId before calling connectAgent", async () => {
221
+ mockRuntimeConnectionStatus =
222
+ CopilotKitCoreRuntimeConnectionStatus.Connected;
223
+ mockAgent.threadId = "old-thread-id";
224
+ applyMocks();
225
+
226
+ renderHook(() => useCopilotChatInternal(), { wrapper: createWrapper() });
227
+
228
+ await vi.waitFor(() => {
229
+ expect(mockConnectAgent).toHaveBeenCalledTimes(1);
230
+ });
231
+
232
+ expect(mockAgent.threadId).toBe("config-thread-id");
233
+ });
234
+ });
@@ -21,7 +21,10 @@ import {
21
21
  useRenderCustomMessages,
22
22
  useSuggestions,
23
23
  } from "@copilotkitnext/react";
24
- import { Suggestion } from "@copilotkitnext/core";
24
+ import {
25
+ Suggestion,
26
+ CopilotKitCoreRuntimeConnectionStatus,
27
+ } from "@copilotkitnext/core";
25
28
  import { useLazyToolRenderer } from "./use-lazy-tool-renderer";
26
29
  import { AbstractAgent, AGUIConnectNotImplementedError } from "@ag-ui/client";
27
30
  import {
@@ -350,13 +353,21 @@ export function useCopilotChatInternal({
350
353
  if (
351
354
  agent &&
352
355
  existingConfig?.threadId &&
353
- agent.threadId !== existingConfig.threadId
356
+ agent.threadId !== existingConfig.threadId &&
357
+ copilotkit.runtimeConnectionStatus ===
358
+ CopilotKitCoreRuntimeConnectionStatus.Connected
354
359
  ) {
355
360
  agent.threadId = existingConfig.threadId;
356
361
  connect(agent);
357
362
  }
358
363
  return () => {};
359
- }, [existingConfig?.threadId, agent, copilotkit, resolvedAgentId]);
364
+ }, [
365
+ existingConfig?.threadId,
366
+ agent,
367
+ copilotkit,
368
+ copilotkit.runtimeConnectionStatus,
369
+ resolvedAgentId,
370
+ ]);
360
371
 
361
372
  useEffect(() => {
362
373
  onInProgress?.(Boolean(agent?.isRunning));