@copilotkit/react-core 1.4.8 → 1.5.0-coagents-v0-3.0

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 (107) hide show
  1. package/CHANGELOG.md +14 -0
  2. package/dist/{chunk-X6ZF5WAX.mjs → chunk-35EN6BG4.mjs} +2 -2
  3. package/dist/{chunk-FSC4A3JN.mjs → chunk-42N5VKIX.mjs} +23 -5
  4. package/dist/{chunk-FSC4A3JN.mjs.map → chunk-42N5VKIX.mjs.map} +1 -1
  5. package/dist/{chunk-AG7FH7OD.mjs → chunk-5FYKUKG3.mjs} +2 -2
  6. package/dist/{chunk-YUY5ZAST.mjs → chunk-ALR5W5JK.mjs} +17 -8
  7. package/dist/chunk-ALR5W5JK.mjs.map +1 -0
  8. package/dist/{chunk-6EMLM6WX.mjs → chunk-BT6WK2JZ.mjs} +43 -6
  9. package/dist/chunk-BT6WK2JZ.mjs.map +1 -0
  10. package/dist/{chunk-NTLCOVE5.mjs → chunk-QTDCEDOC.mjs} +141 -70
  11. package/dist/chunk-QTDCEDOC.mjs.map +1 -0
  12. package/dist/{chunk-IFTHM7LF.mjs → chunk-QX6V774L.mjs} +6 -8
  13. package/dist/chunk-QX6V774L.mjs.map +1 -0
  14. package/dist/{chunk-XQFVXX6R.mjs → chunk-TQN3EZWQ.mjs} +10 -2
  15. package/dist/chunk-TQN3EZWQ.mjs.map +1 -0
  16. package/dist/{chunk-UOVONDR6.mjs → chunk-V3PFWGIY.mjs} +2 -2
  17. package/dist/{chunk-IVYL7JRC.mjs → chunk-VMP6JWBB.mjs} +12 -3
  18. package/dist/{chunk-IVYL7JRC.mjs.map → chunk-VMP6JWBB.mjs.map} +1 -1
  19. package/dist/chunk-XERJQUHA.mjs +31 -0
  20. package/dist/chunk-XERJQUHA.mjs.map +1 -0
  21. package/dist/components/copilot-provider/copilotkit.js +19 -2
  22. package/dist/components/copilot-provider/copilotkit.js.map +1 -1
  23. package/dist/components/copilot-provider/copilotkit.mjs +2 -2
  24. package/dist/components/copilot-provider/index.js +19 -2
  25. package/dist/components/copilot-provider/index.js.map +1 -1
  26. package/dist/components/copilot-provider/index.mjs +2 -2
  27. package/dist/components/index.js +19 -2
  28. package/dist/components/index.js.map +1 -1
  29. package/dist/components/index.mjs +2 -2
  30. package/dist/context/copilot-context.d.ts +8 -2
  31. package/dist/context/copilot-context.js +9 -1
  32. package/dist/context/copilot-context.js.map +1 -1
  33. package/dist/context/copilot-context.mjs +1 -1
  34. package/dist/context/index.d.ts +1 -1
  35. package/dist/context/index.js +9 -1
  36. package/dist/context/index.js.map +1 -1
  37. package/dist/context/index.mjs +1 -1
  38. package/dist/hooks/index.d.ts +2 -1
  39. package/dist/hooks/index.js +264 -95
  40. package/dist/hooks/index.js.map +1 -1
  41. package/dist/hooks/index.mjs +16 -9
  42. package/dist/hooks/use-chat.d.ts +20 -0
  43. package/dist/hooks/use-chat.js +171 -77
  44. package/dist/hooks/use-chat.js.map +1 -1
  45. package/dist/hooks/use-chat.mjs +2 -1
  46. package/dist/hooks/use-coagent-state-render.js +9 -1
  47. package/dist/hooks/use-coagent-state-render.js.map +1 -1
  48. package/dist/hooks/use-coagent-state-render.mjs +2 -2
  49. package/dist/hooks/use-coagent.d.ts +14 -1
  50. package/dist/hooks/use-coagent.js +245 -85
  51. package/dist/hooks/use-coagent.js.map +1 -1
  52. package/dist/hooks/use-coagent.mjs +12 -5
  53. package/dist/hooks/use-copilot-action.d.ts +12 -2
  54. package/dist/hooks/use-copilot-action.js +24 -7
  55. package/dist/hooks/use-copilot-action.js.map +1 -1
  56. package/dist/hooks/use-copilot-action.mjs +2 -2
  57. package/dist/hooks/use-copilot-chat.d.ts +1 -0
  58. package/dist/hooks/use-copilot-chat.js +223 -84
  59. package/dist/hooks/use-copilot-chat.js.map +1 -1
  60. package/dist/hooks/use-copilot-chat.mjs +5 -4
  61. package/dist/hooks/use-copilot-readable.js +9 -1
  62. package/dist/hooks/use-copilot-readable.js.map +1 -1
  63. package/dist/hooks/use-copilot-readable.mjs +2 -2
  64. package/dist/hooks/use-make-copilot-document-readable.js +9 -1
  65. package/dist/hooks/use-make-copilot-document-readable.js.map +1 -1
  66. package/dist/hooks/use-make-copilot-document-readable.mjs +2 -2
  67. package/dist/index.d.ts +2 -2
  68. package/dist/index.js +281 -106
  69. package/dist/index.js.map +1 -1
  70. package/dist/index.mjs +17 -10
  71. package/dist/lib/copilot-task.d.ts +1 -1
  72. package/dist/lib/copilot-task.js +33 -13
  73. package/dist/lib/copilot-task.js.map +1 -1
  74. package/dist/lib/copilot-task.mjs +4 -3
  75. package/dist/lib/index.d.ts +1 -1
  76. package/dist/lib/index.js +33 -13
  77. package/dist/lib/index.js.map +1 -1
  78. package/dist/lib/index.mjs +4 -3
  79. package/dist/types/frontend-action.d.ts +21 -2
  80. package/dist/types/frontend-action.js +34 -0
  81. package/dist/types/frontend-action.js.map +1 -1
  82. package/dist/types/frontend-action.mjs +7 -0
  83. package/dist/types/index.d.ts +2 -1
  84. package/dist/types/index.js.map +1 -1
  85. package/dist/utils/extract.js.map +1 -1
  86. package/dist/utils/extract.mjs +2 -2
  87. package/dist/utils/index.js.map +1 -1
  88. package/dist/utils/index.mjs +2 -2
  89. package/package.json +3 -3
  90. package/src/components/copilot-provider/copilotkit.tsx +10 -0
  91. package/src/context/copilot-context.tsx +30 -2
  92. package/src/hooks/index.ts +1 -1
  93. package/src/hooks/use-chat.ts +196 -88
  94. package/src/hooks/use-coagent.ts +21 -4
  95. package/src/hooks/use-copilot-action.ts +38 -10
  96. package/src/hooks/use-copilot-chat.ts +43 -3
  97. package/src/lib/copilot-task.ts +2 -8
  98. package/src/types/frontend-action.ts +55 -2
  99. package/src/types/index.ts +5 -1
  100. package/dist/chunk-6EMLM6WX.mjs.map +0 -1
  101. package/dist/chunk-IFTHM7LF.mjs.map +0 -1
  102. package/dist/chunk-NTLCOVE5.mjs.map +0 -1
  103. package/dist/chunk-XQFVXX6R.mjs.map +0 -1
  104. package/dist/chunk-YUY5ZAST.mjs.map +0 -1
  105. /package/dist/{chunk-X6ZF5WAX.mjs.map → chunk-35EN6BG4.mjs.map} +0 -0
  106. /package/dist/{chunk-AG7FH7OD.mjs.map → chunk-5FYKUKG3.mjs.map} +0 -0
  107. /package/dist/{chunk-UOVONDR6.mjs.map → chunk-V3PFWGIY.mjs.map} +0 -0
@@ -18,10 +18,11 @@ import {
18
18
  Role,
19
19
  CopilotRequestType,
20
20
  ActionInputAvailability,
21
+ loadMessagesFromJsonRepresentation,
21
22
  } from "@copilotkit/runtime-client-gql";
22
23
 
23
24
  import { CopilotApiConfig } from "../context";
24
- import { FrontendAction } from "../types/frontend-action";
25
+ import { FrontendAction, processActionsForRuntimeRequest } from "../types/frontend-action";
25
26
  import { CoagentState } from "../types/coagent-state";
26
27
  import { AgentSession } from "../context/copilot-context";
27
28
  import { useToast } from "../components/toast/toast-provider";
@@ -98,6 +99,27 @@ export type UseChatOptions = {
98
99
  * setState-powered method to update the agent session
99
100
  */
100
101
  setAgentSession: React.Dispatch<React.SetStateAction<AgentSession | null>>;
102
+
103
+ /**
104
+ * The current thread ID.
105
+ */
106
+ threadId: string | null;
107
+ /**
108
+ * set the current thread ID
109
+ */
110
+ setThreadId: (threadId: string | null) => void;
111
+ /**
112
+ * The current run ID.
113
+ */
114
+ runId: string | null;
115
+ /**
116
+ * set the current run ID
117
+ */
118
+ setRunId: (runId: string | null) => void;
119
+ /**
120
+ * The global chat abort controller.
121
+ */
122
+ chatAbortControllerRef: React.MutableRefObject<AbortController | null>;
101
123
  };
102
124
 
103
125
  export type UseChatHelpers = {
@@ -140,19 +162,23 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
140
162
  coagentStatesRef,
141
163
  agentSession,
142
164
  setAgentSession,
165
+ threadId,
166
+ setThreadId,
167
+ runId,
168
+ setRunId,
169
+ chatAbortControllerRef,
143
170
  } = options;
144
-
145
- const abortControllerRef = useRef<AbortController>();
146
- const threadIdRef = useRef<string | null>(null);
147
- const runIdRef = useRef<string | null>(null);
148
171
  const { addGraphQLErrorsToast } = useToast();
149
-
150
172
  const runChatCompletionRef = useRef<(previousMessages: Message[]) => Promise<Message[]>>();
151
173
  // We need to keep a ref of coagent states and session because of renderAndWait - making sure
152
174
  // the latest state is sent to the API
153
175
  // This is a workaround and needs to be addressed in the future
154
176
  const agentSessionRef = useRef<AgentSession | null>(agentSession);
155
177
  agentSessionRef.current = agentSession;
178
+ const threadIdRef = useRef<string | null>(threadId);
179
+ threadIdRef.current = threadId;
180
+ const runIdRef = useRef<string | null>(runId);
181
+ runIdRef.current = runId;
156
182
 
157
183
  const publicApiKey = copilotConfig.publicApiKey;
158
184
 
@@ -180,8 +206,8 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
180
206
  role: Role.Assistant,
181
207
  }),
182
208
  ];
183
- const abortController = new AbortController();
184
- abortControllerRef.current = abortController;
209
+
210
+ chatAbortControllerRef.current = new AbortController();
185
211
 
186
212
  setMessages([...previousMessages, ...newMessages]);
187
213
 
@@ -189,34 +215,13 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
189
215
 
190
216
  const messagesWithContext = [systemMessage, ...(initialMessages || []), ...previousMessages];
191
217
 
218
+ const isAgentRun = agentSessionRef.current !== null;
219
+
192
220
  const stream = runtimeClient.asStream(
193
221
  runtimeClient.generateCopilotResponse({
194
222
  data: {
195
223
  frontend: {
196
- actions: actions
197
- .filter(
198
- (action) =>
199
- action.available !== ActionInputAvailability.Disabled || !action.disabled,
200
- )
201
- .map((action) => {
202
- let available: ActionInputAvailability | undefined =
203
- ActionInputAvailability.Enabled;
204
- if (action.disabled) {
205
- available = ActionInputAvailability.Disabled;
206
- } else if (action.available === "disabled") {
207
- available = ActionInputAvailability.Disabled;
208
- } else if (action.available === "remote") {
209
- available = ActionInputAvailability.Remote;
210
- }
211
- return {
212
- name: action.name,
213
- description: action.description || "",
214
- jsonSchema: JSON.stringify(
215
- actionParametersToJsonSchema(action.parameters || []),
216
- ),
217
- available,
218
- };
219
- }),
224
+ actions: processActionsForRuntimeRequest(actions),
220
225
  url: window.location.href,
221
226
  },
222
227
  threadId: threadIdRef.current,
@@ -254,7 +259,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
254
259
  })),
255
260
  },
256
261
  properties: copilotConfig.properties,
257
- signal: abortControllerRef.current?.signal,
262
+ signal: chatAbortControllerRef.current?.signal,
258
263
  }),
259
264
  );
260
265
 
@@ -263,10 +268,12 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
263
268
 
264
269
  const reader = stream.getReader();
265
270
 
266
- let actionResults: { [id: string]: string } = {};
267
271
  let executedCoAgentStateRenders: string[] = [];
268
272
  let followUp: FrontendAction["followUp"] = undefined;
269
273
 
274
+ let messages: Message[] = [];
275
+ let syncedMessages: Message[] = [];
276
+
270
277
  try {
271
278
  while (true) {
272
279
  let done, value;
@@ -280,6 +287,9 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
280
287
  }
281
288
 
282
289
  if (done) {
290
+ if (chatAbortControllerRef.current.signal.aborted) {
291
+ return [];
292
+ }
283
293
  break;
284
294
  }
285
295
 
@@ -290,7 +300,10 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
290
300
  threadIdRef.current = value.generateCopilotResponse.threadId || null;
291
301
  runIdRef.current = value.generateCopilotResponse.runId || null;
292
302
 
293
- const messages = convertGqlOutputToMessages(
303
+ setThreadId(threadIdRef.current);
304
+ setRunId(runIdRef.current);
305
+
306
+ messages = convertGqlOutputToMessages(
294
307
  filterAdjacentAgentStateMessages(value.generateCopilotResponse.messages),
295
308
  );
296
309
 
@@ -300,7 +313,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
300
313
 
301
314
  newMessages = [];
302
315
 
303
- // request failed, display error message
316
+ // request failed, display error message and quit
304
317
  if (
305
318
  value.generateCopilotResponse.status?.__typename === "FailedResponseStatus" &&
306
319
  value.generateCopilotResponse.status.reason === "GUARDRAILS_VALIDATION_FAILED"
@@ -311,57 +324,16 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
311
324
  content: value.generateCopilotResponse.status.details?.guardrailsReason || "",
312
325
  }),
313
326
  ];
327
+ setMessages([...previousMessages, ...newMessages]);
328
+ break;
314
329
  }
315
330
 
316
331
  // add messages to the chat
317
332
  else {
318
- for (const message of messages) {
319
- newMessages.push(message);
320
- // execute regular action executions
321
- if (
322
- message.isActionExecutionMessage() &&
323
- message.status.code !== MessageStatusCode.Pending &&
324
- message.scope === "client" &&
325
- onFunctionCall
326
- ) {
327
- if (!(message.id in actionResults)) {
328
- // Do not execute a function call if guardrails are enabled but the status is not known
329
- if (guardrailsEnabled && value.generateCopilotResponse.status === undefined) {
330
- break;
331
- }
332
- // execute action
333
- try {
334
- // We update the message state before calling the handler so that the render
335
- // function can be called with `executing` state
336
- setMessages([...previousMessages, ...newMessages]);
337
-
338
- const action = actions.find((action) => action.name === message.name);
339
-
340
- if (action) {
341
- followUp = action.followUp;
342
- }
333
+ newMessages = [...messages];
343
334
 
344
- const result = await onFunctionCall({
345
- messages: previousMessages,
346
- name: message.name,
347
- args: message.arguments,
348
- });
349
- actionResults[message.id] = result;
350
- } catch (e) {
351
- actionResults[message.id] = `Failed to execute action ${message.name}`;
352
- console.error(`Failed to execute action ${message.name}: ${e}`);
353
- }
354
- }
355
- // add the result message
356
- newMessages.push(
357
- new ResultMessage({
358
- result: ResultMessage.encodeResult(actionResults[message.id]),
359
- actionExecutionId: message.id,
360
- actionName: message.name,
361
- }),
362
- );
363
- }
364
- // execute coagent actions
335
+ for (const message of messages) {
336
+ // execute onCoAgentStateRender handler
365
337
  if (
366
338
  message.isAgentStateMessage() &&
367
339
  !message.active &&
@@ -387,6 +359,14 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
387
359
  .find((message) => message.isAgentStateMessage());
388
360
 
389
361
  if (lastAgentStateMessage) {
362
+ if (
363
+ lastAgentStateMessage.state.messages &&
364
+ lastAgentStateMessage.state.messages.length > 0
365
+ ) {
366
+ syncedMessages = loadMessagesFromJsonRepresentation(
367
+ lastAgentStateMessage.state.messages,
368
+ );
369
+ }
390
370
  setCoagentStatesWithRef((prevAgentStates) => ({
391
371
  ...prevAgentStates,
392
372
  [lastAgentStateMessage.agentName]: {
@@ -416,14 +396,88 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
416
396
  setMessages([...previousMessages, ...newMessages]);
417
397
  }
418
398
  }
399
+ const finalMessages = constructFinalMessages(syncedMessages, previousMessages, newMessages);
400
+
401
+ let didExecuteAction = false;
402
+
403
+ // execute regular action executions that are specific to the frontend (last actions)
404
+ if (onFunctionCall) {
405
+ // Find consecutive action execution messages at the end
406
+ const lastMessages = [];
407
+ for (let i = finalMessages.length - 1; i >= 0; i--) {
408
+ const message = finalMessages[i];
409
+ if (
410
+ message.isActionExecutionMessage() &&
411
+ message.status.code !== MessageStatusCode.Pending
412
+ ) {
413
+ lastMessages.unshift(message);
414
+ } else {
415
+ break;
416
+ }
417
+ }
418
+
419
+ for (const message of lastMessages) {
420
+ // We update the message state before calling the handler so that the render
421
+ // function can be called with `executing` state
422
+ setMessages(finalMessages);
423
+
424
+ const action = actions.find((action) => action.name === message.name);
425
+
426
+ if (action) {
427
+ followUp = action.followUp;
428
+ let result: any;
429
+ try {
430
+ result = await Promise.race([
431
+ onFunctionCall({
432
+ messages: previousMessages,
433
+ name: message.name,
434
+ args: message.arguments,
435
+ }),
436
+ new Promise((resolve) =>
437
+ chatAbortControllerRef.current?.signal.addEventListener("abort", () =>
438
+ resolve("Operation was aborted by the user"),
439
+ ),
440
+ ),
441
+ // if the user stopped generation, we also abort consecutive actions
442
+ new Promise((resolve) => {
443
+ if (chatAbortControllerRef.current?.signal.aborted) {
444
+ resolve("Operation was aborted by the user");
445
+ }
446
+ }),
447
+ ]);
448
+ } catch (e) {
449
+ result = `Failed to execute action ${message.name}`;
450
+ console.error(`Failed to execute action ${message.name}: ${e}`);
451
+ }
452
+ didExecuteAction = true;
453
+ const messageIndex = finalMessages.findIndex((msg) => msg.id === message.id);
454
+ finalMessages.splice(
455
+ messageIndex + 1,
456
+ 0,
457
+ new ResultMessage({
458
+ id: "result-" + message.id,
459
+ result: ResultMessage.encodeResult(result),
460
+ actionExecutionId: message.id,
461
+ actionName: message.name,
462
+ }),
463
+ );
464
+ }
465
+ }
466
+
467
+ setMessages(finalMessages);
468
+ }
419
469
 
420
470
  if (
421
471
  // if followUp is not explicitly false
422
472
  followUp !== false &&
423
- // if we have client side results
424
- (Object.values(actionResults).length ||
425
- // or the last message we received is a result
426
- (newMessages.length && newMessages[newMessages.length - 1].isResultMessage()))
473
+ // and we executed an action
474
+ (didExecuteAction ||
475
+ // the last message is a server side result
476
+ (!isAgentRun &&
477
+ finalMessages.length &&
478
+ finalMessages[finalMessages.length - 1].isResultMessage())) &&
479
+ // the user did not stop generation
480
+ !chatAbortControllerRef.current?.signal.aborted
427
481
  ) {
428
482
  // run the completion again and return the result
429
483
 
@@ -431,7 +485,32 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
431
485
  // - tried using react-dom's flushSync, but it did not work
432
486
  await new Promise((resolve) => setTimeout(resolve, 10));
433
487
 
434
- return await runChatCompletionRef.current!([...previousMessages, ...newMessages]);
488
+ return await runChatCompletionRef.current!(finalMessages);
489
+ } else if (chatAbortControllerRef.current?.signal.aborted) {
490
+ // filter out all the action execution messages that do not have a consecutive matching result message
491
+ const repairedMessages = finalMessages.filter((message, actionExecutionIndex) => {
492
+ if (message.isActionExecutionMessage()) {
493
+ return finalMessages.find(
494
+ (msg, resultIndex) =>
495
+ msg.isResultMessage() &&
496
+ msg.actionExecutionId === message.id &&
497
+ resultIndex === actionExecutionIndex + 1,
498
+ );
499
+ }
500
+ return true;
501
+ });
502
+ const repairedMessageIds = repairedMessages.map((message) => message.id);
503
+ setMessages(repairedMessages);
504
+
505
+ if (agentSessionRef.current?.nodeName) {
506
+ setAgentSession({
507
+ threadId: agentSessionRef.current.threadId,
508
+ agentName: agentSessionRef.current.agentName,
509
+ nodeName: "__end__",
510
+ });
511
+ }
512
+ // only return new messages that were not filtered out
513
+ return newMessages.filter((message) => repairedMessageIds.includes(message.id));
435
514
  } else {
436
515
  return newMessages.slice();
437
516
  }
@@ -496,7 +575,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
496
575
  }, [isLoading, messages, setMessages, runChatCompletionAndHandleFunctionCall]);
497
576
 
498
577
  const stop = (): void => {
499
- abortControllerRef.current?.abort();
578
+ chatAbortControllerRef.current?.abort("Stop was called");
500
579
  };
501
580
 
502
581
  return {
@@ -506,3 +585,32 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
506
585
  runChatCompletion: () => runChatCompletionRef.current!(messages),
507
586
  };
508
587
  }
588
+
589
+ function constructFinalMessages(
590
+ syncedMessages: Message[],
591
+ previousMessages: Message[],
592
+ newMessages: Message[],
593
+ ): Message[] {
594
+ const finalMessages =
595
+ syncedMessages.length > 0 ? [...syncedMessages] : [...previousMessages, ...newMessages];
596
+
597
+ if (syncedMessages.length > 0) {
598
+ const messagesWithAgentState = [...previousMessages, ...newMessages];
599
+
600
+ let previousMessageId: string | undefined = undefined;
601
+
602
+ for (const message of messagesWithAgentState) {
603
+ if (message.isAgentStateMessage()) {
604
+ // insert this message into finalMessages after the position of previousMessageId
605
+ const index = finalMessages.findIndex((msg) => msg.id === previousMessageId);
606
+ if (index !== -1) {
607
+ finalMessages.splice(index + 1, 0, message);
608
+ }
609
+ }
610
+
611
+ previousMessageId = message.id;
612
+ }
613
+ }
614
+
615
+ return finalMessages;
616
+ }
@@ -265,7 +265,11 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
265
265
  } else if (coagentStates[name] === undefined) {
266
266
  setState(options.initialState === undefined ? {} : options.initialState);
267
267
  }
268
- }, [isExternalStateManagement(options) ? JSON.stringify(options.state) : undefined]);
268
+ }, [
269
+ isExternalStateManagement(options) ? JSON.stringify(options.state) : undefined,
270
+ // reset initialstate on reset
271
+ coagentStates[name] === undefined,
272
+ ]);
269
273
 
270
274
  const runAgentCallback = useAsyncCallback(
271
275
  async (hint?: HintFunction) => {
@@ -288,23 +292,36 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
288
292
  };
289
293
  }
290
294
 
291
- function startAgent(name: string, context: CopilotContextParams) {
295
+ export function startAgent(name: string, context: CopilotContextParams) {
292
296
  const { setAgentSession } = context;
293
297
  setAgentSession({
294
298
  agentName: name,
295
299
  });
296
300
  }
297
301
 
298
- function stopAgent(name: string, context: CopilotContextParams) {
302
+ export function stopAgent(name: string, context: CopilotContextParams) {
299
303
  const { agentSession, setAgentSession } = context;
300
304
  if (agentSession && agentSession.agentName === name) {
301
305
  setAgentSession(null);
306
+ context.setCoagentStates((prevAgentStates) => {
307
+ return {
308
+ ...prevAgentStates,
309
+ [name]: {
310
+ ...prevAgentStates[name],
311
+ running: false,
312
+ active: false,
313
+ threadId: undefined,
314
+ nodeName: undefined,
315
+ runId: undefined,
316
+ },
317
+ };
318
+ });
302
319
  } else {
303
320
  console.warn(`No agent session found for ${name}`);
304
321
  }
305
322
  }
306
323
 
307
- async function runAgent(
324
+ export async function runAgent(
308
325
  name: string,
309
326
  context: CopilotContextParams & CopilotMessagesContextParams,
310
327
  appendMessage: (message: Message) => Promise<void>,
@@ -71,6 +71,15 @@
71
71
  * );
72
72
  * },
73
73
  * });
74
+ *
75
+ * @example
76
+ * // Catch all action allows you to render actions that are not defined in the frontend
77
+ * useCopilotAction({
78
+ * name: "*",
79
+ * render: ({ name, args, status, result, handler, respond }) => {
80
+ * return <div>Rendering action: {name}</div>;
81
+ * },
82
+ * });
74
83
  */
75
84
 
76
85
  /**
@@ -129,6 +138,7 @@ import {
129
138
  ActionRenderProps,
130
139
  ActionRenderPropsNoArgsWait,
131
140
  ActionRenderPropsWait,
141
+ CatchAllFrontendAction,
132
142
  FrontendAction,
133
143
  } from "../types/frontend-action";
134
144
 
@@ -142,7 +152,7 @@ import {
142
152
  // useCallback, useMemo or other memoization techniques are not suitable here,
143
153
  // because they will cause a infinite rerender loop.
144
154
  export function useCopilotAction<const T extends Parameter[] | [] = []>(
145
- action: FrontendAction<T>,
155
+ action: FrontendAction<T> | CatchAllFrontendAction,
146
156
  dependencies?: any[],
147
157
  ): void {
148
158
  const { setAction, removeAction, actions, chatComponentsCache } = useCopilotContext();
@@ -152,9 +162,14 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
152
162
  // clone the action to avoid mutating the original object
153
163
  action = { ...action };
154
164
 
155
- // If the developer provides a renderAndWait function, we transform the action
165
+ // If the developer provides a renderAndWaitForResponse function, we transform the action
156
166
  // to use a promise internally, so that we can treat it like a normal action.
157
- if (action.renderAndWait || action.renderAndWaitForResponse) {
167
+ if (
168
+ // renderAndWaitForResponse is not available for catch all actions
169
+ isFrontendAction(action) &&
170
+ // check if renderAndWaitForResponse is set
171
+ (action.renderAndWait || action.renderAndWaitForResponse)
172
+ ) {
158
173
  const renderAndWait = action.renderAndWait || action.renderAndWaitForResponse;
159
174
  // remove the renderAndWait function from the action
160
175
  action.renderAndWait = undefined;
@@ -212,10 +227,15 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
212
227
  // This ensures that any captured variables in the handler are up to date.
213
228
  if (dependencies === undefined) {
214
229
  if (actions[idRef.current]) {
215
- actions[idRef.current].handler = action.handler as any;
230
+ // catch all actions don't have a handler
231
+ if (isFrontendAction(action)) {
232
+ actions[idRef.current].handler = action.handler as any;
233
+ }
216
234
  if (typeof action.render === "function") {
217
235
  if (chatComponentsCache.current !== null) {
218
- chatComponentsCache.current.actions[action.name] = action.render;
236
+ // TODO: using as any here because the type definitions are getting to tricky
237
+ // not wasting time on this now - we know the types are compatible
238
+ chatComponentsCache.current.actions[action.name] = action.render as any;
219
239
  }
220
240
  }
221
241
  }
@@ -224,23 +244,25 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
224
244
  useEffect(() => {
225
245
  setAction(idRef.current, action as any);
226
246
  if (chatComponentsCache.current !== null && action.render !== undefined) {
227
- chatComponentsCache.current.actions[action.name] = action.render;
247
+ // see comment about type safety above
248
+ chatComponentsCache.current.actions[action.name] = action.render as any;
228
249
  }
229
250
  return () => {
230
251
  // NOTE: For now, we don't remove the chatComponentsCache entry when the action is removed.
231
252
  // This is because we currently don't have access to the messages array in CopilotContext.
253
+ // UPDATE: We now have access, we should remove the entry if not referenced by any message.
232
254
  removeAction(idRef.current);
233
255
  };
234
256
  }, [
235
257
  setAction,
236
258
  removeAction,
237
- action.description,
259
+ isFrontendAction(action) ? action.description : undefined,
238
260
  action.name,
239
- action.disabled,
240
- action.available,
261
+ isFrontendAction(action) ? action.disabled : undefined,
262
+ isFrontendAction(action) ? action.available : undefined,
241
263
  // This should be faster than deep equality checking
242
264
  // In addition, all major JS engines guarantee the order of object keys
243
- JSON.stringify(action.parameters),
265
+ JSON.stringify(isFrontendAction(action) ? action.parameters : []),
244
266
  // include render only if it's a string
245
267
  typeof action.render === "string" ? action.render : undefined,
246
268
  // dependencies set by the developer
@@ -248,6 +270,12 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
248
270
  ]);
249
271
  }
250
272
 
273
+ function isFrontendAction<T extends Parameter[]>(
274
+ action: FrontendAction<T> | CatchAllFrontendAction,
275
+ ): action is FrontendAction<T> {
276
+ return action.name !== "*";
277
+ }
278
+
251
279
  interface RenderAndWaitForResponse {
252
280
  promise: Promise<any>;
253
281
  resolve: (result: any) => void;
@@ -39,7 +39,7 @@
39
39
  * ```
40
40
  */
41
41
  import { useRef, useEffect, useCallback } from "react";
42
- import { useCopilotContext } from "../context/copilot-context";
42
+ import { AgentSession, useCopilotContext } from "../context/copilot-context";
43
43
  import { Message, Role, TextMessage } from "@copilotkit/runtime-client-gql";
44
44
  import { SystemMessageFunction } from "../types";
45
45
  import { useChat } from "./use-chat";
@@ -79,6 +79,7 @@ export interface UseCopilotChatReturn {
79
79
  deleteMessage: (messageId: string) => void;
80
80
  reloadMessages: () => Promise<void>;
81
81
  stopGeneration: () => void;
82
+ reset: () => void;
82
83
  isLoading: boolean;
83
84
  runChatCompletion: () => Promise<Message[]>;
84
85
  }
@@ -100,6 +101,12 @@ export function useCopilotChat({
100
101
  coAgentStateRenders,
101
102
  agentSession,
102
103
  setAgentSession,
104
+ agentLock,
105
+ threadId,
106
+ setThreadId,
107
+ runId,
108
+ setRunId,
109
+ chatAbortControllerRef,
103
110
  } = useCopilotContext();
104
111
  const { messages, setMessages } = useCopilotMessagesContext();
105
112
 
@@ -158,11 +165,16 @@ export function useCopilotChat({
158
165
  setCoagentStatesWithRef,
159
166
  agentSession,
160
167
  setAgentSession,
168
+ threadId,
169
+ setThreadId,
170
+ runId,
171
+ setRunId,
172
+ chatAbortControllerRef,
161
173
  });
162
174
 
163
- // this is a workaround born out of a bug that Athena insessently ran into.
175
+ // this is a workaround born out of a bug that Athena incessantly ran into.
164
176
  // We could not find the origin of the bug, however, it was clear that an outdated version of the append function was being used somehow --
165
- // it referecned the old state of the messages array, and not the latest one.
177
+ // it referenced the old state of the messages array, and not the latest one.
166
178
  //
167
179
  // We want to make copilotkit as abuse-proof as possible, so we are adding this workaround to ensure that the latest version of the append function is always used.
168
180
  //
@@ -207,12 +219,40 @@ export function useCopilotChat({
207
219
  return await latestRunChatCompletion.current!();
208
220
  }, [latestRunChatCompletion]);
209
221
 
222
+ const reset = useCallback(() => {
223
+ latestStopFunc();
224
+ setMessages([]);
225
+ setThreadId(null);
226
+ setRunId(null);
227
+ setCoagentStatesWithRef({});
228
+ let initialAgentSession: AgentSession | null = null;
229
+ if (agentLock) {
230
+ initialAgentSession = {
231
+ agentName: agentLock,
232
+ };
233
+ }
234
+ setAgentSession(initialAgentSession);
235
+ }, [
236
+ latestStopFunc,
237
+ setMessages,
238
+ setThreadId,
239
+ setCoagentStatesWithRef,
240
+ setAgentSession,
241
+ agentLock,
242
+ ]);
243
+
244
+ const latestReset = useUpdatedRef(reset);
245
+ const latestResetFunc = useCallback(() => {
246
+ return latestReset.current();
247
+ }, [latestReset]);
248
+
210
249
  return {
211
250
  visibleMessages: messages,
212
251
  appendMessage: latestAppendFunc,
213
252
  setMessages: latestSetMessagesFunc,
214
253
  reloadMessages: latestReloadFunc,
215
254
  stopGeneration: latestStopFunc,
255
+ reset: latestResetFunc,
216
256
  deleteMessage: latestDeleteFunc,
217
257
  runChatCompletion: latestRunChatCompletionFunc,
218
258
  isLoading,