@copilotkit/react-core 1.9.3 → 1.10.0-next.1

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 (144) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/chunk-2IDV5OHF.mjs +11 -0
  3. package/dist/chunk-2IDV5OHF.mjs.map +1 -0
  4. package/dist/{chunk-36MGCCPZ.mjs → chunk-4CFY3CON.mjs} +2 -2
  5. package/dist/{chunk-SP4LFJSS.mjs → chunk-4XVBXDCX.mjs} +2 -2
  6. package/dist/chunk-4XVBXDCX.mjs.map +1 -0
  7. package/dist/{chunk-BVK7PLK6.mjs → chunk-DF4YG4PF.mjs} +2 -2
  8. package/dist/{chunk-CUAFWKTQ.mjs → chunk-JZQOCH4A.mjs} +2 -2
  9. package/dist/{chunk-MEAIJ7V2.mjs → chunk-KV25ZRMH.mjs} +3 -3
  10. package/dist/{chunk-5BSUSFHM.mjs → chunk-LNAQ7JG3.mjs} +2 -2
  11. package/dist/{chunk-FN3UA2ZE.mjs → chunk-NXCJELW7.mjs} +3 -3
  12. package/dist/{chunk-GIMSRCVW.mjs → chunk-OMVNJ7S3.mjs} +149 -10
  13. package/dist/chunk-OMVNJ7S3.mjs.map +1 -0
  14. package/dist/{chunk-BSAVFYRQ.mjs → chunk-PYULBXCD.mjs} +2 -2
  15. package/dist/{chunk-VDADWRS3.mjs → chunk-Q6FZZJ5A.mjs} +2 -2
  16. package/dist/{chunk-KIXKBJUV.mjs → chunk-RGKZCCPA.mjs} +2 -2
  17. package/dist/{chunk-H56XI6TM.mjs → chunk-VM7CVIET.mjs} +3 -3
  18. package/dist/chunk-VM7CVIET.mjs.map +1 -0
  19. package/dist/{chunk-UBPKEQ7Z.mjs → chunk-VOMGRGWT.mjs} +94 -22
  20. package/dist/chunk-VOMGRGWT.mjs.map +1 -0
  21. package/dist/{chunk-NJA5ZLAZ.mjs → chunk-WUORFPJ7.mjs} +2 -2
  22. package/dist/chunk-WUORFPJ7.mjs.map +1 -0
  23. package/dist/{chunk-DKZTPL66.mjs → chunk-XGRBCWK6.mjs} +5 -2
  24. package/dist/chunk-XGRBCWK6.mjs.map +1 -0
  25. package/dist/components/copilot-provider/copilot-messages.js +4 -1
  26. package/dist/components/copilot-provider/copilot-messages.js.map +1 -1
  27. package/dist/components/copilot-provider/copilot-messages.mjs +2 -2
  28. package/dist/components/copilot-provider/copilotkit-props.d.ts +2 -1
  29. package/dist/components/copilot-provider/copilotkit.d.ts +2 -1
  30. package/dist/components/copilot-provider/copilotkit.js +8 -2
  31. package/dist/components/copilot-provider/copilotkit.js.map +1 -1
  32. package/dist/components/copilot-provider/copilotkit.mjs +4 -3
  33. package/dist/components/copilot-provider/index.d.ts +2 -1
  34. package/dist/components/copilot-provider/index.js +8 -2
  35. package/dist/components/copilot-provider/index.js.map +1 -1
  36. package/dist/components/copilot-provider/index.mjs +4 -3
  37. package/dist/components/index.d.ts +2 -1
  38. package/dist/components/index.js +8 -2
  39. package/dist/components/index.js.map +1 -1
  40. package/dist/components/index.mjs +4 -3
  41. package/dist/context/copilot-context.d.ts +2 -1
  42. package/dist/context/copilot-context.js +4 -1
  43. package/dist/context/copilot-context.js.map +1 -1
  44. package/dist/context/copilot-context.mjs +1 -1
  45. package/dist/context/index.d.ts +1 -1
  46. package/dist/context/index.js +4 -1
  47. package/dist/context/index.js.map +1 -1
  48. package/dist/context/index.mjs +1 -1
  49. package/dist/{copilot-context-3ab4fdf5.d.ts → copilot-context-bd88d30d.d.ts} +21 -1
  50. package/dist/hooks/index.d.ts +5 -5
  51. package/dist/hooks/index.js +492 -153
  52. package/dist/hooks/index.js.map +1 -1
  53. package/dist/hooks/index.mjs +17 -16
  54. package/dist/hooks/use-chat.d.ts +6 -1
  55. package/dist/hooks/use-chat.js +4 -1
  56. package/dist/hooks/use-chat.js.map +1 -1
  57. package/dist/hooks/use-chat.mjs +2 -2
  58. package/dist/hooks/use-coagent-state-render.js +4 -1
  59. package/dist/hooks/use-coagent-state-render.js.map +1 -1
  60. package/dist/hooks/use-coagent-state-render.mjs +2 -2
  61. package/dist/hooks/use-coagent.d.ts +4 -4
  62. package/dist/hooks/use-coagent.js +426 -44
  63. package/dist/hooks/use-coagent.js.map +1 -1
  64. package/dist/hooks/use-coagent.mjs +8 -6
  65. package/dist/hooks/use-copilot-action.d.ts +1 -1
  66. package/dist/hooks/use-copilot-action.js +4 -1
  67. package/dist/hooks/use-copilot-action.js.map +1 -1
  68. package/dist/hooks/use-copilot-action.mjs +2 -2
  69. package/dist/hooks/use-copilot-additional-instructions.js +4 -1
  70. package/dist/hooks/use-copilot-additional-instructions.js.map +1 -1
  71. package/dist/hooks/use-copilot-additional-instructions.mjs +2 -2
  72. package/dist/hooks/use-copilot-authenticated-action.js +4 -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.d.ts +43 -6
  76. package/dist/hooks/use-copilot-chat.js +442 -64
  77. package/dist/hooks/use-copilot-chat.js.map +1 -1
  78. package/dist/hooks/use-copilot-chat.mjs +7 -5
  79. package/dist/hooks/use-copilot-readable.js +4 -1
  80. package/dist/hooks/use-copilot-readable.js.map +1 -1
  81. package/dist/hooks/use-copilot-readable.mjs +2 -2
  82. package/dist/hooks/use-langgraph-interrupt-render.js +4 -1
  83. package/dist/hooks/use-langgraph-interrupt-render.js.map +1 -1
  84. package/dist/hooks/use-langgraph-interrupt-render.mjs +2 -2
  85. package/dist/hooks/use-langgraph-interrupt.d.ts +4 -3
  86. package/dist/hooks/use-langgraph-interrupt.js +451 -71
  87. package/dist/hooks/use-langgraph-interrupt.js.map +1 -1
  88. package/dist/hooks/use-langgraph-interrupt.mjs +8 -6
  89. package/dist/hooks/use-make-copilot-document-readable.js +4 -1
  90. package/dist/hooks/use-make-copilot-document-readable.js.map +1 -1
  91. package/dist/hooks/use-make-copilot-document-readable.mjs +2 -2
  92. package/dist/index.d.ts +2 -1
  93. package/dist/index.js +421 -200
  94. package/dist/index.js.map +1 -1
  95. package/dist/index.mjs +24 -18
  96. package/dist/lib/copilot-task.d.ts +2 -1
  97. package/dist/lib/copilot-task.js.map +1 -1
  98. package/dist/lib/copilot-task.mjs +5 -4
  99. package/dist/lib/index.d.ts +2 -1
  100. package/dist/lib/index.js.map +1 -1
  101. package/dist/lib/index.mjs +5 -4
  102. package/dist/types/interrupt-action.d.ts +3 -2
  103. package/dist/utils/extract.d.ts +1 -1
  104. package/dist/utils/extract.js.map +1 -1
  105. package/dist/utils/extract.mjs +4 -3
  106. package/dist/utils/index.d.ts +4 -3
  107. package/dist/utils/index.js +144 -0
  108. package/dist/utils/index.js.map +1 -1
  109. package/dist/utils/index.mjs +10 -4
  110. package/dist/utils/suggestions-constants.d.ts +9 -0
  111. package/dist/utils/suggestions-constants.js +35 -0
  112. package/dist/utils/suggestions-constants.js.map +1 -0
  113. package/dist/utils/suggestions-constants.mjs +8 -0
  114. package/dist/utils/suggestions-constants.mjs.map +1 -0
  115. package/dist/utils/suggestions.d.ts +11 -0
  116. package/dist/utils/suggestions.js +343 -0
  117. package/dist/utils/suggestions.js.map +1 -0
  118. package/dist/utils/suggestions.mjs +22 -0
  119. package/dist/utils/suggestions.mjs.map +1 -0
  120. package/package.json +3 -3
  121. package/src/components/copilot-provider/copilotkit.tsx +4 -0
  122. package/src/context/copilot-context.tsx +6 -0
  123. package/src/hooks/use-chat.ts +4 -0
  124. package/src/hooks/use-coagent.ts +2 -2
  125. package/src/hooks/use-copilot-action.ts +1 -1
  126. package/src/hooks/use-copilot-chat.ts +197 -21
  127. package/src/utils/index.ts +3 -0
  128. package/src/utils/suggestions-constants.ts +8 -0
  129. package/src/utils/suggestions.ts +208 -0
  130. package/dist/chunk-DKZTPL66.mjs.map +0 -1
  131. package/dist/chunk-GIMSRCVW.mjs.map +0 -1
  132. package/dist/chunk-H56XI6TM.mjs.map +0 -1
  133. package/dist/chunk-NJA5ZLAZ.mjs.map +0 -1
  134. package/dist/chunk-SP4LFJSS.mjs.map +0 -1
  135. package/dist/chunk-UBPKEQ7Z.mjs.map +0 -1
  136. /package/dist/{chunk-36MGCCPZ.mjs.map → chunk-4CFY3CON.mjs.map} +0 -0
  137. /package/dist/{chunk-BVK7PLK6.mjs.map → chunk-DF4YG4PF.mjs.map} +0 -0
  138. /package/dist/{chunk-CUAFWKTQ.mjs.map → chunk-JZQOCH4A.mjs.map} +0 -0
  139. /package/dist/{chunk-MEAIJ7V2.mjs.map → chunk-KV25ZRMH.mjs.map} +0 -0
  140. /package/dist/{chunk-5BSUSFHM.mjs.map → chunk-LNAQ7JG3.mjs.map} +0 -0
  141. /package/dist/{chunk-FN3UA2ZE.mjs.map → chunk-NXCJELW7.mjs.map} +0 -0
  142. /package/dist/{chunk-BSAVFYRQ.mjs.map → chunk-PYULBXCD.mjs.map} +0 -0
  143. /package/dist/{chunk-VDADWRS3.mjs.map → chunk-Q6FZZJ5A.mjs.map} +0 -0
  144. /package/dist/{chunk-KIXKBJUV.mjs.map → chunk-RGKZCCPA.mjs.map} +0 -0
@@ -27,6 +27,37 @@
27
27
  * }
28
28
  * ```
29
29
  *
30
+ * ### Working with Suggestions
31
+ *
32
+ * ```tsx
33
+ * import { useCopilotChat, useCopilotChatSuggestions } from "@copilotkit/react-core";
34
+ *
35
+ * export function YourComponent() {
36
+ * const {
37
+ * suggestions,
38
+ * setSuggestions,
39
+ * generateSuggestions,
40
+ * isLoadingSuggestions
41
+ * } = useCopilotChat();
42
+ *
43
+ * // Configure AI suggestion generation
44
+ * useCopilotChatSuggestions({
45
+ * instructions: "Suggest helpful actions based on the current context",
46
+ * maxSuggestions: 3
47
+ * });
48
+ *
49
+ * // Manual suggestion control
50
+ * const handleCustomSuggestion = () => {
51
+ * setSuggestions([{ title: "Custom Action", message: "Perform custom action" }]);
52
+ * };
53
+ *
54
+ * // Trigger AI generation
55
+ * const handleGenerateSuggestions = async () => {
56
+ * await generateSuggestions();
57
+ * };
58
+ * }
59
+ * ```
60
+ *
30
61
  * `useCopilotChat` returns an object with the following properties:
31
62
  *
32
63
  * ```tsx
@@ -39,18 +70,30 @@
39
70
  * stopGeneration, // A function to stop the generation of the next message.
40
71
  * reset, // A function to reset the chat.
41
72
  * isLoading, // A boolean indicating if the chat is loading.
73
+ *
74
+ * // Suggestion control (headless UI)
75
+ * suggestions, // Current suggestions array
76
+ * setSuggestions, // Manually set suggestions
77
+ * generateSuggestions, // Trigger AI suggestion generation
78
+ * resetSuggestions, // Clear all suggestions
79
+ * isLoadingSuggestions, // Whether suggestions are being generated
42
80
  * } = useCopilotChat();
43
81
  * ```
44
82
  */
45
- import { useRef, useEffect, useCallback, useState } from "react";
46
- import { AgentSession, useCopilotContext } from "../context/copilot-context";
47
- import { Message, Role, TextMessage } from "@copilotkit/runtime-client-gql";
83
+ import { useRef, useEffect, useCallback, useState, useMemo } from "react";
84
+ import { AgentSession, useCopilotContext, CopilotContextParams } from "../context/copilot-context";
85
+ import { useCopilotMessagesContext, CopilotMessagesContextParams } from "../context";
48
86
  import { SystemMessageFunction } from "../types";
49
87
  import { useChat, AppendMessageOptions } from "./use-chat";
50
88
  import { defaultCopilotContextCategories } from "../components";
51
89
  import { CoAgentStateRenderHandlerArguments } from "@copilotkit/shared";
52
- import { useCopilotMessagesContext } from "../context";
53
90
  import { useAsyncCallback } from "../components/error-boundary/error-utils";
91
+ import { reloadSuggestions as generateSuggestions } from "../utils";
92
+ import type { SuggestionItem } from "../utils";
93
+
94
+ import { Message } from "@copilotkit/shared";
95
+ import { Role as gqlRole, TextMessage, aguiToGQL, gqlToAGUI } from "@copilotkit/runtime-client-gql";
96
+ import { useLangGraphInterruptRender } from "./use-langgraph-interrupt-render";
54
97
 
55
98
  export interface UseCopilotChatOptions {
56
99
  /**
@@ -64,8 +107,9 @@ export interface UseCopilotChatOptions {
64
107
  * HTTP headers to be sent with the API request.
65
108
  */
66
109
  headers?: Record<string, string> | Headers;
110
+
67
111
  /**
68
- * System messages of the chat. Defaults to an empty array.
112
+ * Initial messages to populate the chat with.
69
113
  */
70
114
  initialMessages?: Message[];
71
115
 
@@ -81,23 +125,75 @@ export interface MCPServerConfig {
81
125
  }
82
126
 
83
127
  export interface UseCopilotChatReturn {
128
+ /** Array of messages currently visible in the chat interface */
84
129
  visibleMessages: Message[];
130
+
131
+ /** Send a new message to the chat */
85
132
  appendMessage: (message: Message, options?: AppendMessageOptions) => Promise<void>;
133
+
134
+ /** Replace all messages in the chat */
86
135
  setMessages: (messages: Message[]) => void;
136
+
137
+ /** Remove a specific message by ID */
87
138
  deleteMessage: (messageId: string) => void;
139
+
140
+ /** Regenerate the response for a specific message */
88
141
  reloadMessages: (messageId: string) => Promise<void>;
142
+
143
+ /** Stop the current message generation */
89
144
  stopGeneration: () => void;
145
+
146
+ /** Clear all messages and reset chat state */
90
147
  reset: () => void;
148
+
149
+ /** Whether the chat is currently generating a response */
91
150
  isLoading: boolean;
151
+
152
+ /** Manually trigger chat completion (advanced usage) */
92
153
  runChatCompletion: () => Promise<Message[]>;
154
+
155
+ /** MCP (Model Context Protocol) server configurations */
93
156
  mcpServers: MCPServerConfig[];
157
+
158
+ /** Update MCP server configurations */
94
159
  setMcpServers: (mcpServers: MCPServerConfig[]) => void;
160
+
161
+ /**
162
+ * Current suggestions array
163
+ * Use this to read the current suggestions or in conjunction with setSuggestions for manual control
164
+ */
165
+ suggestions: SuggestionItem[];
166
+
167
+ /**
168
+ * Manually set suggestions
169
+ * Useful for manual mode or custom suggestion workflows
170
+ */
171
+ setSuggestions: (suggestions: SuggestionItem[]) => void;
172
+
173
+ /**
174
+ * Trigger AI-powered suggestion generation
175
+ * Uses configurations from useCopilotChatSuggestions hooks
176
+ * Respects global debouncing - only one generation can run at a time
177
+ */
178
+ generateSuggestions: () => Promise<void>;
179
+
180
+ /**
181
+ * Clear all current suggestions
182
+ * Also resets suggestion generation state
183
+ */
184
+ resetSuggestions: () => void;
185
+
186
+ /** Whether suggestions are currently being generated */
187
+ isLoadingSuggestions: boolean;
188
+
189
+ /** Interrupt content for human-in-the-loop workflows */
190
+ interrupt: string | React.ReactElement | null;
95
191
  }
96
192
 
97
- export function useCopilotChat({
98
- makeSystemMessage,
99
- ...options
100
- }: UseCopilotChatOptions = {}): UseCopilotChatReturn {
193
+ let globalSuggestionPromise: Promise<void> | null = null;
194
+
195
+ export function useCopilotChat(options: UseCopilotChatOptions = {}): UseCopilotChatReturn {
196
+ const makeSystemMessage = options.makeSystemMessage ?? defaultSystemMessage;
101
197
  const {
102
198
  getContextString,
103
199
  getFunctionCallHandler,
@@ -122,22 +218,91 @@ export function useCopilotChat({
122
218
  setExtensions,
123
219
  langGraphInterruptAction,
124
220
  setLangGraphInterruptAction,
221
+ chatSuggestionConfiguration,
222
+ suggestions,
223
+ setSuggestions,
224
+ runtimeClient,
125
225
  } = useCopilotContext();
126
226
  const { messages, setMessages } = useCopilotMessagesContext();
127
227
 
128
228
  // Simple state for MCP servers (keep for interface compatibility)
129
229
  const [mcpServers, setLocalMcpServers] = useState<MCPServerConfig[]>([]);
130
230
 
131
- // This effect directly updates the context when mcpServers state changes
231
+ // Basic suggestion state for programmatic control
232
+ const suggestionsAbortControllerRef = useRef<AbortController | null>(null);
233
+ const isLoadingSuggestionsRef = useRef<boolean>(false);
234
+
235
+ const abortSuggestions = useCallback(
236
+ (clear: boolean = true) => {
237
+ suggestionsAbortControllerRef.current?.abort("suggestions aborted by user");
238
+ suggestionsAbortControllerRef.current = null;
239
+ if (clear) {
240
+ setSuggestions([]);
241
+ }
242
+ },
243
+ [setSuggestions],
244
+ );
245
+
246
+ // Memoize context with stable dependencies only
247
+ const stableContext = useMemo(() => {
248
+ return {
249
+ actions,
250
+ copilotApiConfig,
251
+ chatSuggestionConfiguration,
252
+ messages,
253
+ setMessages,
254
+ getContextString,
255
+ runtimeClient,
256
+ };
257
+ }, [
258
+ JSON.stringify(Object.keys(actions)),
259
+ copilotApiConfig.chatApiEndpoint,
260
+ messages.length,
261
+ Object.keys(chatSuggestionConfiguration).length,
262
+ ]);
263
+
264
+ // Programmatic suggestion generation function
265
+ const generateSuggestionsFunc = useCallback(async () => {
266
+ // If a global suggestion is running, ignore this call
267
+ if (globalSuggestionPromise) {
268
+ return globalSuggestionPromise;
269
+ }
270
+
271
+ globalSuggestionPromise = (async () => {
272
+ try {
273
+ abortSuggestions();
274
+ isLoadingSuggestionsRef.current = true;
275
+ suggestionsAbortControllerRef.current = new AbortController();
276
+
277
+ setSuggestions([]);
278
+
279
+ await generateSuggestions(
280
+ stableContext as CopilotContextParams & CopilotMessagesContextParams,
281
+ chatSuggestionConfiguration,
282
+ setSuggestions,
283
+ suggestionsAbortControllerRef,
284
+ );
285
+ } catch (error) {
286
+ // Re-throw to allow caller to handle the error
287
+ throw error;
288
+ } finally {
289
+ isLoadingSuggestionsRef.current = false;
290
+ globalSuggestionPromise = null;
291
+ }
292
+ })();
293
+
294
+ return globalSuggestionPromise;
295
+ }, [stableContext, chatSuggestionConfiguration, setSuggestions, abortSuggestions]);
296
+
297
+ const resetSuggestions = useCallback(() => {
298
+ setSuggestions([]);
299
+ }, [setSuggestions]);
300
+
301
+ // MCP servers logic
132
302
  useEffect(() => {
133
303
  if (mcpServers.length > 0) {
134
- // Copy to avoid issues
135
304
  const serversCopy = [...mcpServers];
136
-
137
- // Update in all locations
138
305
  copilotApiConfig.mcpServers = serversCopy;
139
-
140
- // Also ensure it's in properties
141
306
  if (!copilotApiConfig.properties) {
142
307
  copilotApiConfig.properties = {};
143
308
  }
@@ -145,7 +310,6 @@ export function useCopilotChat({
145
310
  }
146
311
  }, [mcpServers, copilotApiConfig]);
147
312
 
148
- // Provide the same interface
149
313
  const setMcpServers = useCallback((servers: MCPServerConfig[]) => {
150
314
  setLocalMcpServers(servers);
151
315
  }, []);
@@ -176,7 +340,7 @@ export function useCopilotChat({
176
340
 
177
341
  return new TextMessage({
178
342
  content: systemMessageMaker(contextString, chatInstructions),
179
- role: Role.System,
343
+ role: gqlRole.System,
180
344
  });
181
345
  }, [getContextString, makeSystemMessage, chatInstructions]);
182
346
 
@@ -192,7 +356,7 @@ export function useCopilotChat({
192
356
  ...options,
193
357
  actions: Object.values(actions),
194
358
  copilotConfig: copilotApiConfig,
195
- initialMessages: options.initialMessages || [],
359
+ initialMessages: aguiToGQL(options.initialMessages || []),
196
360
  onFunctionCall: getFunctionCallHandler(),
197
361
  onCoAgentStateRender,
198
362
  messages,
@@ -220,7 +384,8 @@ export function useCopilotChat({
220
384
  const latestAppend = useUpdatedRef(append);
221
385
  const latestAppendFunc = useAsyncCallback(
222
386
  async (message: Message, options?: AppendMessageOptions) => {
223
- return await latestAppend.current(message, options);
387
+ abortSuggestions(options?.clearSuggestions);
388
+ return await latestAppend.current(aguiToGQL([message])[0], options);
224
389
  },
225
390
  [latestAppend],
226
391
  );
@@ -249,7 +414,7 @@ export function useCopilotChat({
249
414
  const latestSetMessages = useUpdatedRef(setMessages);
250
415
  const latestSetMessagesFunc = useCallback(
251
416
  (messages: Message[]) => {
252
- return latestSetMessages.current(messages);
417
+ return latestSetMessages.current(aguiToGQL(messages));
253
418
  },
254
419
  [latestSetMessages],
255
420
  );
@@ -271,6 +436,8 @@ export function useCopilotChat({
271
436
  };
272
437
  }
273
438
  setAgentSession(initialAgentSession);
439
+ // Reset suggestions when chat is reset
440
+ resetSuggestions();
274
441
  }, [
275
442
  latestStopFunc,
276
443
  setMessages,
@@ -278,6 +445,7 @@ export function useCopilotChat({
278
445
  setCoagentStatesWithRef,
279
446
  setAgentSession,
280
447
  agentLock,
448
+ resetSuggestions,
281
449
  ]);
282
450
 
283
451
  const latestReset = useUpdatedRef(reset);
@@ -285,8 +453,10 @@ export function useCopilotChat({
285
453
  return latestReset.current();
286
454
  }, [latestReset]);
287
455
 
456
+ const interrupt = useLangGraphInterruptRender();
457
+
288
458
  return {
289
- visibleMessages: messages,
459
+ visibleMessages: gqlToAGUI(messages, actions, coAgentStateRenders),
290
460
  appendMessage: latestAppendFunc,
291
461
  setMessages: latestSetMessagesFunc,
292
462
  reloadMessages: latestReloadFunc,
@@ -297,6 +467,12 @@ export function useCopilotChat({
297
467
  isLoading,
298
468
  mcpServers,
299
469
  setMcpServers,
470
+ suggestions,
471
+ setSuggestions,
472
+ generateSuggestions: generateSuggestionsFunc,
473
+ resetSuggestions,
474
+ isLoadingSuggestions: isLoadingSuggestionsRef.current,
475
+ interrupt,
300
476
  };
301
477
  }
302
478
 
@@ -1,2 +1,5 @@
1
1
  export { extract } from "./extract";
2
+ export { reloadSuggestions } from "./suggestions";
3
+ export type { SuggestionItem } from "./suggestions";
4
+ export { SUGGESTION_RETRY_CONFIG } from "./suggestions-constants";
2
5
  export * from "./dev-console";
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Constants for suggestions retry logic
3
+ */
4
+
5
+ export const SUGGESTION_RETRY_CONFIG = {
6
+ MAX_RETRIES: 3,
7
+ COOLDOWN_MS: 5000, // 5 seconds
8
+ } as const;
@@ -0,0 +1,208 @@
1
+ /**
2
+ * Suggestions utility functions for CopilotKit
3
+ *
4
+ * This module handles the generation of chat suggestions with optimized error handling
5
+ * and streaming validation to prevent infinite retry loops and console spam.
6
+ */
7
+
8
+ import { extract } from "./extract";
9
+ import { actionParametersToJsonSchema } from "@copilotkit/shared";
10
+ import { CopilotRequestType } from "@copilotkit/runtime-client-gql";
11
+ import { CopilotContextParams, CopilotMessagesContextParams } from "../context";
12
+ import { CopilotChatSuggestionConfiguration } from "../types";
13
+
14
+ export interface SuggestionItem {
15
+ title: string;
16
+ message: string;
17
+ partial?: boolean;
18
+ className?: string;
19
+ }
20
+
21
+ export const reloadSuggestions = async (
22
+ context: CopilotContextParams & CopilotMessagesContextParams,
23
+ chatSuggestionConfiguration: { [key: string]: CopilotChatSuggestionConfiguration },
24
+ setCurrentSuggestions: (suggestions: SuggestionItem[]) => void,
25
+ abortControllerRef: React.MutableRefObject<AbortController | null>,
26
+ ): Promise<void> => {
27
+ const abortController = abortControllerRef.current;
28
+
29
+ // Early abort check
30
+ if (abortController?.signal.aborted) {
31
+ return;
32
+ }
33
+
34
+ // Abort-aware suggestion setter with safety checks to prevent race conditions
35
+ const setSuggestionsIfNotAborted = (suggestions: SuggestionItem[]) => {
36
+ if (!abortController?.signal.aborted && abortControllerRef.current === abortController) {
37
+ setCurrentSuggestions(suggestions);
38
+ }
39
+ };
40
+
41
+ try {
42
+ const tools = JSON.stringify(
43
+ Object.values(context.actions).map((action) => ({
44
+ name: action.name,
45
+ description: action.description,
46
+ jsonSchema: JSON.stringify(actionParametersToJsonSchema(action.parameters)),
47
+ })),
48
+ );
49
+
50
+ const allSuggestions: SuggestionItem[] = [];
51
+ let hasSuccessfulSuggestions = false;
52
+ let hasErrors = false; // Track if any errors occurred
53
+ let lastError: Error | null = null; // Track the last error for better error reporting
54
+
55
+ // Get enabled configurations
56
+ const enabledConfigs = Object.values(chatSuggestionConfiguration).filter(
57
+ (config) => config.instructions && config.instructions.trim().length > 0,
58
+ );
59
+
60
+ if (enabledConfigs.length === 0) {
61
+ return;
62
+ }
63
+
64
+ // Clear existing suggestions
65
+ setSuggestionsIfNotAborted([]);
66
+
67
+ // Generate suggestions for each configuration
68
+ for (const config of enabledConfigs) {
69
+ // Check if aborted before each configuration
70
+ if (abortController?.signal.aborted) {
71
+ setSuggestionsIfNotAborted([]);
72
+ return;
73
+ }
74
+
75
+ try {
76
+ const result = await extract({
77
+ context,
78
+ instructions:
79
+ "Suggest what the user could say next. Provide clear, highly relevant suggestions. Do not literally suggest function calls. ",
80
+ data: `${config.instructions}\n\nAvailable tools: ${tools}\n\n`,
81
+ requestType: CopilotRequestType.Task,
82
+ parameters: [
83
+ {
84
+ name: "suggestions",
85
+ type: "object[]",
86
+ attributes: [
87
+ {
88
+ name: "title",
89
+ description:
90
+ "The title of the suggestion. This is shown as a button and should be short.",
91
+ type: "string",
92
+ },
93
+ {
94
+ name: "message",
95
+ description:
96
+ "The message to send when the suggestion is clicked. This should be a clear, complete sentence and will be sent as an instruction to the AI.",
97
+ type: "string",
98
+ },
99
+ ],
100
+ },
101
+ ],
102
+ include: {
103
+ messages: true,
104
+ readable: true,
105
+ },
106
+ abortSignal: abortController?.signal,
107
+ stream: ({ status, args }: { status: string; args: any }) => {
108
+ // Check abort status in stream callback
109
+ if (abortController?.signal.aborted) {
110
+ return;
111
+ }
112
+
113
+ const suggestions = args.suggestions || [];
114
+ const newSuggestions: SuggestionItem[] = [];
115
+
116
+ for (let i = 0; i < suggestions.length; i++) {
117
+ // Respect max suggestions limit
118
+ if (config.maxSuggestions !== undefined && i >= config.maxSuggestions) {
119
+ break;
120
+ }
121
+
122
+ const suggestion = suggestions[i];
123
+
124
+ // Skip completely empty or invalid objects during streaming
125
+ if (!suggestion || typeof suggestion !== "object") {
126
+ continue;
127
+ }
128
+
129
+ const { title, message } = suggestion;
130
+
131
+ // During streaming, be permissive but require at least a meaningful title
132
+ const hasValidTitle = title && typeof title === "string" && title.trim().length > 0;
133
+ const hasValidMessage =
134
+ message && typeof message === "string" && message.trim().length > 0;
135
+
136
+ // During streaming, we need at least a title to show something useful
137
+ if (!hasValidTitle) {
138
+ continue;
139
+ }
140
+
141
+ // Mark as partial if this is the last suggestion and streaming isn't complete
142
+ const partial = i === suggestions.length - 1 && status !== "complete";
143
+
144
+ newSuggestions.push({
145
+ title: title.trim(),
146
+ message: hasValidMessage ? message.trim() : "", // Use title as fallback
147
+ partial,
148
+ className: config.className,
149
+ });
150
+ }
151
+
152
+ // Update suggestions with current batch
153
+ setSuggestionsIfNotAborted([...allSuggestions, ...newSuggestions]);
154
+ },
155
+ });
156
+
157
+ // Process final results with strict validation
158
+ if (result?.suggestions && Array.isArray(result.suggestions)) {
159
+ const validSuggestions = result.suggestions
160
+ .filter(
161
+ (suggestion: any) =>
162
+ suggestion &&
163
+ typeof suggestion.title === "string" &&
164
+ suggestion.title.trim().length > 0,
165
+ )
166
+ .map((suggestion: any) => ({
167
+ title: suggestion.title.trim(),
168
+ message:
169
+ suggestion.message &&
170
+ typeof suggestion.message === "string" &&
171
+ suggestion.message.trim()
172
+ ? suggestion.message.trim()
173
+ : suggestion.title.trim(),
174
+ }));
175
+
176
+ if (validSuggestions.length > 0) {
177
+ allSuggestions.push(...validSuggestions);
178
+ hasSuccessfulSuggestions = true;
179
+ }
180
+ }
181
+ } catch (error) {
182
+ // Simple error handling - just continue with next config, don't log here
183
+ hasErrors = true;
184
+ lastError = error instanceof Error ? error : new Error(String(error));
185
+ }
186
+ }
187
+
188
+ // Display any successful suggestions we got
189
+ if (hasSuccessfulSuggestions && allSuggestions.length > 0) {
190
+ // Remove duplicates based on message content
191
+ const uniqueSuggestions = allSuggestions.filter(
192
+ (suggestion, index, self) =>
193
+ index === self.findIndex((s) => s.message === suggestion.message),
194
+ );
195
+
196
+ setSuggestionsIfNotAborted(uniqueSuggestions);
197
+ } else if (hasErrors) {
198
+ // If we had errors but no successful suggestions, throw an error with details
199
+ const errorMessage = lastError
200
+ ? lastError.message
201
+ : "Failed to generate suggestions due to API errors";
202
+ throw new Error(errorMessage);
203
+ }
204
+ } catch (error) {
205
+ // Top-level error handler - re-throw to allow caller to handle
206
+ throw error;
207
+ }
208
+ };
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/context/copilot-context.tsx"],"sourcesContent":["import { CopilotCloudConfig, FunctionCallHandler, CopilotErrorHandler } from \"@copilotkit/shared\";\nimport {\n ActionRenderProps,\n CatchAllActionRenderProps,\n FrontendAction,\n} from \"../types/frontend-action\";\nimport React from \"react\";\nimport { TreeNodeId, Tree } from \"../hooks/use-tree\";\nimport { DocumentPointer } from \"../types\";\nimport { CopilotChatSuggestionConfiguration } from \"../types/chat-suggestion-configuration\";\nimport { CoAgentStateRender, CoAgentStateRenderProps } from \"../types/coagent-action\";\nimport { CoagentState } from \"../types/coagent-state\";\nimport {\n CopilotRuntimeClient,\n ExtensionsInput,\n ForwardedParametersInput,\n} from \"@copilotkit/runtime-client-gql\";\nimport { Agent } from \"@copilotkit/runtime-client-gql\";\nimport {\n LangGraphInterruptAction,\n LangGraphInterruptActionSetter,\n} from \"../types/interrupt-action\";\n\n/**\n * Interface for the configuration of the Copilot API.\n */\nexport interface CopilotApiConfig {\n /**\n * The public API key for Copilot Cloud.\n */\n publicApiKey?: string;\n\n /**\n * The configuration for Copilot Cloud.\n */\n cloud?: CopilotCloudConfig;\n\n /**\n * The endpoint for the chat API.\n */\n chatApiEndpoint: string;\n\n /**\n * The endpoint for the Copilot transcribe audio service.\n */\n transcribeAudioUrl?: string;\n\n /**\n * The endpoint for the Copilot text to speech service.\n */\n textToSpeechUrl?: string;\n\n /**\n * additional headers to be sent with the request\n * @default {}\n * @example\n * ```\n * {\n * 'Authorization': 'Bearer your_token_here'\n * }\n * ```\n */\n headers: Record<string, string>;\n\n /**\n * Custom properties to be sent with the request\n * @default {}\n * @example\n * ```\n * {\n * 'user_id': 'user_id'\n * }\n * ```\n */\n properties?: Record<string, any>;\n\n /**\n * Indicates whether the user agent should send or receive cookies from the other domain\n * in the case of cross-origin requests.\n */\n credentials?: RequestCredentials;\n\n /**\n * Optional configuration for connecting to Model Context Protocol (MCP) servers.\n * This is typically derived from the CopilotKitProps and used internally.\n * @experimental\n */\n mcpServers?: Array<{ endpoint: string; apiKey?: string }>;\n}\n\nexport type InChatRenderFunction<TProps = ActionRenderProps<any> | CatchAllActionRenderProps<any>> =\n (props: TProps) => string | JSX.Element;\nexport type CoagentInChatRenderFunction = (\n props: CoAgentStateRenderProps<any>,\n) => string | JSX.Element | undefined | null;\n\nexport interface ChatComponentsCache {\n actions: Record<string, InChatRenderFunction | string>;\n coAgentStateRenders: Record<string, CoagentInChatRenderFunction | string>;\n}\n\nexport interface AgentSession {\n agentName: string;\n threadId?: string;\n nodeName?: string;\n}\n\nexport interface AuthState {\n status: \"authenticated\" | \"unauthenticated\";\n authHeaders: Record<string, string>;\n userId?: string;\n metadata?: Record<string, any>;\n}\n\nexport type ActionName = string;\nexport type ContextTree = Tree;\n\nexport interface CopilotContextParams {\n // function-calling\n actions: Record<string, FrontendAction<any>>;\n setAction: (id: string, action: FrontendAction<any>) => void;\n removeAction: (id: string) => void;\n\n // coagent actions\n coAgentStateRenders: Record<string, CoAgentStateRender<any>>;\n setCoAgentStateRender: (id: string, stateRender: CoAgentStateRender<any>) => void;\n removeCoAgentStateRender: (id: string) => void;\n\n chatComponentsCache: React.RefObject<ChatComponentsCache>;\n\n getFunctionCallHandler: (\n customEntryPoints?: Record<string, FrontendAction<any>>,\n ) => FunctionCallHandler;\n\n // text context\n addContext: (context: string, parentId?: string, categories?: string[]) => TreeNodeId;\n removeContext: (id: TreeNodeId) => void;\n getAllContext: () => Tree;\n getContextString: (documents: DocumentPointer[], categories: string[]) => string;\n\n // document context\n addDocumentContext: (documentPointer: DocumentPointer, categories?: string[]) => TreeNodeId;\n removeDocumentContext: (documentId: string) => void;\n getDocumentsContext: (categories: string[]) => DocumentPointer[];\n\n isLoading: boolean;\n setIsLoading: React.Dispatch<React.SetStateAction<boolean>>;\n\n chatSuggestionConfiguration: { [key: string]: CopilotChatSuggestionConfiguration };\n addChatSuggestionConfiguration: (\n id: string,\n suggestion: CopilotChatSuggestionConfiguration,\n ) => void;\n removeChatSuggestionConfiguration: (id: string) => void;\n\n chatInstructions: string;\n setChatInstructions: React.Dispatch<React.SetStateAction<string>>;\n\n additionalInstructions?: string[];\n setAdditionalInstructions: React.Dispatch<React.SetStateAction<string[]>>;\n\n // api endpoints\n copilotApiConfig: CopilotApiConfig;\n\n showDevConsole: boolean;\n\n // agents\n coagentStates: Record<string, CoagentState>;\n setCoagentStates: React.Dispatch<React.SetStateAction<Record<string, CoagentState>>>;\n coagentStatesRef: React.RefObject<Record<string, CoagentState>>;\n setCoagentStatesWithRef: (\n value:\n | Record<string, CoagentState>\n | ((prev: Record<string, CoagentState>) => Record<string, CoagentState>),\n ) => void;\n\n agentSession: AgentSession | null;\n setAgentSession: React.Dispatch<React.SetStateAction<AgentSession | null>>;\n\n agentLock: string | null;\n\n threadId: string;\n setThreadId: React.Dispatch<React.SetStateAction<string>>;\n\n runId: string | null;\n setRunId: React.Dispatch<React.SetStateAction<string | null>>;\n\n // The chat abort controller can be used to stop generation globally,\n // i.e. when using `stop()` from `useChat`\n chatAbortControllerRef: React.MutableRefObject<AbortController | null>;\n\n // runtime\n runtimeClient: CopilotRuntimeClient;\n\n /**\n * The forwarded parameters to use for the task.\n */\n forwardedParameters?: Partial<Pick<ForwardedParametersInput, \"temperature\">>;\n availableAgents: Agent[];\n\n /**\n * The auth states for the CopilotKit.\n */\n authStates_c?: Record<ActionName, AuthState>;\n setAuthStates_c?: React.Dispatch<React.SetStateAction<Record<ActionName, AuthState>>>;\n\n /**\n * The auth config for the CopilotKit.\n */\n authConfig_c?: {\n SignInComponent: React.ComponentType<{\n onSignInComplete: (authState: AuthState) => void;\n }>;\n };\n\n extensions: ExtensionsInput;\n setExtensions: React.Dispatch<React.SetStateAction<ExtensionsInput>>;\n langGraphInterruptAction: LangGraphInterruptAction | null;\n setLangGraphInterruptAction: LangGraphInterruptActionSetter;\n removeLangGraphInterruptAction: () => void;\n\n /**\n * Optional trace handler for comprehensive debugging and observability.\n */\n onError?: CopilotErrorHandler;\n}\n\nconst emptyCopilotContext: CopilotContextParams = {\n actions: {},\n setAction: () => {},\n removeAction: () => {},\n\n coAgentStateRenders: {},\n setCoAgentStateRender: () => {},\n removeCoAgentStateRender: () => {},\n\n chatComponentsCache: { current: { actions: {}, coAgentStateRenders: {} } },\n getContextString: (documents: DocumentPointer[], categories: string[]) =>\n returnAndThrowInDebug(\"\"),\n addContext: () => \"\",\n removeContext: () => {},\n getAllContext: () => [],\n\n getFunctionCallHandler: () => returnAndThrowInDebug(async () => {}),\n\n isLoading: false,\n setIsLoading: () => returnAndThrowInDebug(false),\n\n chatInstructions: \"\",\n setChatInstructions: () => returnAndThrowInDebug(\"\"),\n\n additionalInstructions: [],\n setAdditionalInstructions: () => returnAndThrowInDebug([]),\n\n getDocumentsContext: (categories: string[]) => returnAndThrowInDebug([]),\n addDocumentContext: () => returnAndThrowInDebug(\"\"),\n removeDocumentContext: () => {},\n runtimeClient: {} as any,\n\n copilotApiConfig: new (class implements CopilotApiConfig {\n get chatApiEndpoint(): string {\n throw new Error(\"Remember to wrap your app in a `<CopilotKit> {...} </CopilotKit>` !!!\");\n }\n\n get headers(): Record<string, string> {\n return {};\n }\n get body(): Record<string, any> {\n return {};\n }\n })(),\n\n chatSuggestionConfiguration: {},\n addChatSuggestionConfiguration: () => {},\n removeChatSuggestionConfiguration: () => {},\n showDevConsole: false,\n coagentStates: {},\n setCoagentStates: () => {},\n coagentStatesRef: { current: {} },\n setCoagentStatesWithRef: () => {},\n agentSession: null,\n setAgentSession: () => {},\n forwardedParameters: {},\n agentLock: null,\n threadId: \"\",\n setThreadId: () => {},\n runId: null,\n setRunId: () => {},\n chatAbortControllerRef: { current: null },\n availableAgents: [],\n extensions: {},\n setExtensions: () => {},\n langGraphInterruptAction: null,\n setLangGraphInterruptAction: () => null,\n removeLangGraphInterruptAction: () => null,\n onError: undefined,\n};\n\nexport const CopilotContext = React.createContext<CopilotContextParams>(emptyCopilotContext);\n\nexport function useCopilotContext(): CopilotContextParams {\n const context = React.useContext(CopilotContext);\n if (context === emptyCopilotContext) {\n throw new Error(\"Remember to wrap your app in a `<CopilotKit> {...} </CopilotKit>` !!!\");\n }\n return context;\n}\n\nfunction returnAndThrowInDebug<T>(_value: T): T {\n throw new Error(\"Remember to wrap your app in a `<CopilotKit> {...} </CopilotKit>` !!!\");\n}\n"],"mappings":";;;;;AAMA,OAAO,WAAW;AA6NlB,IAAM,sBAA4C;AAAA,EAChD,SAAS,CAAC;AAAA,EACV,WAAW,MAAM;AAAA,EAAC;AAAA,EAClB,cAAc,MAAM;AAAA,EAAC;AAAA,EAErB,qBAAqB,CAAC;AAAA,EACtB,uBAAuB,MAAM;AAAA,EAAC;AAAA,EAC9B,0BAA0B,MAAM;AAAA,EAAC;AAAA,EAEjC,qBAAqB,EAAE,SAAS,EAAE,SAAS,CAAC,GAAG,qBAAqB,CAAC,EAAE,EAAE;AAAA,EACzE,kBAAkB,CAAC,WAA8B,eAC/C,sBAAsB,EAAE;AAAA,EAC1B,YAAY,MAAM;AAAA,EAClB,eAAe,MAAM;AAAA,EAAC;AAAA,EACtB,eAAe,MAAM,CAAC;AAAA,EAEtB,wBAAwB,MAAM,sBAAsB,MAAY;AAAA,EAAC,EAAC;AAAA,EAElE,WAAW;AAAA,EACX,cAAc,MAAM,sBAAsB,KAAK;AAAA,EAE/C,kBAAkB;AAAA,EAClB,qBAAqB,MAAM,sBAAsB,EAAE;AAAA,EAEnD,wBAAwB,CAAC;AAAA,EACzB,2BAA2B,MAAM,sBAAsB,CAAC,CAAC;AAAA,EAEzD,qBAAqB,CAAC,eAAyB,sBAAsB,CAAC,CAAC;AAAA,EACvE,oBAAoB,MAAM,sBAAsB,EAAE;AAAA,EAClD,uBAAuB,MAAM;AAAA,EAAC;AAAA,EAC9B,eAAe,CAAC;AAAA,EAEhB,kBAAkB,IAAK,MAAkC;AAAA,IACvD,IAAI,kBAA0B;AAC5B,YAAM,IAAI,MAAM,uEAAuE;AAAA,IACzF;AAAA,IAEA,IAAI,UAAkC;AACpC,aAAO,CAAC;AAAA,IACV;AAAA,IACA,IAAI,OAA4B;AAC9B,aAAO,CAAC;AAAA,IACV;AAAA,EACF,EAAG;AAAA,EAEH,6BAA6B,CAAC;AAAA,EAC9B,gCAAgC,MAAM;AAAA,EAAC;AAAA,EACvC,mCAAmC,MAAM;AAAA,EAAC;AAAA,EAC1C,gBAAgB;AAAA,EAChB,eAAe,CAAC;AAAA,EAChB,kBAAkB,MAAM;AAAA,EAAC;AAAA,EACzB,kBAAkB,EAAE,SAAS,CAAC,EAAE;AAAA,EAChC,yBAAyB,MAAM;AAAA,EAAC;AAAA,EAChC,cAAc;AAAA,EACd,iBAAiB,MAAM;AAAA,EAAC;AAAA,EACxB,qBAAqB,CAAC;AAAA,EACtB,WAAW;AAAA,EACX,UAAU;AAAA,EACV,aAAa,MAAM;AAAA,EAAC;AAAA,EACpB,OAAO;AAAA,EACP,UAAU,MAAM;AAAA,EAAC;AAAA,EACjB,wBAAwB,EAAE,SAAS,KAAK;AAAA,EACxC,iBAAiB,CAAC;AAAA,EAClB,YAAY,CAAC;AAAA,EACb,eAAe,MAAM;AAAA,EAAC;AAAA,EACtB,0BAA0B;AAAA,EAC1B,6BAA6B,MAAM;AAAA,EACnC,gCAAgC,MAAM;AAAA,EACtC,SAAS;AACX;AAEO,IAAM,iBAAiB,MAAM,cAAoC,mBAAmB;AAEpF,SAAS,oBAA0C;AACxD,QAAM,UAAU,MAAM,WAAW,cAAc;AAC/C,MAAI,YAAY,qBAAqB;AACnC,UAAM,IAAI,MAAM,uEAAuE;AAAA,EACzF;AACA,SAAO;AACT;AAEA,SAAS,sBAAyB,QAAc;AAC9C,QAAM,IAAI,MAAM,uEAAuE;AACzF;","names":[]}