@copilotkit/react-core 1.50.0-beta.0 → 1.50.0-beta.10

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 (201) hide show
  1. package/CHANGELOG.md +93 -0
  2. package/dist/{chunk-UJBV5GAG.mjs → chunk-3775VM7Y.mjs} +32 -65
  3. package/dist/chunk-3775VM7Y.mjs.map +1 -0
  4. package/dist/{chunk-3GURHDG7.mjs → chunk-4HRUQH6U.mjs} +3 -3
  5. package/dist/{chunk-7BYHZLPL.mjs → chunk-4RRMC7L2.mjs} +4 -4
  6. package/dist/chunk-4RRMC7L2.mjs.map +1 -0
  7. package/dist/{chunk-D3QSYDJR.mjs → chunk-7IBF6RBW.mjs} +2 -2
  8. package/dist/{chunk-GMI4KO4X.mjs → chunk-7SHWECGN.mjs} +2 -2
  9. package/dist/{chunk-OVYFRPSN.mjs → chunk-ABWT4DRT.mjs} +2 -2
  10. package/dist/{chunk-WVLHXIFP.mjs → chunk-AFNWX62Q.mjs} +2 -2
  11. package/dist/{chunk-WVLHXIFP.mjs.map → chunk-AFNWX62Q.mjs.map} +1 -1
  12. package/dist/{chunk-JRT5BJF3.mjs → chunk-B5ELMVT7.mjs} +2 -2
  13. package/dist/{chunk-TXI72QHK.mjs → chunk-EG56H77V.mjs} +2 -2
  14. package/dist/{chunk-DCHSCK62.mjs → chunk-FYMZKPOL.mjs} +36 -42
  15. package/dist/chunk-FYMZKPOL.mjs.map +1 -0
  16. package/dist/{chunk-FBD24VEH.mjs → chunk-HE22TZMF.mjs} +2 -2
  17. package/dist/{chunk-FBD24VEH.mjs.map → chunk-HE22TZMF.mjs.map} +1 -1
  18. package/dist/chunk-I76HKHPJ.mjs +32 -0
  19. package/dist/chunk-I76HKHPJ.mjs.map +1 -0
  20. package/dist/{chunk-LHKZJ2ND.mjs → chunk-PMWUKW3Z.mjs} +3 -3
  21. package/dist/{chunk-NROJOTQP.mjs → chunk-QNUAXSDP.mjs} +9 -6
  22. package/dist/chunk-QNUAXSDP.mjs.map +1 -0
  23. package/dist/{chunk-NG26QEGF.mjs → chunk-T2VBHAAP.mjs} +9 -3
  24. package/dist/chunk-T2VBHAAP.mjs.map +1 -0
  25. package/dist/{chunk-QU6NONOD.mjs → chunk-U2ZRVVKT.mjs} +2 -2
  26. package/dist/{chunk-R4MR43UQ.mjs → chunk-VV56AVPB.mjs} +33 -9
  27. package/dist/chunk-VV56AVPB.mjs.map +1 -0
  28. package/dist/{chunk-5X5DJRQQ.mjs → chunk-WF65O6HX.mjs} +2 -7
  29. package/dist/chunk-WF65O6HX.mjs.map +1 -0
  30. package/dist/chunk-XDFVCQD3.mjs +27 -0
  31. package/dist/chunk-XDFVCQD3.mjs.map +1 -0
  32. package/dist/{chunk-WMJVBMUX.mjs → chunk-YCG6SNAU.mjs} +2 -2
  33. package/dist/{chunk-3R423LZT.mjs → chunk-YJGPIN3R.mjs} +3 -3
  34. package/dist/{chunk-BR5YEYZJ.mjs → chunk-YTQHRJUA.mjs} +2 -2
  35. package/dist/chunk-Z6JV2LRY.mjs +37 -0
  36. package/dist/chunk-Z6JV2LRY.mjs.map +1 -0
  37. package/dist/{chunk-24SCZAB4.mjs → chunk-ZYTXB6HH.mjs} +22 -14
  38. package/dist/chunk-ZYTXB6HH.mjs.map +1 -0
  39. package/dist/components/CopilotListeners.js +13 -146
  40. package/dist/components/CopilotListeners.js.map +1 -1
  41. package/dist/components/CopilotListeners.mjs +1 -6
  42. package/dist/components/copilot-provider/copilot-messages.js +1 -1
  43. package/dist/components/copilot-provider/copilot-messages.js.map +1 -1
  44. package/dist/components/copilot-provider/copilot-messages.mjs +2 -2
  45. package/dist/components/copilot-provider/copilotkit-props.d.ts +1 -1
  46. package/dist/components/copilot-provider/copilotkit.d.ts +1 -1
  47. package/dist/components/copilot-provider/copilotkit.js +35 -40
  48. package/dist/components/copilot-provider/copilotkit.js.map +1 -1
  49. package/dist/components/copilot-provider/copilotkit.mjs +9 -9
  50. package/dist/components/copilot-provider/index.d.ts +1 -1
  51. package/dist/components/copilot-provider/index.js +35 -40
  52. package/dist/components/copilot-provider/index.js.map +1 -1
  53. package/dist/components/copilot-provider/index.mjs +9 -9
  54. package/dist/components/dev-console/console-trigger.js +1 -1
  55. package/dist/components/dev-console/console-trigger.js.map +1 -1
  56. package/dist/components/dev-console/console-trigger.mjs +3 -3
  57. package/dist/components/dev-console/developer-console-modal.js +1 -1
  58. package/dist/components/dev-console/developer-console-modal.js.map +1 -1
  59. package/dist/components/dev-console/developer-console-modal.mjs +2 -2
  60. package/dist/components/index.d.ts +1 -1
  61. package/dist/components/index.js +35 -40
  62. package/dist/components/index.js.map +1 -1
  63. package/dist/components/index.mjs +9 -9
  64. package/dist/context/copilot-context.d.ts +1 -1
  65. package/dist/context/copilot-context.js +1 -1
  66. package/dist/context/copilot-context.js.map +1 -1
  67. package/dist/context/copilot-context.mjs +1 -1
  68. package/dist/context/index.d.ts +1 -1
  69. package/dist/context/index.js +1 -1
  70. package/dist/context/index.js.map +1 -1
  71. package/dist/context/index.mjs +1 -1
  72. package/dist/{copilot-context-1cd70a3f.d.ts → copilot-context-ec77e921.d.ts} +3 -3
  73. package/dist/hooks/index.d.ts +2 -2
  74. package/dist/hooks/index.js +254 -219
  75. package/dist/hooks/index.js.map +1 -1
  76. package/dist/hooks/index.mjs +24 -23
  77. package/dist/hooks/use-agent-nodename.d.ts +3 -0
  78. package/dist/hooks/use-agent-nodename.js +56 -0
  79. package/dist/hooks/use-agent-nodename.js.map +1 -0
  80. package/dist/hooks/use-agent-nodename.mjs +8 -0
  81. package/dist/hooks/use-coagent-state-render-bridge.js +8 -5
  82. package/dist/hooks/use-coagent-state-render-bridge.js.map +1 -1
  83. package/dist/hooks/use-coagent-state-render-bridge.mjs +2 -2
  84. package/dist/hooks/use-coagent-state-render.js +1 -1
  85. package/dist/hooks/use-coagent-state-render.js.map +1 -1
  86. package/dist/hooks/use-coagent-state-render.mjs +2 -2
  87. package/dist/hooks/use-coagent.js +58 -21
  88. package/dist/hooks/use-coagent.js.map +1 -1
  89. package/dist/hooks/use-coagent.mjs +2 -1
  90. package/dist/hooks/use-copilot-action.js +5 -1
  91. package/dist/hooks/use-copilot-action.js.map +1 -1
  92. package/dist/hooks/use-copilot-action.mjs +2 -2
  93. package/dist/hooks/use-copilot-additional-instructions.js +1 -1
  94. package/dist/hooks/use-copilot-additional-instructions.js.map +1 -1
  95. package/dist/hooks/use-copilot-additional-instructions.mjs +2 -2
  96. package/dist/hooks/use-copilot-authenticated-action.js +6 -2
  97. package/dist/hooks/use-copilot-authenticated-action.js.map +1 -1
  98. package/dist/hooks/use-copilot-authenticated-action.mjs +4 -4
  99. package/dist/hooks/use-copilot-chat-headless_c.js +128 -140
  100. package/dist/hooks/use-copilot-chat-headless_c.js.map +1 -1
  101. package/dist/hooks/use-copilot-chat-headless_c.mjs +6 -6
  102. package/dist/hooks/{use-configure-chat-suggestions.d.ts → use-copilot-chat-suggestions.d.ts} +2 -3
  103. package/dist/hooks/use-copilot-chat-suggestions.js +60 -0
  104. package/dist/hooks/use-copilot-chat-suggestions.js.map +1 -0
  105. package/dist/hooks/use-copilot-chat-suggestions.mjs +8 -0
  106. package/dist/hooks/use-copilot-chat-suggestions.mjs.map +1 -0
  107. package/dist/hooks/use-copilot-chat.js +126 -138
  108. package/dist/hooks/use-copilot-chat.js.map +1 -1
  109. package/dist/hooks/use-copilot-chat.mjs +6 -6
  110. package/dist/hooks/use-copilot-chat_internal.d.ts +18 -1
  111. package/dist/hooks/use-copilot-chat_internal.js +126 -138
  112. package/dist/hooks/use-copilot-chat_internal.js.map +1 -1
  113. package/dist/hooks/use-copilot-chat_internal.mjs +5 -5
  114. package/dist/hooks/use-copilot-readable.d.ts +1 -1
  115. package/dist/hooks/use-copilot-readable.js +29 -5
  116. package/dist/hooks/use-copilot-readable.js.map +1 -1
  117. package/dist/hooks/use-copilot-readable.mjs +1 -1
  118. package/dist/hooks/use-default-tool.js +5 -1
  119. package/dist/hooks/use-default-tool.js.map +1 -1
  120. package/dist/hooks/use-default-tool.mjs +3 -3
  121. package/dist/hooks/use-frontend-tool.js +5 -1
  122. package/dist/hooks/use-frontend-tool.js.map +1 -1
  123. package/dist/hooks/use-frontend-tool.mjs +1 -1
  124. package/dist/hooks/use-langgraph-interrupt-render.js +77 -13
  125. package/dist/hooks/use-langgraph-interrupt-render.js.map +1 -1
  126. package/dist/hooks/use-langgraph-interrupt-render.mjs +3 -2
  127. package/dist/hooks/use-langgraph-interrupt.d.ts +1 -1
  128. package/dist/hooks/use-langgraph-interrupt.js +3 -3
  129. package/dist/hooks/use-langgraph-interrupt.js.map +1 -1
  130. package/dist/hooks/use-langgraph-interrupt.mjs +2 -2
  131. package/dist/hooks/use-make-copilot-document-readable.js +1 -1
  132. package/dist/hooks/use-make-copilot-document-readable.js.map +1 -1
  133. package/dist/hooks/use-make-copilot-document-readable.mjs +2 -2
  134. package/dist/index.d.ts +2 -2
  135. package/dist/index.js +273 -246
  136. package/dist/index.js.map +1 -1
  137. package/dist/index.mjs +34 -33
  138. package/dist/lib/copilot-task.d.ts +1 -1
  139. package/dist/lib/copilot-task.js.map +1 -1
  140. package/dist/lib/copilot-task.mjs +10 -10
  141. package/dist/lib/index.d.ts +1 -1
  142. package/dist/lib/index.js.map +1 -1
  143. package/dist/lib/index.mjs +10 -10
  144. package/dist/types/index.d.ts +1 -1
  145. package/dist/types/interrupt-action.d.ts +1 -1
  146. package/dist/types/interrupt-action.js.map +1 -1
  147. package/dist/utils/index.mjs +3 -3
  148. package/dist/v2/index.css +4 -0
  149. package/dist/v2/index.css.map +1 -0
  150. package/dist/v2/index.js.map +1 -1
  151. package/dist/v2/index.mjs +2 -0
  152. package/dist/v2/index.mjs.map +1 -1
  153. package/jest.config.js +12 -0
  154. package/package.json +27 -24
  155. package/src/components/CopilotListeners.tsx +1 -2
  156. package/src/components/copilot-provider/copilot-messages.tsx +0 -41
  157. package/src/components/copilot-provider/copilotkit.tsx +31 -31
  158. package/src/context/copilot-context.tsx +2 -2
  159. package/src/hooks/__tests__/use-coagent-config.test.ts +189 -129
  160. package/src/hooks/index.ts +2 -2
  161. package/src/hooks/use-agent-nodename.ts +30 -0
  162. package/src/hooks/use-coagent-state-render-bridge.tsx +22 -22
  163. package/src/hooks/use-coagent.ts +22 -13
  164. package/src/hooks/use-copilot-chat-suggestions.tsx +124 -0
  165. package/src/hooks/use-copilot-chat_internal.ts +78 -78
  166. package/src/hooks/use-copilot-readable.ts +30 -12
  167. package/src/hooks/use-frontend-tool.ts +10 -2
  168. package/src/hooks/use-langgraph-interrupt-render.ts +25 -7
  169. package/src/hooks/use-langgraph-interrupt.ts +2 -3
  170. package/src/types/interrupt-action.ts +2 -5
  171. package/src/v2/index.ts +2 -0
  172. package/tsup.config.ts +1 -1
  173. package/dist/chunk-24SCZAB4.mjs.map +0 -1
  174. package/dist/chunk-5X5DJRQQ.mjs.map +0 -1
  175. package/dist/chunk-7BYHZLPL.mjs.map +0 -1
  176. package/dist/chunk-CB7CRBDG.mjs +0 -48
  177. package/dist/chunk-CB7CRBDG.mjs.map +0 -1
  178. package/dist/chunk-DCHSCK62.mjs.map +0 -1
  179. package/dist/chunk-IUSKVYUI.mjs +0 -13
  180. package/dist/chunk-IUSKVYUI.mjs.map +0 -1
  181. package/dist/chunk-NG26QEGF.mjs.map +0 -1
  182. package/dist/chunk-NROJOTQP.mjs.map +0 -1
  183. package/dist/chunk-R4MR43UQ.mjs.map +0 -1
  184. package/dist/chunk-UJBV5GAG.mjs.map +0 -1
  185. package/dist/hooks/use-configure-chat-suggestions.js +0 -210
  186. package/dist/hooks/use-configure-chat-suggestions.js.map +0 -1
  187. package/dist/hooks/use-configure-chat-suggestions.mjs +0 -13
  188. package/src/hooks/use-configure-chat-suggestions.tsx +0 -85
  189. /package/dist/{chunk-3GURHDG7.mjs.map → chunk-4HRUQH6U.mjs.map} +0 -0
  190. /package/dist/{chunk-D3QSYDJR.mjs.map → chunk-7IBF6RBW.mjs.map} +0 -0
  191. /package/dist/{chunk-GMI4KO4X.mjs.map → chunk-7SHWECGN.mjs.map} +0 -0
  192. /package/dist/{chunk-OVYFRPSN.mjs.map → chunk-ABWT4DRT.mjs.map} +0 -0
  193. /package/dist/{chunk-JRT5BJF3.mjs.map → chunk-B5ELMVT7.mjs.map} +0 -0
  194. /package/dist/{chunk-TXI72QHK.mjs.map → chunk-EG56H77V.mjs.map} +0 -0
  195. /package/dist/{chunk-LHKZJ2ND.mjs.map → chunk-PMWUKW3Z.mjs.map} +0 -0
  196. /package/dist/{chunk-QU6NONOD.mjs.map → chunk-U2ZRVVKT.mjs.map} +0 -0
  197. /package/dist/{chunk-WMJVBMUX.mjs.map → chunk-YCG6SNAU.mjs.map} +0 -0
  198. /package/dist/{chunk-3R423LZT.mjs.map → chunk-YJGPIN3R.mjs.map} +0 -0
  199. /package/dist/{chunk-BR5YEYZJ.mjs.map → chunk-YTQHRJUA.mjs.map} +0 -0
  200. /package/dist/hooks/{use-configure-chat-suggestions.mjs.map → use-agent-nodename.mjs.map} +0 -0
  201. /package/src/v2/{styles.css → index.css} +0 -0
@@ -1,8 +1,43 @@
1
- import { renderHook } from "@testing-library/react";
1
+ import { renderHook, waitFor } from "@testing-library/react";
2
2
  import { useCoAgent } from "../use-coagent";
3
+ import type { AgentSubscriber } from "@ag-ui/client";
4
+
5
+ // Mock functions for @copilotkitnext/react
6
+ const mockSetState = jest.fn();
7
+ const mockRunAgent = jest.fn();
8
+ const mockAbortRun = jest.fn();
9
+ const mockSubscribe = jest.fn();
10
+ const mockSetProperties = jest.fn();
11
+
12
+ // Store the last subscriber for triggering events
13
+ let lastSubscriber: AgentSubscriber | null = null;
14
+
15
+ const mockAgent = {
16
+ agentId: "test-agent",
17
+ state: { count: 0 },
18
+ isRunning: false,
19
+ threadId: "thread-123",
20
+ setState: mockSetState,
21
+ runAgent: mockRunAgent,
22
+ abortRun: mockAbortRun,
23
+ subscribe: mockSubscribe.mockImplementation((subscriber: AgentSubscriber) => {
24
+ lastSubscriber = subscriber;
25
+ return {
26
+ unsubscribe: jest.fn(),
27
+ };
28
+ }),
29
+ };
3
30
 
4
- // Mock the dependencies
5
- const mockSetCoagentStatesWithRef = jest.fn();
31
+ jest.mock("@copilotkitnext/react", () => ({
32
+ useAgent: jest.fn(() => ({ agent: mockAgent })),
33
+ useCopilotKit: jest.fn(() => ({
34
+ copilotkit: {
35
+ setProperties: mockSetProperties,
36
+ },
37
+ })),
38
+ }));
39
+
40
+ // Mock other dependencies
6
41
  const mockAppendMessage = jest.fn();
7
42
  const mockRunChatCompletion = jest.fn();
8
43
 
@@ -27,7 +62,6 @@ jest.mock("../../context", () => ({
27
62
  availableAgents: [],
28
63
  coagentStates: {},
29
64
  coagentStatesRef: { current: {} },
30
- setCoagentStatesWithRef: mockSetCoagentStatesWithRef,
31
65
  threadId: "test-thread",
32
66
  copilotApiConfig: {
33
67
  headers: {},
@@ -61,14 +95,10 @@ jest.mock("../../components/copilot-provider/copilot-messages", () => ({
61
95
  describe("useCoAgent config synchronization", () => {
62
96
  beforeEach(() => {
63
97
  jest.clearAllMocks();
64
- mockSetCoagentStatesWithRef.mockImplementation((updater) => {
65
- if (typeof updater === "function") {
66
- updater({});
67
- }
68
- });
98
+ lastSubscriber = null;
69
99
  });
70
100
 
71
- it("should call setCoagentStatesWithRef when config changes", () => {
101
+ it("should call setProperties when config changes", async () => {
72
102
  const { rerender } = renderHook(
73
103
  ({ config }) =>
74
104
  useCoAgent({
@@ -82,16 +112,20 @@ describe("useCoAgent config synchronization", () => {
82
112
  );
83
113
 
84
114
  // Clear the initial calls
85
- mockSetCoagentStatesWithRef.mockClear();
115
+ mockSetProperties.mockClear();
86
116
 
87
117
  // Change config
88
118
  rerender({ config: { configurable: { model: "gpt-4o" } } });
89
119
 
90
- // Should have called setCoagentStatesWithRef with new config
91
- expect(mockSetCoagentStatesWithRef).toHaveBeenCalledWith(expect.any(Function));
120
+ // Wait for effect to complete and verify setProperties was called
121
+ await waitFor(() => {
122
+ expect(mockSetProperties).toHaveBeenCalledWith({
123
+ configurable: { model: "gpt-4o" },
124
+ });
125
+ });
92
126
  });
93
127
 
94
- it("should not call setCoagentStatesWithRef when config is unchanged", () => {
128
+ it("should not call setProperties when config is unchanged", () => {
95
129
  const config = { configurable: { model: "gpt-4" } };
96
130
 
97
131
  const { rerender } = renderHook(
@@ -107,16 +141,16 @@ describe("useCoAgent config synchronization", () => {
107
141
  );
108
142
 
109
143
  // Clear the initial calls
110
- mockSetCoagentStatesWithRef.mockClear();
144
+ mockSetProperties.mockClear();
111
145
 
112
- // Re-render with same config
146
+ // Re-render with same config reference
113
147
  rerender({ config });
114
148
 
115
- // Should not have called setCoagentStatesWithRef
116
- expect(mockSetCoagentStatesWithRef).not.toHaveBeenCalled();
149
+ // Should not have called setProperties
150
+ expect(mockSetProperties).not.toHaveBeenCalled();
117
151
  });
118
152
 
119
- it("should handle backward compatibility with configurable prop", () => {
153
+ it("should handle backward compatibility with configurable prop", async () => {
120
154
  const { rerender } = renderHook(
121
155
  ({ configurable }) =>
122
156
  useCoAgent({
@@ -130,23 +164,32 @@ describe("useCoAgent config synchronization", () => {
130
164
  );
131
165
 
132
166
  // Clear the initial calls
133
- mockSetCoagentStatesWithRef.mockClear();
167
+ mockSetProperties.mockClear();
134
168
 
135
169
  // Change configurable prop
136
170
  rerender({ configurable: { model: "gpt-4o" } });
137
171
 
138
- // Should have called setCoagentStatesWithRef
139
- expect(mockSetCoagentStatesWithRef).toHaveBeenCalledWith(expect.any(Function));
172
+ // Wait for effect to complete and verify setProperties was called
173
+ await waitFor(() => {
174
+ expect(mockSetProperties).toHaveBeenCalledWith({
175
+ configurable: { model: "gpt-4o" },
176
+ });
177
+ });
140
178
  });
141
179
 
142
- it("should update config while preserving other state properties", () => {
143
- let capturedUpdater: any = null;
180
+ it("should not call setProperties when both config and configurable are undefined", () => {
181
+ renderHook(() =>
182
+ useCoAgent({
183
+ name: "test-agent",
184
+ initialState: { count: 0 },
185
+ }),
186
+ );
144
187
 
145
- mockSetCoagentStatesWithRef.mockImplementation((updater) => {
146
- capturedUpdater = updater;
147
- return updater;
148
- });
188
+ // Should not have called setProperties
189
+ expect(mockSetProperties).not.toHaveBeenCalled();
190
+ });
149
191
 
192
+ it("should handle deeply nested config changes", async () => {
150
193
  const { rerender } = renderHook(
151
194
  ({ config }) =>
152
195
  useCoAgent({
@@ -155,137 +198,154 @@ describe("useCoAgent config synchronization", () => {
155
198
  config,
156
199
  }),
157
200
  {
158
- initialProps: { config: { configurable: { model: "gpt-4" } } },
201
+ initialProps: {
202
+ config: {
203
+ configurable: {
204
+ model: "gpt-4",
205
+ settings: { temperature: 0.5 },
206
+ },
207
+ },
208
+ },
159
209
  },
160
210
  );
161
211
 
162
- // Clear the initial calls and reset captured updater
163
- mockSetCoagentStatesWithRef.mockClear();
164
- capturedUpdater = null;
165
-
166
- // Change config
167
- rerender({ config: { configurable: { model: "gpt-4o" } } });
168
-
169
- // Should have called setCoagentStatesWithRef
170
- expect(mockSetCoagentStatesWithRef).toHaveBeenCalledWith(expect.any(Function));
171
- expect(capturedUpdater).toBeTruthy();
212
+ // Clear the initial calls
213
+ mockSetProperties.mockClear();
172
214
 
173
- // Test the updater function behavior
174
- const prevState = {
175
- "test-agent": {
176
- name: "test-agent",
177
- state: { count: 5 },
178
- config: { configurable: { model: "gpt-4" } },
179
- running: true,
180
- active: true,
181
- threadId: "thread-123",
182
- nodeName: "test-node",
183
- runId: "run-456",
215
+ // Change nested config
216
+ rerender({
217
+ config: {
218
+ configurable: {
219
+ model: "gpt-4",
220
+ settings: { temperature: 0.7 }, // Only nested property changed
221
+ },
184
222
  },
185
- };
186
-
187
- const newState = capturedUpdater(prevState);
223
+ });
188
224
 
189
- // Verify the state is updated correctly
190
- expect(newState).toEqual({
191
- "test-agent": {
192
- name: "test-agent",
193
- state: { count: 5 }, // State preserved
194
- config: { configurable: { model: "gpt-4o" } }, // Config updated
195
- running: true, // Other properties preserved
196
- active: true,
197
- threadId: "thread-123",
198
- nodeName: "test-node",
199
- runId: "run-456",
200
- },
225
+ // Wait for effect to complete and verify setProperties was called
226
+ await waitFor(() => {
227
+ expect(mockSetProperties).toHaveBeenCalledWith({
228
+ configurable: {
229
+ model: "gpt-4",
230
+ settings: { temperature: 0.7 },
231
+ },
232
+ });
201
233
  });
202
234
  });
203
235
 
204
- it("should create new agent state when agent doesn't exist", () => {
205
- let capturedUpdater: any = null;
236
+ describe("State Management", () => {
237
+ // Helper to create mock subscriber params
238
+ const createMockParams = (stateOverride: any = {}) => ({
239
+ messages: [],
240
+ state: stateOverride,
241
+ agent: mockAgent as any,
242
+ input: {} as any,
243
+ });
244
+
245
+ it("should initialize agent state via onRunInitialized event", () => {
246
+ // Set agent to have NO state (empty object)
247
+ mockAgent.state = {} as any;
248
+
249
+ renderHook(() =>
250
+ useCoAgent({
251
+ name: "test-agent",
252
+ initialState: { count: 42 },
253
+ }),
254
+ );
255
+
256
+ // Verify subscription was created
257
+ expect(mockSubscribe).toHaveBeenCalled();
258
+ expect(lastSubscriber).toBeTruthy();
259
+
260
+ // Clear any initial state calls
261
+ mockSetState.mockClear();
262
+
263
+ // Trigger onRunInitialized with no run state
264
+ lastSubscriber?.onRunInitialized?.(createMockParams({}));
265
+
266
+ // Should set state to initialState
267
+ expect(mockSetState).toHaveBeenCalledWith({ count: 42 });
206
268
 
207
- mockSetCoagentStatesWithRef.mockImplementation((updater) => {
208
- capturedUpdater = updater;
209
- return updater;
269
+ // Reset agent state
270
+ mockAgent.state = { count: 0 };
210
271
  });
211
272
 
212
- const { rerender } = renderHook(
213
- ({ config }) =>
273
+ it("should preserve existing agent state on onRunInitialized", () => {
274
+ // Set agent to have existing state
275
+ mockAgent.state = { count: 100 };
276
+
277
+ renderHook(() =>
214
278
  useCoAgent({
215
279
  name: "test-agent",
216
- initialState: { count: 0 },
217
- config,
280
+ initialState: { count: 42 },
218
281
  }),
219
- {
220
- initialProps: { config: { configurable: { model: "gpt-4" } } },
221
- },
222
- );
282
+ );
223
283
 
224
- // Clear the initial calls and reset captured updater
225
- mockSetCoagentStatesWithRef.mockClear();
226
- capturedUpdater = null;
284
+ // Clear any initial state calls
285
+ mockSetState.mockClear();
227
286
 
228
- // Change config
229
- rerender({ config: { configurable: { model: "gpt-4o" } } });
287
+ // Trigger onRunInitialized with no new state
288
+ lastSubscriber?.onRunInitialized?.(createMockParams({}));
289
+
290
+ // Should NOT override existing state
291
+ expect(mockSetState).not.toHaveBeenCalled();
292
+
293
+ // Reset agent state
294
+ mockAgent.state = { count: 0 };
295
+ });
230
296
 
231
- // Should have called setCoagentStatesWithRef
232
- expect(mockSetCoagentStatesWithRef).toHaveBeenCalledWith(expect.any(Function));
233
- expect(capturedUpdater).toBeTruthy();
297
+ it("should prioritize run state over initialState", () => {
298
+ renderHook(() =>
299
+ useCoAgent({
300
+ name: "test-agent",
301
+ initialState: { count: 42 },
302
+ }),
303
+ );
234
304
 
235
- // Test the updater function behavior with empty previous state
236
- const prevState = {}; // No existing agent state
305
+ // Clear any initial state calls
306
+ mockSetState.mockClear();
237
307
 
238
- const newState = capturedUpdater(prevState);
308
+ // Trigger onRunInitialized with state from the run
309
+ lastSubscriber?.onRunInitialized?.(createMockParams({ count: 999 }));
239
310
 
240
- // Verify the state creates a new agent state with default values
241
- expect(newState).toEqual({
242
- "test-agent": {
243
- name: "test-agent",
244
- state: { count: 0 }, // Uses initialState
245
- config: { configurable: { model: "gpt-4o" } }, // New config
246
- running: false, // Default values
247
- active: false,
248
- threadId: undefined,
249
- nodeName: undefined,
250
- runId: undefined,
251
- },
311
+ // Should use run state, not initialState
312
+ expect(mockSetState).toHaveBeenCalledWith({ count: 999 });
252
313
  });
253
- });
254
314
 
255
- it("should handle deeply nested config changes", () => {
256
- const { rerender } = renderHook(
257
- ({ config }) =>
315
+ it("should handle setState with object updates", () => {
316
+ const { result } = renderHook(() =>
258
317
  useCoAgent({
259
318
  name: "test-agent",
260
319
  initialState: { count: 0 },
261
- config,
262
320
  }),
263
- {
264
- initialProps: {
265
- config: {
266
- configurable: {
267
- model: "gpt-4",
268
- settings: { temperature: 0.5 },
269
- },
270
- },
271
- },
272
- },
273
- );
321
+ );
274
322
 
275
- // Clear the initial calls
276
- mockSetCoagentStatesWithRef.mockClear();
323
+ // Update state with object
324
+ result.current.setState({ count: 5 });
277
325
 
278
- // Change nested config
279
- rerender({
280
- config: {
281
- configurable: {
282
- model: "gpt-4",
283
- settings: { temperature: 0.7 }, // Only nested property changed
284
- },
285
- },
326
+ // Should merge with existing state
327
+ expect(mockSetState).toHaveBeenCalledWith({ count: 5 });
286
328
  });
287
329
 
288
- // Should detect the nested change
289
- expect(mockSetCoagentStatesWithRef).toHaveBeenCalledWith(expect.any(Function));
330
+ it("should handle setState with function updaters", () => {
331
+ // Set current agent state
332
+ mockAgent.state = { count: 10 };
333
+
334
+ const { result } = renderHook(() =>
335
+ useCoAgent({
336
+ name: "test-agent",
337
+ initialState: { count: 0 },
338
+ }),
339
+ );
340
+
341
+ // Update state with function
342
+ result.current.setState((prev) => ({ count: (prev?.count || 0) + 5 }));
343
+
344
+ // Should call function with current state and set result
345
+ expect(mockSetState).toHaveBeenCalledWith({ count: 15 });
346
+
347
+ // Reset agent state
348
+ mockAgent.state = { count: 0 };
349
+ });
290
350
  });
291
351
  });
@@ -24,6 +24,6 @@ export { useRenderToolCall } from "./use-render-tool-call";
24
24
  export { useDefaultTool } from "./use-default-tool";
25
25
  export { useLazyToolRenderer } from "./use-lazy-tool-renderer";
26
26
  export {
27
- useConfigureChatSuggestions,
27
+ useCopilotChatSuggestions,
28
28
  type UseCopilotChatSuggestionsConfiguration,
29
- } from "./use-configure-chat-suggestions";
29
+ } from "./use-copilot-chat-suggestions";
@@ -0,0 +1,30 @@
1
+ import { useEffect, useRef } from "react";
2
+ import type { AgentSubscriber } from "@ag-ui/client";
3
+ import { useAgent } from "@copilotkitnext/react";
4
+
5
+ export function useAgentNodeName(agentName?: string) {
6
+ const { agent } = useAgent({ agentId: agentName });
7
+ const nodeNameRef = useRef<string>("start");
8
+
9
+ useEffect(() => {
10
+ if (!agent) return;
11
+ const subscriber: AgentSubscriber = {
12
+ onStepStartedEvent: ({ event }) => {
13
+ nodeNameRef.current = event.stepName;
14
+ },
15
+ onRunStartedEvent: () => {
16
+ nodeNameRef.current = "start";
17
+ },
18
+ onRunFinishedEvent: () => {
19
+ nodeNameRef.current = "end";
20
+ },
21
+ };
22
+
23
+ const subscription = agent.subscribe(subscriber);
24
+ return () => {
25
+ subscription.unsubscribe();
26
+ };
27
+ }, [agent]);
28
+
29
+ return nodeNameRef.current;
30
+ }
@@ -5,6 +5,7 @@ import { useCoAgentStateRenders } from "../context";
5
5
  import { dataToUUID, parseJson } from "@copilotkit/shared";
6
6
 
7
7
  function getStateWithoutConstantKeys(state: any) {
8
+ if (!state) return {};
8
9
  const { messages, tools, copilotkit, ...stateWithoutConstantKeys } = state;
9
10
  return stateWithoutConstantKeys;
10
11
  }
@@ -148,23 +149,17 @@ export function useCoagentStateRenderBridge(agentId: string, props: CoAgentState
148
149
  // eslint-disable-next-line react-hooks/exhaustive-deps
149
150
  }, [agentId, nodeName]);
150
151
 
151
- if (messageIndexInRun !== 0) {
152
- return null;
153
- }
154
-
155
152
  const getStateRender = useCallback(
156
153
  (messageId: string) => {
157
- return Object.entries(coAgentStateRenders).find(([stateRenderId, stateRender]) => {
158
- if (claimsRef.current[messageId]) {
159
- return stateRenderId === claimsRef.current[messageId].stateRenderId;
160
- }
161
- const matchingAgentName = stateRender.name === agentId;
162
- const matchesNodeContext = stateRender.nodeName
163
- ? stateRender.nodeName === nodeName
164
- : true;
165
- return matchingAgentName && matchesNodeContext;
166
- });
167
- },
154
+ return Object.entries(coAgentStateRenders).find(([stateRenderId, stateRender]) => {
155
+ if (claimsRef.current[messageId]) {
156
+ return stateRenderId === claimsRef.current[messageId].stateRenderId;
157
+ }
158
+ const matchingAgentName = stateRender.name === agentId;
159
+ const matchesNodeContext = stateRender.nodeName ? stateRender.nodeName === nodeName : true;
160
+ return matchingAgentName && matchesNodeContext;
161
+ });
162
+ },
168
163
  [coAgentStateRenders, nodeName, agentId],
169
164
  );
170
165
 
@@ -200,8 +195,8 @@ export function useCoagentStateRenderBridge(agentId: string, props: CoAgentState
200
195
  const renderClaimedByOtherMessage = Object.values(claimsRef.current).find(
201
196
  (c) =>
202
197
  c.stateRenderId === stateRenderId &&
203
- dataToUUID(JSON.stringify(getStateWithoutConstantKeys(c.stateSnapshot))) ===
204
- dataToUUID(JSON.stringify(getStateWithoutConstantKeys(renderSnapshot))),
198
+ dataToUUID(getStateWithoutConstantKeys(c.stateSnapshot)) ===
199
+ dataToUUID(getStateWithoutConstantKeys(renderSnapshot)),
205
200
  );
206
201
  if (renderClaimedByOtherMessage) {
207
202
  // If:
@@ -228,6 +223,10 @@ export function useCoagentStateRenderBridge(agentId: string, props: CoAgentState
228
223
  };
229
224
 
230
225
  return useMemo(() => {
226
+ if (messageIndexInRun !== 0) {
227
+ return null;
228
+ }
229
+
231
230
  const [stateRenderId, stateRender] = getStateRender(message.id) ?? [];
232
231
 
233
232
  if (!stateRender || !stateRenderId) {
@@ -270,11 +269,11 @@ export function useCoagentStateRenderBridge(agentId: string, props: CoAgentState
270
269
 
271
270
  if (typeof stateRender.render === "string") return stateRender.render;
272
271
 
273
- return stateRender.render({
274
- status,
275
- // Always use state from claim, to make sure the state does not seem "wiped" for a fraction of a second
276
- state: claimsRef.current[message.id].stateSnapshot ?? {},
277
- nodeName: nodeName ?? "",
272
+ return stateRender.render({
273
+ status,
274
+ // Always use state from claim, to make sure the state does not seem "wiped" for a fraction of a second
275
+ state: claimsRef.current[message.id].stateSnapshot ?? {},
276
+ nodeName: nodeName ?? "",
278
277
  });
279
278
  }
280
279
  }, [
@@ -285,6 +284,7 @@ export function useCoagentStateRenderBridge(agentId: string, props: CoAgentState
285
284
  nodeName,
286
285
  effectiveRunId,
287
286
  message.id,
287
+ messageIndexInRun,
288
288
  ]);
289
289
  }
290
290
 
@@ -90,8 +90,9 @@
90
90
 
91
91
  import { useCallback, useEffect, useMemo, useRef, useState } from "react";
92
92
  import { Message } from "@copilotkit/shared";
93
- import { useAgent } from "@copilotkitnext/react";
93
+ import { useAgent, useCopilotKit } from "@copilotkitnext/react";
94
94
  import { type AgentSubscriber } from "@ag-ui/client";
95
+ import { useAgentNodeName } from "./use-agent-nodename";
95
96
 
96
97
  interface UseCoagentOptionsBase {
97
98
  /**
@@ -203,7 +204,8 @@ export type HintFunction = (params: HintFunctionParams) => Message | undefined;
203
204
  */
204
205
  export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentReturnType<T> {
205
206
  const { agent } = useAgent({ agentId: options.name });
206
- const nodeNameRef = useRef<string>("start");
207
+ const { copilotkit } = useCopilotKit();
208
+ const nodeName = useAgentNodeName(options.name);
207
209
 
208
210
  const handleStateUpdate = useCallback(
209
211
  (newState: T | ((prevState: T | undefined) => T)) => {
@@ -219,6 +221,22 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
219
221
  [agent?.state, agent?.setState],
220
222
  );
221
223
 
224
+ useEffect(() => {
225
+ if (!options.config && !options.configurable) return;
226
+
227
+ let config = options.config ?? {};
228
+ if (options.configurable) {
229
+ config = {
230
+ ...config,
231
+ configurable: {
232
+ ...options.configurable,
233
+ ...config.configurable,
234
+ },
235
+ };
236
+ }
237
+ copilotkit.setProperties(config);
238
+ }, [options.config, options.configurable]);
239
+
222
240
  const externalStateStr = useMemo(
223
241
  () => (isExternalStateManagement(options) ? JSON.stringify(options.state) : undefined),
224
242
  [isExternalStateManagement(options) ? JSON.stringify(options.state) : undefined],
@@ -284,15 +302,6 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
284
302
  handleStateUpdate(initialStateRef.current);
285
303
  }
286
304
  },
287
- onStepStartedEvent: ({ event }) => {
288
- nodeNameRef.current = event.stepName;
289
- },
290
- onRunStartedEvent: () => {
291
- nodeNameRef.current = "start";
292
- },
293
- onRunFinishedEvent: () => {
294
- nodeNameRef.current = "end";
295
- },
296
305
  };
297
306
 
298
307
  const subscription = agent.subscribe(subscriber);
@@ -314,7 +323,7 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
314
323
  ({} as T);
315
324
  return {
316
325
  name: options.name,
317
- nodeName: nodeNameRef.current,
326
+ nodeName,
318
327
  threadId: undefined,
319
328
  running: false,
320
329
  state: initialState as T,
@@ -327,7 +336,7 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
327
336
 
328
337
  return {
329
338
  name: agent?.agentId ?? options.name,
330
- nodeName: nodeNameRef.current,
339
+ nodeName,
331
340
  threadId: agent.threadId,
332
341
  running: agent.isRunning,
333
342
  state: agent.state,