@copilotkit/react-core 1.5.0-tyler-reset-chat.0 → 1.5.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 (140) hide show
  1. package/CHANGELOG.md +158 -4
  2. package/README.md +2 -0
  3. package/dist/{chunk-3AYELZJS.mjs → chunk-35EN6BG4.mjs} +2 -2
  4. package/dist/{chunk-3AYELZJS.mjs.map → chunk-35EN6BG4.mjs.map} +1 -1
  5. package/dist/{chunk-SEPYQHH7.mjs → chunk-42N5VKIX.mjs} +34 -28
  6. package/dist/chunk-42N5VKIX.mjs.map +1 -0
  7. package/dist/{chunk-USL3EHJB.mjs → chunk-5FYKUKG3.mjs} +2 -2
  8. package/dist/{chunk-ODN4H66E.mjs → chunk-7LRDVJH5.mjs} +6 -2
  9. package/dist/chunk-7LRDVJH5.mjs.map +1 -0
  10. package/dist/{chunk-CZMEZR6F.mjs → chunk-BT6WK2JZ.mjs} +34 -19
  11. package/dist/chunk-BT6WK2JZ.mjs.map +1 -0
  12. package/dist/{chunk-3R4J2TPH.mjs → chunk-EUU6NNYU.mjs} +29 -13
  13. package/dist/chunk-EUU6NNYU.mjs.map +1 -0
  14. package/dist/chunk-QCUP6HLK.mjs +37 -0
  15. package/dist/chunk-QCUP6HLK.mjs.map +1 -0
  16. package/dist/chunk-QTDCEDOC.mjs +392 -0
  17. package/dist/chunk-QTDCEDOC.mjs.map +1 -0
  18. package/dist/{chunk-JR55I3FL.mjs → chunk-QX6V774L.mjs} +6 -8
  19. package/dist/chunk-QX6V774L.mjs.map +1 -0
  20. package/dist/{chunk-2KCEHGSI.mjs → chunk-SFPANIOY.mjs} +99 -49
  21. package/dist/chunk-SFPANIOY.mjs.map +1 -0
  22. package/dist/{chunk-2JP64U3A.mjs → chunk-TQN3EZWQ.mjs} +4 -1
  23. package/dist/chunk-TQN3EZWQ.mjs.map +1 -0
  24. package/dist/{chunk-XUPO37VH.mjs → chunk-V3PFWGIY.mjs} +2 -2
  25. package/dist/{chunk-6QKA3SNN.mjs → chunk-VMP6JWBB.mjs} +21 -5
  26. package/dist/chunk-VMP6JWBB.mjs.map +1 -0
  27. package/dist/chunk-XERJQUHA.mjs +31 -0
  28. package/dist/chunk-XERJQUHA.mjs.map +1 -0
  29. package/dist/components/copilot-provider/copilotkit.js +173 -92
  30. package/dist/components/copilot-provider/copilotkit.js.map +1 -1
  31. package/dist/components/copilot-provider/copilotkit.mjs +5 -4
  32. package/dist/components/copilot-provider/index.js +173 -92
  33. package/dist/components/copilot-provider/index.js.map +1 -1
  34. package/dist/components/copilot-provider/index.mjs +5 -4
  35. package/dist/components/error-boundary/error-boundary.d.ts +22 -0
  36. package/dist/components/error-boundary/error-boundary.js +183 -0
  37. package/dist/components/error-boundary/error-boundary.js.map +1 -0
  38. package/dist/components/error-boundary/error-boundary.mjs +12 -0
  39. package/dist/components/error-boundary/error-boundary.mjs.map +1 -0
  40. package/dist/components/error-boundary/error-utils.d.ts +11 -0
  41. package/dist/components/error-boundary/error-utils.js +177 -0
  42. package/dist/components/error-boundary/error-utils.js.map +1 -0
  43. package/dist/components/error-boundary/error-utils.mjs +13 -0
  44. package/dist/components/error-boundary/error-utils.mjs.map +1 -0
  45. package/dist/components/index.js +173 -92
  46. package/dist/components/index.js.map +1 -1
  47. package/dist/components/index.mjs +5 -4
  48. package/dist/components/toast/toast-provider.d.ts +2 -1
  49. package/dist/components/toast/toast-provider.js +76 -62
  50. package/dist/components/toast/toast-provider.js.map +1 -1
  51. package/dist/components/toast/toast-provider.mjs +1 -1
  52. package/dist/context/copilot-context.d.ts +4 -2
  53. package/dist/context/copilot-context.js +3 -0
  54. package/dist/context/copilot-context.js.map +1 -1
  55. package/dist/context/copilot-context.mjs +1 -1
  56. package/dist/context/index.d.ts +1 -1
  57. package/dist/context/index.js +3 -0
  58. package/dist/context/index.js.map +1 -1
  59. package/dist/context/index.mjs +1 -1
  60. package/dist/hooks/index.js +554 -308
  61. package/dist/hooks/index.js.map +1 -1
  62. package/dist/hooks/index.mjs +13 -11
  63. package/dist/hooks/use-chat.d.ts +6 -2
  64. package/dist/hooks/use-chat.js +434 -219
  65. package/dist/hooks/use-chat.js.map +1 -1
  66. package/dist/hooks/use-chat.mjs +4 -3
  67. package/dist/hooks/use-coagent-state-render.d.ts +2 -2
  68. package/dist/hooks/use-coagent-state-render.js +3 -0
  69. package/dist/hooks/use-coagent-state-render.js.map +1 -1
  70. package/dist/hooks/use-coagent-state-render.mjs +2 -2
  71. package/dist/hooks/use-coagent.d.ts +1 -1
  72. package/dist/hooks/use-coagent.js +510 -277
  73. package/dist/hooks/use-coagent.js.map +1 -1
  74. package/dist/hooks/use-coagent.mjs +9 -7
  75. package/dist/hooks/use-copilot-action.d.ts +12 -2
  76. package/dist/hooks/use-copilot-action.js +157 -16
  77. package/dist/hooks/use-copilot-action.js.map +1 -1
  78. package/dist/hooks/use-copilot-action.mjs +4 -2
  79. package/dist/hooks/use-copilot-chat.d.ts +1 -0
  80. package/dist/hooks/use-copilot-chat.js +483 -253
  81. package/dist/hooks/use-copilot-chat.js.map +1 -1
  82. package/dist/hooks/use-copilot-chat.mjs +8 -6
  83. package/dist/hooks/use-copilot-readable.js +3 -0
  84. package/dist/hooks/use-copilot-readable.js.map +1 -1
  85. package/dist/hooks/use-copilot-readable.mjs +2 -2
  86. package/dist/hooks/use-copilot-runtime-client.js +110 -4
  87. package/dist/hooks/use-copilot-runtime-client.js.map +1 -1
  88. package/dist/hooks/use-copilot-runtime-client.mjs +2 -2
  89. package/dist/hooks/use-make-copilot-document-readable.js +3 -0
  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 +1 -1
  93. package/dist/index.js +616 -401
  94. package/dist/index.js.map +1 -1
  95. package/dist/index.mjs +14 -12
  96. package/dist/lib/copilot-task.d.ts +1 -1
  97. package/dist/lib/copilot-task.js +33 -13
  98. package/dist/lib/copilot-task.js.map +1 -1
  99. package/dist/lib/copilot-task.mjs +7 -5
  100. package/dist/lib/index.d.ts +1 -1
  101. package/dist/lib/index.js +33 -13
  102. package/dist/lib/index.js.map +1 -1
  103. package/dist/lib/index.mjs +7 -5
  104. package/dist/types/frontend-action.d.ts +21 -2
  105. package/dist/types/frontend-action.js +34 -0
  106. package/dist/types/frontend-action.js.map +1 -1
  107. package/dist/types/frontend-action.mjs +7 -0
  108. package/dist/types/index.d.ts +2 -1
  109. package/dist/types/index.js.map +1 -1
  110. package/dist/utils/extract.js.map +1 -1
  111. package/dist/utils/extract.mjs +5 -4
  112. package/dist/utils/index.js.map +1 -1
  113. package/dist/utils/index.mjs +5 -4
  114. package/package.json +5 -5
  115. package/src/components/copilot-provider/copilotkit.tsx +22 -1
  116. package/src/components/error-boundary/error-boundary.tsx +42 -0
  117. package/src/components/error-boundary/error-utils.tsx +95 -0
  118. package/src/components/toast/toast-provider.tsx +10 -49
  119. package/src/context/copilot-context.tsx +17 -2
  120. package/src/hooks/use-chat.ts +375 -279
  121. package/src/hooks/use-coagent-state-render.ts +2 -2
  122. package/src/hooks/use-coagent.ts +34 -28
  123. package/src/hooks/use-copilot-action.ts +50 -15
  124. package/src/hooks/use-copilot-chat.ts +28 -14
  125. package/src/hooks/use-copilot-runtime-client.ts +4 -0
  126. package/src/lib/copilot-task.ts +2 -8
  127. package/src/types/frontend-action.ts +55 -2
  128. package/src/types/index.ts +5 -1
  129. package/dist/chunk-2JP64U3A.mjs.map +0 -1
  130. package/dist/chunk-2KCEHGSI.mjs.map +0 -1
  131. package/dist/chunk-3R4J2TPH.mjs.map +0 -1
  132. package/dist/chunk-6EN7J4V2.mjs +0 -317
  133. package/dist/chunk-6EN7J4V2.mjs.map +0 -1
  134. package/dist/chunk-6QKA3SNN.mjs.map +0 -1
  135. package/dist/chunk-CZMEZR6F.mjs.map +0 -1
  136. package/dist/chunk-JR55I3FL.mjs.map +0 -1
  137. package/dist/chunk-ODN4H66E.mjs.map +0 -1
  138. package/dist/chunk-SEPYQHH7.mjs.map +0 -1
  139. /package/dist/{chunk-USL3EHJB.mjs.map → chunk-5FYKUKG3.mjs.map} +0 -0
  140. /package/dist/{chunk-XUPO37VH.mjs.map → chunk-V3PFWGIY.mjs.map} +0 -0
@@ -18,14 +18,16 @@ 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";
28
29
  import { useCopilotRuntimeClient } from "./use-copilot-runtime-client";
30
+ import { useAsyncCallback } from "../components/error-boundary/error-utils";
29
31
 
30
32
  export type UseChatOptions = {
31
33
  /**
@@ -81,12 +83,12 @@ export type UseChatOptions = {
81
83
  /**
82
84
  * The current list of coagent states.
83
85
  */
84
- coagentStates: Record<string, CoagentState>;
86
+ coagentStatesRef: React.RefObject<Record<string, CoagentState>>;
85
87
 
86
88
  /**
87
89
  * setState-powered method to update the agent states
88
90
  */
89
- setCoagentStates: React.Dispatch<React.SetStateAction<Record<string, CoagentState>>>;
91
+ setCoagentStatesWithRef: React.Dispatch<React.SetStateAction<Record<string, CoagentState>>>;
90
92
 
91
93
  /**
92
94
  * The current agent session.
@@ -137,6 +139,11 @@ export type UseChatHelpers = {
137
139
  * Abort the current request immediately, keep the generated tokens if any.
138
140
  */
139
141
  stop: () => void;
142
+
143
+ /**
144
+ * Run the chat completion.
145
+ */
146
+ runChatCompletion: () => Promise<Message[]>;
140
147
  };
141
148
 
142
149
  export function useChat(options: UseChatOptions): UseChatHelpers {
@@ -151,8 +158,8 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
151
158
  actions,
152
159
  onFunctionCall,
153
160
  onCoAgentStateRender,
154
- setCoagentStates,
155
- coagentStates,
161
+ setCoagentStatesWithRef,
162
+ coagentStatesRef,
156
163
  agentSession,
157
164
  setAgentSession,
158
165
  threadId,
@@ -161,18 +168,11 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
161
168
  setRunId,
162
169
  chatAbortControllerRef,
163
170
  } = options;
164
-
165
- const abortController = new AbortController();
166
- chatAbortControllerRef.current = abortController;
167
-
168
171
  const { addGraphQLErrorsToast } = useToast();
169
-
170
172
  const runChatCompletionRef = useRef<(previousMessages: Message[]) => Promise<Message[]>>();
171
- // We need to keep a ref of coagent states because of renderAndWait - making sure
173
+ // We need to keep a ref of coagent states and session because of renderAndWait - making sure
172
174
  // the latest state is sent to the API
173
175
  // This is a workaround and needs to be addressed in the future
174
- const coagentStatesRef = useRef<Record<string, CoagentState>>(coagentStates);
175
- coagentStatesRef.current = coagentStates;
176
176
  const agentSessionRef = useRef<AgentSession | null>(agentSession);
177
177
  agentSessionRef.current = agentSession;
178
178
  const threadIdRef = useRef<string | null>(threadId);
@@ -194,305 +194,371 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
194
194
  credentials: copilotConfig.credentials,
195
195
  });
196
196
 
197
- const runChatCompletion = async (previousMessages: Message[]): Promise<Message[]> => {
198
- setIsLoading(true);
199
-
200
- // this message is just a placeholder. It will disappear once the first real message
201
- // is received
202
- let newMessages: Message[] = [
203
- new TextMessage({
204
- content: "",
205
- role: Role.Assistant,
206
- }),
207
- ];
208
-
209
- chatAbortControllerRef.current = new AbortController();
210
-
211
- setMessages([...previousMessages, ...newMessages]);
212
-
213
- const systemMessage = makeSystemMessageCallback();
214
-
215
- const messagesWithContext = [systemMessage, ...(initialMessages || []), ...previousMessages];
216
-
217
- const stream = runtimeClient.asStream(
218
- runtimeClient.generateCopilotResponse({
219
- data: {
220
- frontend: {
221
- actions: actions
222
- .filter(
223
- (action) =>
224
- action.available !== ActionInputAvailability.Disabled || !action.disabled,
225
- )
226
- .map((action) => {
227
- let available: ActionInputAvailability | undefined =
228
- ActionInputAvailability.Enabled;
229
- if (action.disabled) {
230
- available = ActionInputAvailability.Disabled;
231
- } else if (action.available === "disabled") {
232
- available = ActionInputAvailability.Disabled;
233
- } else if (action.available === "remote") {
234
- available = ActionInputAvailability.Remote;
235
- }
236
- return {
237
- name: action.name,
238
- description: action.description || "",
239
- jsonSchema: JSON.stringify(actionParametersToJsonSchema(action.parameters || [])),
240
- available,
241
- };
242
- }),
243
- url: window.location.href,
244
- },
245
- threadId: threadIdRef.current,
246
- runId: runIdRef.current,
247
- messages: convertMessagesToGqlInput(filterAgentStateMessages(messagesWithContext)),
248
- ...(copilotConfig.cloud
249
- ? {
250
- cloud: {
251
- ...(copilotConfig.cloud.guardrails?.input?.restrictToTopic?.enabled
252
- ? {
253
- guardrails: {
254
- inputValidationRules: {
255
- allowList:
256
- copilotConfig.cloud.guardrails.input.restrictToTopic.validTopics,
257
- denyList:
258
- copilotConfig.cloud.guardrails.input.restrictToTopic.invalidTopics,
197
+ const runChatCompletion = useAsyncCallback(
198
+ async (previousMessages: Message[]): Promise<Message[]> => {
199
+ setIsLoading(true);
200
+
201
+ // this message is just a placeholder. It will disappear once the first real message
202
+ // is received
203
+ let newMessages: Message[] = [
204
+ new TextMessage({
205
+ content: "",
206
+ role: Role.Assistant,
207
+ }),
208
+ ];
209
+
210
+ chatAbortControllerRef.current = new AbortController();
211
+
212
+ setMessages([...previousMessages, ...newMessages]);
213
+
214
+ const systemMessage = makeSystemMessageCallback();
215
+
216
+ const messagesWithContext = [systemMessage, ...(initialMessages || []), ...previousMessages];
217
+
218
+ const isAgentRun = agentSessionRef.current !== null;
219
+
220
+ const stream = runtimeClient.asStream(
221
+ runtimeClient.generateCopilotResponse({
222
+ data: {
223
+ frontend: {
224
+ actions: processActionsForRuntimeRequest(actions),
225
+ url: window.location.href,
226
+ },
227
+ threadId: threadIdRef.current,
228
+ runId: runIdRef.current,
229
+ messages: convertMessagesToGqlInput(filterAgentStateMessages(messagesWithContext)),
230
+ ...(copilotConfig.cloud
231
+ ? {
232
+ cloud: {
233
+ ...(copilotConfig.cloud.guardrails?.input?.restrictToTopic?.enabled
234
+ ? {
235
+ guardrails: {
236
+ inputValidationRules: {
237
+ allowList:
238
+ copilotConfig.cloud.guardrails.input.restrictToTopic.validTopics,
239
+ denyList:
240
+ copilotConfig.cloud.guardrails.input.restrictToTopic.invalidTopics,
241
+ },
259
242
  },
260
- },
261
- }
262
- : {}),
263
- },
264
- }
265
- : {}),
266
- metadata: {
267
- requestType: CopilotRequestType.Chat,
243
+ }
244
+ : {}),
245
+ },
246
+ }
247
+ : {}),
248
+ metadata: {
249
+ requestType: CopilotRequestType.Chat,
250
+ },
251
+ ...(agentSessionRef.current
252
+ ? {
253
+ agentSession: agentSessionRef.current,
254
+ }
255
+ : {}),
256
+ agentStates: Object.values(coagentStatesRef.current!).map((state) => ({
257
+ agentName: state.name,
258
+ state: JSON.stringify(state.state),
259
+ })),
268
260
  },
269
- ...(agentSessionRef.current
270
- ? {
271
- agentSession: agentSessionRef.current,
272
- }
273
- : {}),
274
- agentStates: Object.values(coagentStatesRef.current).map((state) => ({
275
- agentName: state.name,
276
- state: JSON.stringify(state.state),
277
- })),
278
- },
279
- properties: copilotConfig.properties,
280
- signal: chatAbortControllerRef.current?.signal,
281
- }),
282
- );
283
-
284
- const guardrailsEnabled =
285
- copilotConfig.cloud?.guardrails?.input?.restrictToTopic.enabled || false;
286
-
287
- const reader = stream.getReader();
288
-
289
- let actionResults: { [id: string]: string } = {};
290
- let executedCoAgentStateRenders: string[] = [];
291
- let followUp: FrontendAction["followUp"] = undefined;
292
-
293
- try {
294
- while (true) {
295
- let done, value;
296
-
297
- try {
298
- const readResult = await reader.read();
299
- done = readResult.done;
300
- value = readResult.value;
301
- } catch (readError) {
302
- break;
303
- }
261
+ properties: copilotConfig.properties,
262
+ signal: chatAbortControllerRef.current?.signal,
263
+ }),
264
+ );
265
+
266
+ const guardrailsEnabled =
267
+ copilotConfig.cloud?.guardrails?.input?.restrictToTopic.enabled || false;
268
+
269
+ const reader = stream.getReader();
304
270
 
305
- if (done) {
306
- if (chatAbortControllerRef.current.signal.aborted) {
307
- return newMessages.slice();
271
+ let executedCoAgentStateRenders: string[] = [];
272
+ let followUp: FrontendAction["followUp"] = undefined;
273
+
274
+ let messages: Message[] = [];
275
+ let syncedMessages: Message[] = [];
276
+
277
+ try {
278
+ while (true) {
279
+ let done, value;
280
+
281
+ try {
282
+ const readResult = await reader.read();
283
+ done = readResult.done;
284
+ value = readResult.value;
285
+ } catch (readError) {
286
+ break;
308
287
  }
309
- break;
310
- }
311
288
 
312
- if (!value?.generateCopilotResponse) {
313
- continue;
314
- }
289
+ if (done) {
290
+ if (chatAbortControllerRef.current.signal.aborted) {
291
+ return [];
292
+ }
293
+ break;
294
+ }
315
295
 
316
- threadIdRef.current = value.generateCopilotResponse.threadId || null;
317
- runIdRef.current = value.generateCopilotResponse.runId || null;
296
+ if (!value?.generateCopilotResponse) {
297
+ continue;
298
+ }
318
299
 
319
- setThreadId(threadIdRef.current);
320
- setRunId(runIdRef.current);
300
+ threadIdRef.current = value.generateCopilotResponse.threadId || null;
301
+ runIdRef.current = value.generateCopilotResponse.runId || null;
321
302
 
322
- const messages = convertGqlOutputToMessages(
323
- filterAdjacentAgentStateMessages(value.generateCopilotResponse.messages),
324
- );
303
+ setThreadId(threadIdRef.current);
304
+ setRunId(runIdRef.current);
325
305
 
326
- if (messages.length === 0) {
327
- continue;
328
- }
306
+ messages = convertGqlOutputToMessages(
307
+ filterAdjacentAgentStateMessages(value.generateCopilotResponse.messages),
308
+ );
329
309
 
330
- newMessages = [];
310
+ if (messages.length === 0) {
311
+ continue;
312
+ }
331
313
 
332
- // request failed, display error message
333
- if (
334
- value.generateCopilotResponse.status?.__typename === "FailedResponseStatus" &&
335
- value.generateCopilotResponse.status.reason === "GUARDRAILS_VALIDATION_FAILED"
336
- ) {
337
- newMessages = [
338
- new TextMessage({
339
- role: MessageRole.Assistant,
340
- content: value.generateCopilotResponse.status.details?.guardrailsReason || "",
341
- }),
342
- ];
314
+ newMessages = [];
315
+
316
+ // request failed, display error message and quit
317
+ if (
318
+ value.generateCopilotResponse.status?.__typename === "FailedResponseStatus" &&
319
+ value.generateCopilotResponse.status.reason === "GUARDRAILS_VALIDATION_FAILED"
320
+ ) {
321
+ newMessages = [
322
+ new TextMessage({
323
+ role: MessageRole.Assistant,
324
+ content: value.generateCopilotResponse.status.details?.guardrailsReason || "",
325
+ }),
326
+ ];
327
+ setMessages([...previousMessages, ...newMessages]);
328
+ break;
329
+ }
330
+
331
+ // add messages to the chat
332
+ else {
333
+ newMessages = [...messages];
334
+
335
+ for (const message of messages) {
336
+ // execute onCoAgentStateRender handler
337
+ if (
338
+ message.isAgentStateMessage() &&
339
+ !message.active &&
340
+ !executedCoAgentStateRenders.includes(message.id) &&
341
+ onCoAgentStateRender
342
+ ) {
343
+ // Do not execute a coagent action if guardrails are enabled but the status is not known
344
+ if (guardrailsEnabled && value.generateCopilotResponse.status === undefined) {
345
+ break;
346
+ }
347
+ // execute coagent action
348
+ await onCoAgentStateRender({
349
+ name: message.agentName,
350
+ nodeName: message.nodeName,
351
+ state: message.state,
352
+ });
353
+ executedCoAgentStateRenders.push(message.id);
354
+ }
355
+ }
356
+
357
+ const lastAgentStateMessage = [...messages]
358
+ .reverse()
359
+ .find((message) => message.isAgentStateMessage());
360
+
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
+ }
370
+ setCoagentStatesWithRef((prevAgentStates) => ({
371
+ ...prevAgentStates,
372
+ [lastAgentStateMessage.agentName]: {
373
+ name: lastAgentStateMessage.agentName,
374
+ state: lastAgentStateMessage.state,
375
+ running: lastAgentStateMessage.running,
376
+ active: lastAgentStateMessage.active,
377
+ threadId: lastAgentStateMessage.threadId,
378
+ nodeName: lastAgentStateMessage.nodeName,
379
+ runId: lastAgentStateMessage.runId,
380
+ },
381
+ }));
382
+ if (lastAgentStateMessage.running) {
383
+ setAgentSession({
384
+ threadId: lastAgentStateMessage.threadId,
385
+ agentName: lastAgentStateMessage.agentName,
386
+ nodeName: lastAgentStateMessage.nodeName,
387
+ });
388
+ } else {
389
+ setAgentSession(null);
390
+ }
391
+ }
392
+ }
393
+
394
+ if (newMessages.length > 0) {
395
+ // Update message state
396
+ setMessages([...previousMessages, ...newMessages]);
397
+ }
343
398
  }
399
+ const finalMessages = constructFinalMessages(syncedMessages, previousMessages, newMessages);
400
+
401
+ let didExecuteAction = false;
344
402
 
345
- // add messages to the chat
346
- else {
347
- for (const message of messages) {
348
- newMessages.push(message);
349
- // execute regular action executions
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];
350
409
  if (
351
410
  message.isActionExecutionMessage() &&
352
- message.status.code !== MessageStatusCode.Pending &&
353
- message.scope === "client" &&
354
- onFunctionCall
411
+ message.status.code !== MessageStatusCode.Pending
355
412
  ) {
356
- if (!(message.id in actionResults)) {
357
- // Do not execute a function call if guardrails are enabled but the status is not known
358
- if (guardrailsEnabled && value.generateCopilotResponse.status === undefined) {
359
- break;
360
- }
361
- // execute action
362
- try {
363
- // We update the message state before calling the handler so that the render
364
- // function can be called with `executing` state
365
- setMessages([...previousMessages, ...newMessages]);
366
-
367
- const action = actions.find((action) => action.name === message.name);
368
-
369
- if (action) {
370
- followUp = action.followUp;
371
- }
372
-
373
- const result = await Promise.race([
374
- onFunctionCall({
375
- messages: previousMessages,
376
- name: message.name,
377
- args: message.arguments,
378
- }),
379
- new Promise((_, reject) => chatAbortControllerRef.current?.signal.addEventListener('abort', () => reject(new Error('Operation was aborted'))))
380
- ])
381
- if (chatAbortControllerRef.current.signal.aborted){
382
- actionResults[message.id] = "";
383
- } else {
384
- actionResults[message.id] = result;
385
- }
386
- } catch (e) {
387
- actionResults[message.id] = `Failed to execute action ${message.name}`;
388
- console.error(`Failed to execute action ${message.name}: ${e}`);
389
- }
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}`);
390
451
  }
391
- // add the result message
392
- newMessages.push(
452
+ didExecuteAction = true;
453
+ const messageIndex = finalMessages.findIndex((msg) => msg.id === message.id);
454
+ finalMessages.splice(
455
+ messageIndex + 1,
456
+ 0,
393
457
  new ResultMessage({
394
- result: ResultMessage.encodeResult(actionResults[message.id]),
458
+ id: "result-" + message.id,
459
+ result: ResultMessage.encodeResult(result),
395
460
  actionExecutionId: message.id,
396
461
  actionName: message.name,
397
462
  }),
398
463
  );
399
464
  }
400
- // execute coagent actions
401
- if (
402
- message.isAgentStateMessage() &&
403
- !message.active &&
404
- !executedCoAgentStateRenders.includes(message.id) &&
405
- onCoAgentStateRender
406
- ) {
407
- // Do not execute a coagent action if guardrails are enabled but the status is not known
408
- if (guardrailsEnabled && value.generateCopilotResponse.status === undefined) {
409
- break;
410
- }
411
- // execute coagent action
412
- await onCoAgentStateRender({
413
- name: message.agentName,
414
- nodeName: message.nodeName,
415
- state: message.state,
416
- });
417
- executedCoAgentStateRenders.push(message.id);
418
- }
419
465
  }
420
466
 
421
- const lastAgentStateMessage = [...messages]
422
- .reverse()
423
- .find((message) => message.isAgentStateMessage());
424
-
425
- if (lastAgentStateMessage) {
426
- setCoagentStates((prevAgentStates) => ({
427
- ...prevAgentStates,
428
- [lastAgentStateMessage.agentName]: {
429
- name: lastAgentStateMessage.agentName,
430
- state: lastAgentStateMessage.state,
431
- running: lastAgentStateMessage.running,
432
- active: lastAgentStateMessage.active,
433
- threadId: lastAgentStateMessage.threadId,
434
- nodeName: lastAgentStateMessage.nodeName,
435
- runId: lastAgentStateMessage.runId,
436
- },
437
- }));
438
- if (lastAgentStateMessage.running) {
439
- setAgentSession({
440
- threadId: lastAgentStateMessage.threadId,
441
- agentName: lastAgentStateMessage.agentName,
442
- nodeName: lastAgentStateMessage.nodeName,
443
- });
444
- } else {
445
- setAgentSession(null);
446
- }
447
- }
467
+ setMessages(finalMessages);
448
468
  }
449
469
 
450
- if (newMessages.length > 0) {
451
- // Update message state
452
- setMessages([...previousMessages, ...newMessages]);
470
+ if (
471
+ // if followUp is not explicitly false
472
+ followUp !== false &&
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
481
+ ) {
482
+ // run the completion again and return the result
483
+
484
+ // wait for next tick to make sure all the react state updates
485
+ // - tried using react-dom's flushSync, but it did not work
486
+ await new Promise((resolve) => setTimeout(resolve, 10));
487
+
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));
514
+ } else {
515
+ return newMessages.slice();
453
516
  }
517
+ } finally {
518
+ setIsLoading(false);
454
519
  }
455
-
456
- if (
457
- // if followUp is not explicitly false
458
- followUp !== false &&
459
- // if we have client side results
460
- (Object.values(actionResults).length ||
461
- // or the last message we received is a result
462
- (newMessages.length && newMessages[newMessages.length - 1].isResultMessage()))
463
- ) {
464
- // run the completion again and return the result
465
-
466
- // wait for next tick to make sure all the react state updates
467
- // - tried using react-dom's flushSync, but it did not work
468
- await new Promise((resolve) => setTimeout(resolve, 10));
469
-
470
- return await runChatCompletionRef.current!([...previousMessages, ...newMessages]);
471
- } else {
472
- return newMessages.slice();
473
- }
474
- } finally {
475
- setIsLoading(false);
476
- }
477
- };
520
+ },
521
+ [
522
+ messages,
523
+ setMessages,
524
+ makeSystemMessageCallback,
525
+ copilotConfig,
526
+ setIsLoading,
527
+ initialMessages,
528
+ isLoading,
529
+ actions,
530
+ onFunctionCall,
531
+ onCoAgentStateRender,
532
+ setCoagentStatesWithRef,
533
+ coagentStatesRef,
534
+ agentSession,
535
+ setAgentSession,
536
+ ],
537
+ );
478
538
 
479
539
  runChatCompletionRef.current = runChatCompletion;
480
540
 
481
- const runChatCompletionAndHandleFunctionCall = async (messages: Message[]): Promise<void> => {
482
- await runChatCompletionRef.current!(messages);
483
- };
541
+ const runChatCompletionAndHandleFunctionCall = useAsyncCallback(
542
+ async (messages: Message[]): Promise<void> => {
543
+ await runChatCompletionRef.current!(messages);
544
+ },
545
+ [messages],
546
+ );
547
+
548
+ const append = useAsyncCallback(
549
+ async (message: Message): Promise<void> => {
550
+ if (isLoading) {
551
+ return;
552
+ }
484
553
 
485
- const append = async (message: Message): Promise<void> => {
486
- if (isLoading) {
487
- return;
488
- }
554
+ const newMessages = [...messages, message];
555
+ setMessages(newMessages);
556
+ return runChatCompletionAndHandleFunctionCall(newMessages);
557
+ },
558
+ [isLoading, messages, setMessages, runChatCompletionAndHandleFunctionCall],
559
+ );
489
560
 
490
- const newMessages = [...messages, message];
491
- setMessages(newMessages);
492
- return runChatCompletionAndHandleFunctionCall(newMessages);
493
- };
494
-
495
- const reload = async (): Promise<void> => {
561
+ const reload = useAsyncCallback(async (): Promise<void> => {
496
562
  if (isLoading || messages.length === 0) {
497
563
  return;
498
564
  }
@@ -506,7 +572,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
506
572
  setMessages(newMessages);
507
573
 
508
574
  return runChatCompletionAndHandleFunctionCall(newMessages);
509
- };
575
+ }, [isLoading, messages, setMessages, runChatCompletionAndHandleFunctionCall]);
510
576
 
511
577
  const stop = (): void => {
512
578
  chatAbortControllerRef.current?.abort("Stop was called");
@@ -516,5 +582,35 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
516
582
  append,
517
583
  reload,
518
584
  stop,
585
+ runChatCompletion: () => runChatCompletionRef.current!(messages),
519
586
  };
520
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
+ }