@copilotkit/react-core 1.50.0-beta.1 → 1.50.0-beta.2

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 (141) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/dist/{chunk-IUSKVYUI.mjs → chunk-2CYJN455.mjs} +2 -1
  3. package/dist/{chunk-IUSKVYUI.mjs.map → chunk-2CYJN455.mjs.map} +1 -1
  4. package/dist/{chunk-UJBV5GAG.mjs → chunk-6F7Q6CPI.mjs} +17 -36
  5. package/dist/chunk-6F7Q6CPI.mjs.map +1 -0
  6. package/dist/{chunk-JRT5BJF3.mjs → chunk-6SK26J65.mjs} +2 -2
  7. package/dist/{chunk-3GURHDG7.mjs → chunk-BPJ6V4YX.mjs} +2 -2
  8. package/dist/{chunk-TXI72QHK.mjs → chunk-EG56H77V.mjs} +2 -2
  9. package/dist/{chunk-3R423LZT.mjs → chunk-EHXVGFWN.mjs} +2 -2
  10. package/dist/{chunk-CB7CRBDG.mjs → chunk-FBVI3LQ6.mjs} +11 -11
  11. package/dist/chunk-FBVI3LQ6.mjs.map +1 -0
  12. package/dist/chunk-I76HKHPJ.mjs +32 -0
  13. package/dist/chunk-I76HKHPJ.mjs.map +1 -0
  14. package/dist/{chunk-R4MR43UQ.mjs → chunk-ISNVEPPQ.mjs} +17 -3
  15. package/dist/chunk-ISNVEPPQ.mjs.map +1 -0
  16. package/dist/{chunk-FBD24VEH.mjs → chunk-LD3MGPZB.mjs} +1 -1
  17. package/dist/{chunk-FBD24VEH.mjs.map → chunk-LD3MGPZB.mjs.map} +1 -1
  18. package/dist/{chunk-GMI4KO4X.mjs → chunk-OAEX7G5G.mjs} +2 -2
  19. package/dist/{chunk-DCHSCK62.mjs → chunk-SD7TUPQM.mjs} +13 -14
  20. package/dist/chunk-SD7TUPQM.mjs.map +1 -0
  21. package/dist/{chunk-NG26QEGF.mjs → chunk-T2VBHAAP.mjs} +9 -3
  22. package/dist/chunk-T2VBHAAP.mjs.map +1 -0
  23. package/dist/{chunk-NROJOTQP.mjs → chunk-TGIWTM6S.mjs} +8 -5
  24. package/dist/chunk-TGIWTM6S.mjs.map +1 -0
  25. package/dist/{chunk-QU6NONOD.mjs → chunk-U2ZRVVKT.mjs} +2 -2
  26. package/dist/{chunk-5X5DJRQQ.mjs → chunk-WF65O6HX.mjs} +2 -7
  27. package/dist/chunk-WF65O6HX.mjs.map +1 -0
  28. package/dist/{chunk-24SCZAB4.mjs → chunk-ZYTXB6HH.mjs} +22 -14
  29. package/dist/chunk-ZYTXB6HH.mjs.map +1 -0
  30. package/dist/components/CopilotListeners.js +13 -146
  31. package/dist/components/CopilotListeners.js.map +1 -1
  32. package/dist/components/CopilotListeners.mjs +1 -6
  33. package/dist/components/copilot-provider/copilot-messages.js.map +1 -1
  34. package/dist/components/copilot-provider/copilot-messages.mjs +1 -1
  35. package/dist/components/copilot-provider/copilotkit-props.d.ts +1 -1
  36. package/dist/components/copilot-provider/copilotkit.d.ts +1 -1
  37. package/dist/components/copilot-provider/copilotkit.js +11 -11
  38. package/dist/components/copilot-provider/copilotkit.js.map +1 -1
  39. package/dist/components/copilot-provider/copilotkit.mjs +6 -6
  40. package/dist/components/copilot-provider/index.d.ts +1 -1
  41. package/dist/components/copilot-provider/index.js +11 -11
  42. package/dist/components/copilot-provider/index.js.map +1 -1
  43. package/dist/components/copilot-provider/index.mjs +6 -6
  44. package/dist/components/index.d.ts +1 -1
  45. package/dist/components/index.js +11 -11
  46. package/dist/components/index.js.map +1 -1
  47. package/dist/components/index.mjs +6 -6
  48. package/dist/context/copilot-context.d.ts +1 -1
  49. package/dist/context/index.d.ts +1 -1
  50. package/dist/{copilot-context-1cd70a3f.d.ts → copilot-context-81022020.d.ts} +1 -1
  51. package/dist/hooks/index.d.ts +1 -1
  52. package/dist/hooks/index.js +187 -155
  53. package/dist/hooks/index.js.map +1 -1
  54. package/dist/hooks/index.mjs +24 -23
  55. package/dist/hooks/use-agent-nodename.d.ts +3 -0
  56. package/dist/hooks/use-agent-nodename.js +56 -0
  57. package/dist/hooks/use-agent-nodename.js.map +1 -0
  58. package/dist/hooks/use-agent-nodename.mjs +8 -0
  59. package/dist/hooks/use-agent-nodename.mjs.map +1 -0
  60. package/dist/hooks/use-coagent-state-render-bridge.js +7 -4
  61. package/dist/hooks/use-coagent-state-render-bridge.js.map +1 -1
  62. package/dist/hooks/use-coagent-state-render-bridge.mjs +1 -1
  63. package/dist/hooks/use-coagent.js +58 -21
  64. package/dist/hooks/use-coagent.js.map +1 -1
  65. package/dist/hooks/use-coagent.mjs +2 -1
  66. package/dist/hooks/use-configure-chat-suggestions.js +13 -144
  67. package/dist/hooks/use-configure-chat-suggestions.js.map +1 -1
  68. package/dist/hooks/use-configure-chat-suggestions.mjs +1 -6
  69. package/dist/hooks/use-copilot-action.js +5 -1
  70. package/dist/hooks/use-copilot-action.js.map +1 -1
  71. package/dist/hooks/use-copilot-action.mjs +2 -2
  72. package/dist/hooks/use-copilot-authenticated-action.js +5 -1
  73. package/dist/hooks/use-copilot-authenticated-action.js.map +1 -1
  74. package/dist/hooks/use-copilot-authenticated-action.mjs +3 -3
  75. package/dist/hooks/use-copilot-chat-headless_c.js +107 -84
  76. package/dist/hooks/use-copilot-chat-headless_c.js.map +1 -1
  77. package/dist/hooks/use-copilot-chat-headless_c.mjs +6 -5
  78. package/dist/hooks/use-copilot-chat.js +105 -82
  79. package/dist/hooks/use-copilot-chat.js.map +1 -1
  80. package/dist/hooks/use-copilot-chat.mjs +6 -5
  81. package/dist/hooks/use-copilot-chat_internal.d.ts +1 -1
  82. package/dist/hooks/use-copilot-chat_internal.js +105 -82
  83. package/dist/hooks/use-copilot-chat_internal.js.map +1 -1
  84. package/dist/hooks/use-copilot-chat_internal.mjs +5 -4
  85. package/dist/hooks/use-copilot-readable.d.ts +1 -1
  86. package/dist/hooks/use-copilot-readable.js +1 -0
  87. package/dist/hooks/use-copilot-readable.js.map +1 -1
  88. package/dist/hooks/use-copilot-readable.mjs +1 -1
  89. package/dist/hooks/use-default-tool.js +5 -1
  90. package/dist/hooks/use-default-tool.js.map +1 -1
  91. package/dist/hooks/use-default-tool.mjs +3 -3
  92. package/dist/hooks/use-frontend-tool.js +5 -1
  93. package/dist/hooks/use-frontend-tool.js.map +1 -1
  94. package/dist/hooks/use-frontend-tool.mjs +1 -1
  95. package/dist/hooks/use-langgraph-interrupt-render.js +61 -7
  96. package/dist/hooks/use-langgraph-interrupt-render.js.map +1 -1
  97. package/dist/hooks/use-langgraph-interrupt-render.mjs +2 -1
  98. package/dist/hooks/use-langgraph-interrupt.d.ts +1 -1
  99. package/dist/index.d.ts +1 -1
  100. package/dist/index.js +184 -155
  101. package/dist/index.js.map +1 -1
  102. package/dist/index.mjs +31 -30
  103. package/dist/lib/copilot-task.d.ts +1 -1
  104. package/dist/lib/copilot-task.js.map +1 -1
  105. package/dist/lib/copilot-task.mjs +7 -7
  106. package/dist/lib/index.d.ts +1 -1
  107. package/dist/lib/index.js.map +1 -1
  108. package/dist/lib/index.mjs +7 -7
  109. package/dist/types/index.d.ts +1 -1
  110. package/dist/types/index.mjs +1 -1
  111. package/dist/types/interrupt-action.d.ts +1 -1
  112. package/dist/types/interrupt-action.js.map +1 -1
  113. package/jest.config.js +12 -0
  114. package/package.json +7 -6
  115. package/src/components/CopilotListeners.tsx +1 -2
  116. package/src/components/copilot-provider/copilot-messages.tsx +0 -41
  117. package/src/components/copilot-provider/copilotkit.tsx +2 -2
  118. package/src/hooks/__tests__/use-coagent-config.test.ts +189 -129
  119. package/src/hooks/use-agent-nodename.ts +30 -0
  120. package/src/hooks/use-coagent-state-render-bridge.tsx +20 -20
  121. package/src/hooks/use-coagent.ts +22 -13
  122. package/src/hooks/use-configure-chat-suggestions.tsx +8 -7
  123. package/src/hooks/use-copilot-chat_internal.ts +44 -42
  124. package/src/hooks/use-copilot-readable.ts +2 -1
  125. package/src/hooks/use-frontend-tool.ts +10 -2
  126. package/src/hooks/use-langgraph-interrupt-render.ts +10 -2
  127. package/src/types/interrupt-action.ts +1 -1
  128. package/dist/chunk-24SCZAB4.mjs.map +0 -1
  129. package/dist/chunk-5X5DJRQQ.mjs.map +0 -1
  130. package/dist/chunk-CB7CRBDG.mjs.map +0 -1
  131. package/dist/chunk-DCHSCK62.mjs.map +0 -1
  132. package/dist/chunk-NG26QEGF.mjs.map +0 -1
  133. package/dist/chunk-NROJOTQP.mjs.map +0 -1
  134. package/dist/chunk-R4MR43UQ.mjs.map +0 -1
  135. package/dist/chunk-UJBV5GAG.mjs.map +0 -1
  136. /package/dist/{chunk-JRT5BJF3.mjs.map → chunk-6SK26J65.mjs.map} +0 -0
  137. /package/dist/{chunk-3GURHDG7.mjs.map → chunk-BPJ6V4YX.mjs.map} +0 -0
  138. /package/dist/{chunk-TXI72QHK.mjs.map → chunk-EG56H77V.mjs.map} +0 -0
  139. /package/dist/{chunk-3R423LZT.mjs.map → chunk-EHXVGFWN.mjs.map} +0 -0
  140. /package/dist/{chunk-GMI4KO4X.mjs.map → chunk-OAEX7G5G.mjs.map} +0 -0
  141. /package/dist/{chunk-QU6NONOD.mjs.map → chunk-U2ZRVVKT.mjs.map} +0 -0
@@ -1,5 +1,5 @@
1
1
  import '@copilotkit/runtime-client-gql';
2
- export { f as LangGraphInterruptAction, h as LangGraphInterruptActionSetter, g as LangGraphInterruptActionSetterArgs, e as LangGraphInterruptRender, L as LangGraphInterruptRenderHandlerProps, d as LangGraphInterruptRenderProps, Q as QueuedInterruptEvent } from '../copilot-context-1cd70a3f.js';
2
+ export { f as LangGraphInterruptAction, h as LangGraphInterruptActionSetter, g as LangGraphInterruptActionSetterArgs, e as LangGraphInterruptRender, L as LangGraphInterruptRenderHandlerProps, d as LangGraphInterruptRenderProps, Q as QueuedInterruptEvent } from '../copilot-context-81022020.js';
3
3
  import '@copilotkit/shared';
4
4
  import './frontend-action.js';
5
5
  import 'react';
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/types/interrupt-action.ts"],"sourcesContent":["import { LangGraphInterruptEvent } from \"@copilotkit/runtime-client-gql\";\nimport { AgentSession } from \"../context/copilot-context\";\n\nexport interface LangGraphInterruptRenderHandlerProps<TEventValue = any> {\n event: LangGraphInterruptEvent<TEventValue>;\n resolve: (resolution: string) => void;\n}\n\nexport interface LangGraphInterruptRenderProps<TEventValue = any> {\n result: unknown;\n event: LangGraphInterruptEvent<TEventValue>;\n resolve: (resolution: string) => void;\n}\n\nexport interface LangGraphInterruptRender<TEventValue = any> {\n id: string;\n /**\n * The handler function to handle the event.\n */\n handler?: (props: LangGraphInterruptRenderHandlerProps<TEventValue>) => any | Promise<any>;\n /**\n * The render function to handle the event.\n */\n render?: (props: LangGraphInterruptRenderProps<TEventValue>) => string | React.ReactElement;\n /**\n * Method that returns a boolean, indicating if the interrupt action should run\n * Useful when using multiple interrupts\n */\n enabled?: (args: { eventValue: TEventValue; agentMetadata: AgentSession | null }) => boolean;\n}\n\nexport type LangGraphInterruptAction = LangGraphInterruptRender & {\n event?: LangGraphInterruptEvent;\n};\n\nexport type LangGraphInterruptActionSetterArgs = Partial<LangGraphInterruptRender> | null;\nexport type LangGraphInterruptActionSetter = (\n threadId: string,\n action: LangGraphInterruptActionSetterArgs,\n) => void;\n\nexport interface QueuedInterruptEvent {\n eventId: string; // Generated unique ID for tracking\n threadId: string;\n event: LangGraphInterruptEvent;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
1
+ {"version":3,"sources":["../../src/types/interrupt-action.ts"],"sourcesContent":["import { LangGraphInterruptEvent } from \"@copilotkit/runtime-client-gql\";\nimport { AgentSession } from \"../context/copilot-context\";\n\nexport interface LangGraphInterruptRenderHandlerProps<TEventValue = any> {\n event: LangGraphInterruptEvent<TEventValue>;\n resolve: (resolution: string) => void;\n}\n\nexport interface LangGraphInterruptRenderProps<TEventValue = any> {\n result: unknown;\n event: LangGraphInterruptEvent<TEventValue>;\n resolve: (resolution: string) => void;\n}\n\nexport interface LangGraphInterruptRender<TEventValue = any> {\n id: string;\n /**\n * The handler function to handle the event.\n */\n handler?: (props: LangGraphInterruptRenderHandlerProps<TEventValue>) => any | Promise<any>;\n /**\n * The render function to handle the event.\n */\n render?: (props: LangGraphInterruptRenderProps<TEventValue>) => string | React.ReactElement;\n /**\n * Method that returns a boolean, indicating if the interrupt action should run\n * Useful when using multiple interrupts\n */\n enabled?: (args: { eventValue: TEventValue; agentMetadata: AgentSession }) => boolean;\n}\n\nexport type LangGraphInterruptAction = LangGraphInterruptRender & {\n event?: LangGraphInterruptEvent;\n};\n\nexport type LangGraphInterruptActionSetterArgs = Partial<LangGraphInterruptRender> | null;\nexport type LangGraphInterruptActionSetter = (\n threadId: string,\n action: LangGraphInterruptActionSetterArgs,\n) => void;\n\nexport interface QueuedInterruptEvent {\n eventId: string; // Generated unique ID for tracking\n threadId: string;\n event: LangGraphInterruptEvent;\n}\n"],"mappings":";;;;;;;;;;;;;;;;AAAA;AAAA;","names":[]}
package/jest.config.js CHANGED
@@ -9,5 +9,17 @@ module.exports = {
9
9
  setupFilesAfterEnv: ["<rootDir>/src/setupTests.ts"],
10
10
  moduleNameMapper: {
11
11
  "^@/(.*)$": "<rootDir>/src/$1",
12
+ "\\.(css|less|scss|sass)$": "identity-obj-proxy",
13
+ },
14
+ transformIgnorePatterns: [
15
+ "node_modules/(?!(react-markdown|streamdown|@copilotkitnext)/)",
16
+ ],
17
+ transform: {
18
+ "^.+\\.(ts|tsx)$": "ts-jest",
19
+ "^.+\\.(js|jsx|mjs)$": ["ts-jest", {
20
+ tsconfig: {
21
+ allowJs: true,
22
+ },
23
+ }],
12
24
  },
13
25
  };
package/package.json CHANGED
@@ -9,7 +9,7 @@
9
9
  "publishConfig": {
10
10
  "access": "public"
11
11
  },
12
- "version": "1.50.0-beta.1",
12
+ "version": "1.50.0-beta.2",
13
13
  "sideEffects": false,
14
14
  "main": "./dist/index.js",
15
15
  "module": "./dist/index.mjs",
@@ -29,8 +29,8 @@
29
29
  "types": "./dist/index.d.ts",
30
30
  "license": "MIT",
31
31
  "peerDependencies": {
32
- "@copilotkitnext/core": "0.0.22",
33
- "@copilotkitnext/react": "0.0.22",
32
+ "@copilotkitnext/core": "0.0.23",
33
+ "@copilotkitnext/react": "0.0.23",
34
34
  "react": "^18 || ^19 || ^19.0.0-rc",
35
35
  "react-dom": "^18 || ^19 || ^19.0.0-rc",
36
36
  "zod": ">=3.0.0"
@@ -43,10 +43,11 @@
43
43
  "@types/react": "^19.1.0",
44
44
  "@types/react-dom": "^19.0.2",
45
45
  "eslint": "^8.56.0",
46
+ "identity-obj-proxy": "^3.0.0",
46
47
  "jest": "^29.6.4",
47
48
  "jest-environment-jsdom": "^30.0.2",
48
49
  "react": "^19.1.0",
49
- "react-dom": "^19.0.2",
50
+ "react-dom": "^19.2.0",
50
51
  "ts-jest": "^29.1.1",
51
52
  "tsup": "^6.7.0",
52
53
  "typescript": "^5.2.3",
@@ -58,8 +59,8 @@
58
59
  "@scarf/scarf": "^1.3.0",
59
60
  "react-markdown": "^8.0.7",
60
61
  "untruncate-json": "^0.0.1",
61
- "@copilotkit/shared": "1.50.0-beta.1",
62
- "@copilotkit/runtime-client-gql": "1.50.0-beta.1"
62
+ "@copilotkit/runtime-client-gql": "1.50.0-beta.2",
63
+ "@copilotkit/shared": "1.50.0-beta.2"
63
64
  },
64
65
  "keywords": [
65
66
  "copilotkit",
@@ -55,9 +55,8 @@ const usePredictStateSubscription = (agent?: AbstractAgent) => {
55
55
 
56
56
  export function CopilotListeners() {
57
57
  const { copilotkit } = useCopilotKit();
58
- const { agentSession } = useCopilotContext();
59
58
  const existingConfig = useCopilotChatConfiguration();
60
- const resolvedAgentId = agentSession?.agentName ?? existingConfig?.agentId ?? "default";
59
+ const resolvedAgentId = existingConfig?.agentId;
61
60
  const { setBannerError } = useToast();
62
61
 
63
62
  const { agent } = useAgent({ agentId: resolvedAgentId });
@@ -252,47 +252,6 @@ export function CopilotMessages({ children }: { children: ReactNode }) {
252
252
  [setBannerError, showDevConsole, traceUIError],
253
253
  );
254
254
 
255
- // useEffect(() => {
256
- // if (!threadId || threadId === lastLoadedThreadId.current) return;
257
- // if (
258
- // threadId === lastLoadedThreadId.current &&
259
- // agentSession?.agentName === lastLoadedAgentName.current
260
- // ) {
261
- // return;
262
- // }
263
- //
264
- // const fetchMessages = async () => {
265
- // if (!agentSession?.agentName) return;
266
- //
267
- // const result = await runtimeClient.loadAgentState({
268
- // threadId,
269
- // agentName: agentSession?.agentName,
270
- // });
271
- //
272
- // // Check for GraphQL errors and manually trigger error handling
273
- // if (result.error) {
274
- // // Update refs to prevent infinite retries of the same failed request
275
- // lastLoadedThreadId.current = threadId;
276
- // lastLoadedAgentName.current = agentSession?.agentName;
277
- // handleGraphQLErrors(result.error);
278
- // return; // Don't try to process the data if there's an error
279
- // }
280
- //
281
- // const newMessages = result.data?.loadAgentState?.messages;
282
- // if (newMessages === lastLoadedMessages.current) return;
283
- //
284
- // if (result.data?.loadAgentState) {
285
- // lastLoadedMessages.current = newMessages;
286
- // lastLoadedThreadId.current = threadId;
287
- // lastLoadedAgentName.current = agentSession?.agentName;
288
- //
289
- // const messages = loadMessagesFromJsonRepresentation(JSON.parse(newMessages || "[]"));
290
- // setMessages(messages);
291
- // }
292
- // };
293
- // void fetchMessages();
294
- // }, [threadId, agentSession?.agentName]);
295
-
296
255
  useEffect(() => {
297
256
  updateTapMessages(messages);
298
257
  }, [messages, updateTapMessages]);
@@ -72,7 +72,7 @@ export function CopilotKit({ children, ...props }: CopilotKitProps) {
72
72
  <CopilotErrorBoundary publicApiKey={publicApiKey} showUsageBanner={enabled}>
73
73
  <ThreadsProvider threadId={props.threadId}>
74
74
  <CopilotKitProvider
75
- runtimeUrl={props.runtimeUrl}
75
+ {...props}
76
76
  renderCustomMessages={renderArr}
77
77
  useSingleEndpoint={true}
78
78
  >
@@ -519,7 +519,7 @@ export function CopilotKitInternal(cpkProps: CopilotKitProps) {
519
519
  <CopilotChatConfigurationProvider
520
520
  // labels={labels}
521
521
  // isModalDefaultOpen={isModalDefaultOpen}
522
- agentId={agentSession?.agentName ?? "default"}
522
+ agentId={props.agent ?? "default"}
523
523
  threadId={threadId}
524
524
  >
525
525
  <CopilotContext.Provider
@@ -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
  });
@@ -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
+ }