@copilotkit/react-core 1.9.2-next.8 → 1.9.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +193 -0
- package/dist/{chunk-ERXWDCY6.mjs → chunk-36MGCCPZ.mjs} +2 -2
- package/dist/{chunk-CCESTGAM.mjs → chunk-3OQM3NEK.mjs} +2 -2
- package/dist/{chunk-7G6RR4HE.mjs → chunk-3Q4F7RF2.mjs} +2 -2
- package/dist/chunk-57K2ZJ5F.mjs +348 -0
- package/dist/chunk-57K2ZJ5F.mjs.map +1 -0
- package/dist/{chunk-UBNRUXEK.mjs → chunk-5BSUSFHM.mjs} +2 -2
- package/dist/{chunk-RN3ZRHI7.mjs → chunk-AD7DWJNW.mjs} +66 -25
- package/dist/chunk-AD7DWJNW.mjs.map +1 -0
- package/dist/{chunk-JPMIAGI6.mjs → chunk-BVK7PLK6.mjs} +2 -2
- package/dist/{chunk-VJCHRQ7Q.mjs → chunk-DGON3GZX.mjs} +39 -6
- package/dist/chunk-DGON3GZX.mjs.map +1 -0
- package/dist/{chunk-XFOTNHYA.mjs → chunk-DKZTPL66.mjs} +2 -2
- package/dist/{chunk-XFOTNHYA.mjs.map → chunk-DKZTPL66.mjs.map} +1 -1
- package/dist/{chunk-S4BOATBG.mjs → chunk-FN3UA2ZE.mjs} +3 -3
- package/dist/{chunk-ISYBUDL4.mjs → chunk-JWAXDYOW.mjs} +11 -12
- package/dist/chunk-JWAXDYOW.mjs.map +1 -0
- package/dist/{chunk-T4ZKC4X4.mjs → chunk-KIXKBJUV.mjs} +3 -3
- package/dist/{chunk-I4JPQECN.mjs → chunk-LFAZTKBK.mjs} +5 -5
- package/dist/{chunk-JHIZ5HAI.mjs → chunk-NJA5ZLAZ.mjs} +29 -10
- package/dist/chunk-NJA5ZLAZ.mjs.map +1 -0
- package/dist/{chunk-ZHEEHGLS.mjs → chunk-QGT4JO7R.mjs} +35 -6
- package/dist/chunk-QGT4JO7R.mjs.map +1 -0
- package/dist/{chunk-JXF732XG.mjs → chunk-S5QUEHJC.mjs} +195 -77
- package/dist/chunk-S5QUEHJC.mjs.map +1 -0
- package/dist/{chunk-QQZLIEXK.mjs → chunk-SJJNFYGQ.mjs} +3 -3
- package/dist/{chunk-CMQV4XNY.mjs → chunk-VDADWRS3.mjs} +2 -2
- package/dist/chunk-YAF2LATQ.mjs +310 -0
- package/dist/chunk-YAF2LATQ.mjs.map +1 -0
- package/dist/{chunk-VF6UPRKM.mjs → chunk-ZGMZ5WJI.mjs} +4 -4
- package/dist/components/copilot-provider/copilot-messages.js +37 -4
- package/dist/components/copilot-provider/copilot-messages.js.map +1 -1
- package/dist/components/copilot-provider/copilot-messages.mjs +3 -3
- package/dist/components/copilot-provider/copilotkit-props.d.ts +14 -9
- package/dist/components/copilot-provider/copilotkit-props.js.map +1 -1
- package/dist/components/copilot-provider/copilotkit.d.ts +1 -1
- package/dist/components/copilot-provider/copilotkit.js +354 -258
- package/dist/components/copilot-provider/copilotkit.js.map +1 -1
- package/dist/components/copilot-provider/copilotkit.mjs +10 -10
- package/dist/components/copilot-provider/index.d.ts +1 -1
- package/dist/components/copilot-provider/index.js +354 -258
- package/dist/components/copilot-provider/index.js.map +1 -1
- package/dist/components/copilot-provider/index.mjs +10 -10
- package/dist/components/error-boundary/error-boundary.js +135 -146
- package/dist/components/error-boundary/error-boundary.js.map +1 -1
- package/dist/components/error-boundary/error-boundary.mjs +4 -4
- package/dist/components/error-boundary/error-utils.js.map +1 -1
- package/dist/components/error-boundary/error-utils.mjs +2 -2
- package/dist/components/index.d.ts +1 -1
- package/dist/components/index.js +354 -258
- package/dist/components/index.js.map +1 -1
- package/dist/components/index.mjs +10 -10
- package/dist/components/toast/toast-provider.js +118 -85
- package/dist/components/toast/toast-provider.js.map +1 -1
- package/dist/components/toast/toast-provider.mjs +1 -1
- package/dist/components/usage-banner.js +135 -146
- package/dist/components/usage-banner.js.map +1 -1
- package/dist/components/usage-banner.mjs +1 -1
- package/dist/context/copilot-context.d.ts +1 -1
- package/dist/context/copilot-context.js +1 -1
- package/dist/context/copilot-context.js.map +1 -1
- package/dist/context/copilot-context.mjs +1 -1
- package/dist/context/index.d.ts +1 -1
- package/dist/context/index.js +1 -1
- package/dist/context/index.js.map +1 -1
- package/dist/context/index.mjs +1 -1
- package/dist/{copilot-context-3da805ab.d.ts → copilot-context-3ab4fdf5.d.ts} +3 -3
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +249 -88
- package/dist/hooks/index.js.map +1 -1
- package/dist/hooks/index.mjs +32 -32
- package/dist/hooks/use-chat.d.ts +1 -1
- package/dist/hooks/use-chat.js +287 -174
- package/dist/hooks/use-chat.js.map +1 -1
- package/dist/hooks/use-chat.mjs +5 -5
- package/dist/hooks/use-coagent-state-render.js +1 -1
- package/dist/hooks/use-coagent-state-render.js.map +1 -1
- package/dist/hooks/use-coagent-state-render.mjs +3 -3
- package/dist/hooks/use-coagent.d.ts +1 -1
- package/dist/hooks/use-coagent.js +224 -82
- package/dist/hooks/use-coagent.js.map +1 -1
- package/dist/hooks/use-coagent.mjs +14 -14
- package/dist/hooks/use-copilot-action.js +26 -7
- package/dist/hooks/use-copilot-action.js.map +1 -1
- package/dist/hooks/use-copilot-action.mjs +4 -4
- package/dist/hooks/use-copilot-additional-instructions.js +1 -1
- package/dist/hooks/use-copilot-additional-instructions.js.map +1 -1
- package/dist/hooks/use-copilot-additional-instructions.mjs +2 -2
- package/dist/hooks/use-copilot-authenticated-action.js +26 -7
- package/dist/hooks/use-copilot-authenticated-action.js.map +1 -1
- package/dist/hooks/use-copilot-authenticated-action.mjs +5 -5
- package/dist/hooks/use-copilot-chat.d.ts +1 -1
- package/dist/hooks/use-copilot-chat.js +195 -82
- package/dist/hooks/use-copilot-chat.js.map +1 -1
- package/dist/hooks/use-copilot-chat.mjs +13 -13
- package/dist/hooks/use-copilot-readable.js +1 -1
- package/dist/hooks/use-copilot-readable.js.map +1 -1
- package/dist/hooks/use-copilot-readable.mjs +2 -2
- package/dist/hooks/use-copilot-runtime-client.d.ts +2 -2
- package/dist/hooks/use-copilot-runtime-client.js +8 -8
- package/dist/hooks/use-copilot-runtime-client.js.map +1 -1
- package/dist/hooks/use-copilot-runtime-client.mjs +2 -2
- package/dist/hooks/use-langgraph-interrupt-render.js +1 -1
- package/dist/hooks/use-langgraph-interrupt-render.js.map +1 -1
- package/dist/hooks/use-langgraph-interrupt-render.mjs +2 -2
- package/dist/hooks/use-langgraph-interrupt.d.ts +1 -1
- package/dist/hooks/use-langgraph-interrupt.js +195 -82
- package/dist/hooks/use-langgraph-interrupt.js.map +1 -1
- package/dist/hooks/use-langgraph-interrupt.mjs +14 -14
- package/dist/hooks/use-make-copilot-document-readable.js +1 -1
- package/dist/hooks/use-make-copilot-document-readable.js.map +1 -1
- package/dist/hooks/use-make-copilot-document-readable.mjs +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +593 -336
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +33 -33
- package/dist/lib/copilot-task.d.ts +1 -1
- package/dist/lib/copilot-task.js.map +1 -1
- package/dist/lib/copilot-task.mjs +11 -11
- package/dist/lib/index.d.ts +1 -1
- package/dist/lib/index.js.map +1 -1
- package/dist/lib/index.mjs +11 -11
- package/dist/setupTests.d.ts +2 -0
- package/dist/setupTests.js +26 -0
- package/dist/setupTests.js.map +1 -0
- package/dist/setupTests.mjs +24 -0
- package/dist/setupTests.mjs.map +1 -0
- package/dist/types/interrupt-action.d.ts +1 -1
- package/dist/utils/extract.d.ts +1 -1
- package/dist/utils/extract.js.map +1 -1
- package/dist/utils/extract.mjs +10 -10
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.js.map +1 -1
- package/dist/utils/index.mjs +10 -10
- package/jest.config.js +4 -0
- package/package.json +6 -3
- package/src/components/copilot-provider/__tests__/{copilotkit-trace.test.tsx → copilotkit-error.test.tsx} +17 -17
- package/src/components/copilot-provider/copilot-messages.tsx +43 -4
- package/src/components/copilot-provider/copilotkit-props.tsx +13 -8
- package/src/components/copilot-provider/copilotkit.tsx +61 -19
- package/src/components/toast/toast-provider.tsx +49 -24
- package/src/components/usage-banner.tsx +144 -147
- package/src/context/copilot-context.tsx +4 -4
- package/src/hooks/__tests__/use-coagent-config.test.ts +284 -0
- package/src/hooks/use-chat.ts +249 -61
- package/src/hooks/use-coagent.ts +41 -0
- package/src/hooks/use-copilot-action.ts +51 -9
- package/src/hooks/use-copilot-runtime-client.ts +12 -50
- package/src/setupTests.ts +26 -0
- package/tsconfig.json +5 -2
- package/dist/chunk-HD2GE3DK.mjs +0 -359
- package/dist/chunk-HD2GE3DK.mjs.map +0 -1
- package/dist/chunk-ISYBUDL4.mjs.map +0 -1
- package/dist/chunk-JHIZ5HAI.mjs.map +0 -1
- package/dist/chunk-JXF732XG.mjs.map +0 -1
- package/dist/chunk-RN3ZRHI7.mjs.map +0 -1
- package/dist/chunk-VJCHRQ7Q.mjs.map +0 -1
- package/dist/chunk-VRXANACV.mjs +0 -277
- package/dist/chunk-VRXANACV.mjs.map +0 -1
- package/dist/chunk-ZHEEHGLS.mjs.map +0 -1
- /package/dist/{chunk-ERXWDCY6.mjs.map → chunk-36MGCCPZ.mjs.map} +0 -0
- /package/dist/{chunk-CCESTGAM.mjs.map → chunk-3OQM3NEK.mjs.map} +0 -0
- /package/dist/{chunk-7G6RR4HE.mjs.map → chunk-3Q4F7RF2.mjs.map} +0 -0
- /package/dist/{chunk-UBNRUXEK.mjs.map → chunk-5BSUSFHM.mjs.map} +0 -0
- /package/dist/{chunk-JPMIAGI6.mjs.map → chunk-BVK7PLK6.mjs.map} +0 -0
- /package/dist/{chunk-S4BOATBG.mjs.map → chunk-FN3UA2ZE.mjs.map} +0 -0
- /package/dist/{chunk-T4ZKC4X4.mjs.map → chunk-KIXKBJUV.mjs.map} +0 -0
- /package/dist/{chunk-I4JPQECN.mjs.map → chunk-LFAZTKBK.mjs.map} +0 -0
- /package/dist/{chunk-QQZLIEXK.mjs.map → chunk-SJJNFYGQ.mjs.map} +0 -0
- /package/dist/{chunk-CMQV4XNY.mjs.map → chunk-VDADWRS3.mjs.map} +0 -0
- /package/dist/{chunk-VF6UPRKM.mjs.map → chunk-ZGMZ5WJI.mjs.map} +0 -0
package/src/hooks/use-chat.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useRef } from "react";
|
|
2
|
+
import { flushSync } from "react-dom";
|
|
2
3
|
import {
|
|
3
4
|
FunctionCallHandler,
|
|
4
5
|
COPILOT_CLOUD_PUBLIC_API_KEY_HEADER,
|
|
5
6
|
CoAgentStateRenderHandler,
|
|
6
7
|
randomId,
|
|
7
8
|
parseJson,
|
|
9
|
+
CopilotKitError,
|
|
10
|
+
CopilotKitErrorCode,
|
|
8
11
|
} from "@copilotkit/shared";
|
|
9
12
|
import {
|
|
10
13
|
Message,
|
|
@@ -35,10 +38,10 @@ import {
|
|
|
35
38
|
import { CopilotApiConfig } from "../context";
|
|
36
39
|
import { FrontendAction, processActionsForRuntimeRequest } from "../types/frontend-action";
|
|
37
40
|
import { CoagentState } from "../types/coagent-state";
|
|
38
|
-
import { AgentSession } from "../context/copilot-context";
|
|
41
|
+
import { AgentSession, useCopilotContext } from "../context/copilot-context";
|
|
39
42
|
import { useCopilotRuntimeClient } from "./use-copilot-runtime-client";
|
|
40
|
-
import { useCopilotContext } from "../context/copilot-context";
|
|
41
43
|
import { useAsyncCallback, useErrorToast } from "../components/error-boundary/error-utils";
|
|
44
|
+
import { useToast } from "../components/toast/toast-provider";
|
|
42
45
|
import {
|
|
43
46
|
LangGraphInterruptAction,
|
|
44
47
|
LangGraphInterruptActionSetter,
|
|
@@ -218,6 +221,41 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
218
221
|
} = options;
|
|
219
222
|
const runChatCompletionRef = useRef<(previousMessages: Message[]) => Promise<Message[]>>();
|
|
220
223
|
const addErrorToast = useErrorToast();
|
|
224
|
+
const { setBannerError } = useToast();
|
|
225
|
+
|
|
226
|
+
// Get onError from context since it's not part of copilotConfig
|
|
227
|
+
const { onError } = useCopilotContext();
|
|
228
|
+
|
|
229
|
+
// Add tracing functionality to use-chat
|
|
230
|
+
const traceUIError = async (error: CopilotKitError, originalError?: any) => {
|
|
231
|
+
// Just check if onError and publicApiKey are defined
|
|
232
|
+
if (!onError || !copilotConfig?.publicApiKey) return;
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
const traceEvent = {
|
|
236
|
+
type: "error" as const,
|
|
237
|
+
timestamp: Date.now(),
|
|
238
|
+
context: {
|
|
239
|
+
source: "ui" as const,
|
|
240
|
+
request: {
|
|
241
|
+
operation: "useChatCompletion",
|
|
242
|
+
url: copilotConfig.chatApiEndpoint,
|
|
243
|
+
startTime: Date.now(),
|
|
244
|
+
},
|
|
245
|
+
technical: {
|
|
246
|
+
environment: "browser",
|
|
247
|
+
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
|
|
248
|
+
stackTrace: originalError instanceof Error ? originalError.stack : undefined,
|
|
249
|
+
},
|
|
250
|
+
},
|
|
251
|
+
error,
|
|
252
|
+
};
|
|
253
|
+
|
|
254
|
+
await onError(traceEvent);
|
|
255
|
+
} catch (traceError) {
|
|
256
|
+
console.error("Error in use-chat onError handler:", traceError);
|
|
257
|
+
}
|
|
258
|
+
};
|
|
221
259
|
// We need to keep a ref of coagent states and session because of renderAndWait - making sure
|
|
222
260
|
// the latest state is sent to the API
|
|
223
261
|
// This is a workaround and needs to be addressed in the future
|
|
@@ -453,29 +491,91 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
453
491
|
filterAdjacentAgentStateMessages(rawMessagesResponse),
|
|
454
492
|
);
|
|
455
493
|
|
|
456
|
-
if (messages.length === 0) {
|
|
457
|
-
continue;
|
|
458
|
-
}
|
|
459
|
-
|
|
460
494
|
newMessages = [];
|
|
461
495
|
|
|
496
|
+
// Handle error statuses BEFORE checking if there are messages
|
|
497
|
+
// (errors can come in chunks with no messages)
|
|
498
|
+
|
|
462
499
|
// request failed, display error message and quit
|
|
463
500
|
if (
|
|
464
501
|
value.generateCopilotResponse.status?.__typename === "FailedResponseStatus" &&
|
|
465
502
|
value.generateCopilotResponse.status.reason === "GUARDRAILS_VALIDATION_FAILED"
|
|
466
503
|
) {
|
|
504
|
+
const guardrailsReason =
|
|
505
|
+
value.generateCopilotResponse.status.details?.guardrailsReason || "";
|
|
506
|
+
|
|
467
507
|
newMessages = [
|
|
468
508
|
new TextMessage({
|
|
469
509
|
role: MessageRole.Assistant,
|
|
470
|
-
content:
|
|
510
|
+
content: guardrailsReason,
|
|
471
511
|
}),
|
|
472
512
|
];
|
|
513
|
+
|
|
514
|
+
// Trace guardrails validation failure
|
|
515
|
+
const guardrailsError = new CopilotKitError({
|
|
516
|
+
message: `Guardrails validation failed: ${guardrailsReason}`,
|
|
517
|
+
code: CopilotKitErrorCode.MISUSE,
|
|
518
|
+
});
|
|
519
|
+
await traceUIError(guardrailsError, {
|
|
520
|
+
statusReason: value.generateCopilotResponse.status.reason,
|
|
521
|
+
statusDetails: value.generateCopilotResponse.status.details,
|
|
522
|
+
});
|
|
523
|
+
|
|
473
524
|
setMessages([...previousMessages, ...newMessages]);
|
|
474
525
|
break;
|
|
475
526
|
}
|
|
476
527
|
|
|
528
|
+
// Handle UNKNOWN_ERROR failures (like authentication errors) by routing to banner error system
|
|
529
|
+
if (
|
|
530
|
+
value.generateCopilotResponse.status?.__typename === "FailedResponseStatus" &&
|
|
531
|
+
value.generateCopilotResponse.status.reason === "UNKNOWN_ERROR"
|
|
532
|
+
) {
|
|
533
|
+
const errorMessage =
|
|
534
|
+
value.generateCopilotResponse.status.details?.description ||
|
|
535
|
+
"An unknown error occurred";
|
|
536
|
+
|
|
537
|
+
// Try to extract original error information from the response details
|
|
538
|
+
const statusDetails = value.generateCopilotResponse.status.details;
|
|
539
|
+
const originalError = statusDetails?.originalError || statusDetails?.error;
|
|
540
|
+
|
|
541
|
+
// Extract structured error information if available (prioritize top-level over extensions)
|
|
542
|
+
const originalCode = originalError?.code || originalError?.extensions?.code;
|
|
543
|
+
const originalSeverity = originalError?.severity || originalError?.extensions?.severity;
|
|
544
|
+
const originalVisibility =
|
|
545
|
+
originalError?.visibility || originalError?.extensions?.visibility;
|
|
546
|
+
|
|
547
|
+
// Use the original error code if available, otherwise default to NETWORK_ERROR
|
|
548
|
+
let errorCode = CopilotKitErrorCode.NETWORK_ERROR;
|
|
549
|
+
if (originalCode && Object.values(CopilotKitErrorCode).includes(originalCode)) {
|
|
550
|
+
errorCode = originalCode;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
// Create a structured CopilotKitError preserving original error information
|
|
554
|
+
const structuredError = new CopilotKitError({
|
|
555
|
+
message: errorMessage,
|
|
556
|
+
code: errorCode,
|
|
557
|
+
severity: originalSeverity,
|
|
558
|
+
visibility: originalVisibility,
|
|
559
|
+
});
|
|
560
|
+
|
|
561
|
+
// Display the error in the banner
|
|
562
|
+
setBannerError(structuredError);
|
|
563
|
+
|
|
564
|
+
// Trace the error for debugging/observability
|
|
565
|
+
await traceUIError(structuredError, {
|
|
566
|
+
statusReason: value.generateCopilotResponse.status.reason,
|
|
567
|
+
statusDetails: value.generateCopilotResponse.status.details,
|
|
568
|
+
originalErrorCode: originalCode,
|
|
569
|
+
preservedStructure: !!originalCode,
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
// Stop processing and break from the loop
|
|
573
|
+
setIsLoading(false);
|
|
574
|
+
break;
|
|
575
|
+
}
|
|
576
|
+
|
|
477
577
|
// add messages to the chat
|
|
478
|
-
else {
|
|
578
|
+
else if (messages.length > 0) {
|
|
479
579
|
newMessages = [...messages];
|
|
480
580
|
|
|
481
581
|
for (const message of messages) {
|
|
@@ -523,6 +623,8 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
523
623
|
threadId: lastAgentStateMessage.threadId,
|
|
524
624
|
nodeName: lastAgentStateMessage.nodeName,
|
|
525
625
|
runId: lastAgentStateMessage.runId,
|
|
626
|
+
// Preserve existing config from previous state
|
|
627
|
+
config: prevAgentStates[lastAgentStateMessage.agentName]?.config,
|
|
526
628
|
},
|
|
527
629
|
}));
|
|
528
630
|
if (lastAgentStateMessage.running) {
|
|
@@ -558,6 +660,55 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
558
660
|
|
|
559
661
|
let didExecuteAction = false;
|
|
560
662
|
|
|
663
|
+
// ----- Helper function to execute an action and manage its lifecycle -----
|
|
664
|
+
const executeActionFromMessage = async (
|
|
665
|
+
currentAction: FrontendAction<any>,
|
|
666
|
+
actionMessage: ActionExecutionMessage,
|
|
667
|
+
) => {
|
|
668
|
+
const isInterruptAction = interruptMessages.find((m) => m.id === actionMessage.id);
|
|
669
|
+
// Determine follow-up behavior: use action's specific setting if defined, otherwise default based on interrupt status.
|
|
670
|
+
followUp = currentAction?.followUp ?? !isInterruptAction;
|
|
671
|
+
|
|
672
|
+
// Call _setActivatingMessageId before executing the action for HITL correlation
|
|
673
|
+
if ((currentAction as any)?._setActivatingMessageId) {
|
|
674
|
+
(currentAction as any)._setActivatingMessageId(actionMessage.id);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const resultMessage = await executeAction({
|
|
678
|
+
onFunctionCall: onFunctionCall!,
|
|
679
|
+
message: actionMessage,
|
|
680
|
+
chatAbortControllerRef,
|
|
681
|
+
onError: (error: Error) => {
|
|
682
|
+
addErrorToast([error]);
|
|
683
|
+
// console.error is kept here as it's a genuine error in action execution
|
|
684
|
+
console.error(`Failed to execute action ${actionMessage.name}: ${error}`);
|
|
685
|
+
},
|
|
686
|
+
setMessages,
|
|
687
|
+
getFinalMessages: () => finalMessages,
|
|
688
|
+
isRenderAndWait: (currentAction as any)?._isRenderAndWait || false,
|
|
689
|
+
});
|
|
690
|
+
didExecuteAction = true;
|
|
691
|
+
const messageIndex = finalMessages.findIndex((msg) => msg.id === actionMessage.id);
|
|
692
|
+
finalMessages.splice(messageIndex + 1, 0, resultMessage);
|
|
693
|
+
|
|
694
|
+
// If the executed action was a renderAndWaitForResponse type, update messages immediately
|
|
695
|
+
// to reflect its completion in the UI, making it interactive promptly.
|
|
696
|
+
if ((currentAction as any)?._isRenderAndWait) {
|
|
697
|
+
const messagesForImmediateUpdate = [...finalMessages];
|
|
698
|
+
flushSync(() => {
|
|
699
|
+
setMessages(messagesForImmediateUpdate);
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Clear _setActivatingMessageId after the action is done
|
|
704
|
+
if ((currentAction as any)?._setActivatingMessageId) {
|
|
705
|
+
(currentAction as any)._setActivatingMessageId(null);
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
return resultMessage;
|
|
709
|
+
};
|
|
710
|
+
// ----------------------------------------------------------------------
|
|
711
|
+
|
|
561
712
|
// execute regular action executions that are specific to the frontend (last actions)
|
|
562
713
|
if (onFunctionCall) {
|
|
563
714
|
// Find consecutive action execution messages at the end
|
|
@@ -587,44 +738,38 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
587
738
|
? getPairedFeAction(actions, message)
|
|
588
739
|
: null;
|
|
589
740
|
|
|
590
|
-
const executeActionFromMessage = async (
|
|
591
|
-
action: FrontendAction<any>,
|
|
592
|
-
message: ActionExecutionMessage,
|
|
593
|
-
) => {
|
|
594
|
-
const isInterruptAction = interruptMessages.find((m) => m.id === message.id);
|
|
595
|
-
followUp = action?.followUp ?? !isInterruptAction;
|
|
596
|
-
const resultMessage = await executeAction({
|
|
597
|
-
onFunctionCall,
|
|
598
|
-
previousMessages,
|
|
599
|
-
message,
|
|
600
|
-
chatAbortControllerRef,
|
|
601
|
-
onError: (error: Error) => {
|
|
602
|
-
addErrorToast([error]);
|
|
603
|
-
console.error(`Failed to execute action ${message.name}: ${error}`);
|
|
604
|
-
},
|
|
605
|
-
});
|
|
606
|
-
didExecuteAction = true;
|
|
607
|
-
const messageIndex = finalMessages.findIndex((msg) => msg.id === message.id);
|
|
608
|
-
finalMessages.splice(messageIndex + 1, 0, resultMessage);
|
|
609
|
-
|
|
610
|
-
return resultMessage;
|
|
611
|
-
};
|
|
612
|
-
|
|
613
741
|
// execution message which has an action registered with the hook (remote availability):
|
|
614
742
|
// execute that action first, and then the "paired FE action"
|
|
615
743
|
if (action && message.isActionExecutionMessage()) {
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
744
|
+
// For HITL actions, check if they've already been processed to avoid redundant handler calls.
|
|
745
|
+
const isRenderAndWaitAction = (action as any)?._isRenderAndWait || false;
|
|
746
|
+
const alreadyProcessed =
|
|
747
|
+
isRenderAndWaitAction &&
|
|
748
|
+
finalMessages.some(
|
|
749
|
+
(fm) => fm.isResultMessage() && fm.actionExecutionId === message.id,
|
|
750
|
+
);
|
|
751
|
+
|
|
752
|
+
if (alreadyProcessed) {
|
|
753
|
+
// Skip re-execution if already processed
|
|
754
|
+
} else {
|
|
755
|
+
// Call the single, externally defined executeActionFromMessage
|
|
756
|
+
const resultMessage = await executeActionFromMessage(
|
|
757
|
+
action,
|
|
758
|
+
message as ActionExecutionMessage,
|
|
759
|
+
);
|
|
760
|
+
const pairedFeAction = getPairedFeAction(actions, resultMessage);
|
|
761
|
+
|
|
762
|
+
if (pairedFeAction) {
|
|
763
|
+
const newExecutionMessage = new ActionExecutionMessage({
|
|
764
|
+
name: pairedFeAction.name,
|
|
765
|
+
arguments: parseJson(resultMessage.result, resultMessage.result),
|
|
766
|
+
status: message.status,
|
|
767
|
+
createdAt: message.createdAt,
|
|
768
|
+
parentMessageId: message.parentMessageId,
|
|
769
|
+
});
|
|
770
|
+
// Call the single, externally defined executeActionFromMessage
|
|
771
|
+
await executeActionFromMessage(pairedFeAction, newExecutionMessage);
|
|
772
|
+
}
|
|
628
773
|
}
|
|
629
774
|
} else if (message.isResultMessage() && currentResultMessagePairedFeAction) {
|
|
630
775
|
// Actions which are set up in runtime actions array: Grab the result, executed paired FE action with it as args.
|
|
@@ -635,6 +780,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
635
780
|
createdAt: message.createdAt,
|
|
636
781
|
});
|
|
637
782
|
finalMessages.push(newExecutionMessage);
|
|
783
|
+
// Call the single, externally defined executeActionFromMessage
|
|
638
784
|
await executeActionFromMessage(
|
|
639
785
|
currentResultMessagePairedFeAction,
|
|
640
786
|
newExecutionMessage,
|
|
@@ -645,10 +791,10 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
645
791
|
setMessages(finalMessages);
|
|
646
792
|
}
|
|
647
793
|
|
|
794
|
+
// Conditionally run chat completion again if followUp is not explicitly false
|
|
795
|
+
// and an action was executed or the last message is a server-side result (for non-agent runs).
|
|
648
796
|
if (
|
|
649
|
-
// if followUp is not explicitly false
|
|
650
797
|
followUp !== false &&
|
|
651
|
-
// and we executed an action
|
|
652
798
|
(didExecuteAction ||
|
|
653
799
|
// the last message is a server side result
|
|
654
800
|
(!isAgentRun &&
|
|
@@ -792,25 +938,47 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
792
938
|
);
|
|
793
939
|
|
|
794
940
|
const reload = useAsyncCallback(
|
|
795
|
-
async (
|
|
941
|
+
async (reloadMessageId: string): Promise<void> => {
|
|
796
942
|
if (isLoading || messages.length === 0) {
|
|
797
943
|
return;
|
|
798
944
|
}
|
|
799
945
|
|
|
800
|
-
const
|
|
801
|
-
if (
|
|
802
|
-
console.warn(`Message with id ${
|
|
946
|
+
const reloadMessageIndex = messages.findIndex((msg) => msg.id === reloadMessageId);
|
|
947
|
+
if (reloadMessageIndex === -1) {
|
|
948
|
+
console.warn(`Message with id ${reloadMessageId} not found`);
|
|
803
949
|
return;
|
|
804
950
|
}
|
|
805
951
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
952
|
+
// @ts-expect-error -- message has role
|
|
953
|
+
const reloadMessageRole = messages[reloadMessageIndex].role;
|
|
954
|
+
if (reloadMessageRole !== MessageRole.Assistant) {
|
|
955
|
+
console.warn(`Regenerate cannot be performed on ${reloadMessageRole} role`);
|
|
956
|
+
return;
|
|
809
957
|
}
|
|
810
958
|
|
|
811
|
-
|
|
959
|
+
let historyCutoff: Message[] = [];
|
|
960
|
+
if (messages.length > 2) {
|
|
961
|
+
// message to regenerate from is now first.
|
|
962
|
+
// Work backwards to find the first the closest user message
|
|
963
|
+
const lastUserMessageBeforeRegenerate = messages
|
|
964
|
+
.slice(0, reloadMessageIndex)
|
|
965
|
+
.reverse()
|
|
966
|
+
.find(
|
|
967
|
+
(msg) =>
|
|
968
|
+
// @ts-expect-error -- message has role
|
|
969
|
+
msg.role === MessageRole.User,
|
|
970
|
+
);
|
|
971
|
+
const indexOfLastUserMessageBeforeRegenerate = messages.findIndex(
|
|
972
|
+
(msg) => msg.id === lastUserMessageBeforeRegenerate!.id,
|
|
973
|
+
);
|
|
812
974
|
|
|
813
|
-
|
|
975
|
+
// Include the user message, remove everything after it
|
|
976
|
+
historyCutoff = messages.slice(0, indexOfLastUserMessageBeforeRegenerate + 1);
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
setMessages(historyCutoff);
|
|
980
|
+
|
|
981
|
+
return runChatCompletionAndHandleFunctionCall(historyCutoff);
|
|
814
982
|
},
|
|
815
983
|
[isLoading, messages, setMessages, runChatCompletionAndHandleFunctionCall],
|
|
816
984
|
);
|
|
@@ -858,26 +1026,46 @@ function constructFinalMessages(
|
|
|
858
1026
|
|
|
859
1027
|
async function executeAction({
|
|
860
1028
|
onFunctionCall,
|
|
861
|
-
previousMessages,
|
|
862
1029
|
message,
|
|
863
1030
|
chatAbortControllerRef,
|
|
864
1031
|
onError,
|
|
1032
|
+
setMessages,
|
|
1033
|
+
getFinalMessages,
|
|
1034
|
+
isRenderAndWait,
|
|
865
1035
|
}: {
|
|
866
1036
|
onFunctionCall: FunctionCallHandler;
|
|
867
|
-
previousMessages: Message[];
|
|
868
1037
|
message: ActionExecutionMessage;
|
|
869
1038
|
chatAbortControllerRef: React.MutableRefObject<AbortController | null>;
|
|
870
1039
|
onError: (error: Error) => void;
|
|
1040
|
+
setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
|
|
1041
|
+
getFinalMessages: () => Message[];
|
|
1042
|
+
isRenderAndWait: boolean;
|
|
871
1043
|
}) {
|
|
872
1044
|
let result: any;
|
|
873
1045
|
let error: Error | null = null;
|
|
1046
|
+
|
|
1047
|
+
const currentMessagesForHandler = getFinalMessages();
|
|
1048
|
+
|
|
1049
|
+
// The handler (onFunctionCall) runs its synchronous part here, potentially setting up
|
|
1050
|
+
// renderAndWaitRef.current for HITL actions via useCopilotAction's transformed handler.
|
|
1051
|
+
const handlerReturnedPromise = onFunctionCall({
|
|
1052
|
+
messages: currentMessagesForHandler,
|
|
1053
|
+
name: message.name,
|
|
1054
|
+
args: message.arguments,
|
|
1055
|
+
});
|
|
1056
|
+
|
|
1057
|
+
// For HITL actions, call flushSync immediately after their handler has set up the promise
|
|
1058
|
+
// and before awaiting the promise. This ensures the UI updates to an interactive state.
|
|
1059
|
+
if (isRenderAndWait) {
|
|
1060
|
+
const currentMessagesForRender = getFinalMessages();
|
|
1061
|
+
flushSync(() => {
|
|
1062
|
+
setMessages([...currentMessagesForRender]);
|
|
1063
|
+
});
|
|
1064
|
+
}
|
|
1065
|
+
|
|
874
1066
|
try {
|
|
875
1067
|
result = await Promise.race([
|
|
876
|
-
|
|
877
|
-
messages: previousMessages,
|
|
878
|
-
name: message.name,
|
|
879
|
-
args: message.arguments,
|
|
880
|
-
}),
|
|
1068
|
+
handlerReturnedPromise, // Await the promise returned by the handler
|
|
881
1069
|
new Promise((resolve) =>
|
|
882
1070
|
chatAbortControllerRef.current?.signal.addEventListener("abort", () =>
|
|
883
1071
|
resolve("Operation was aborted by the user"),
|
package/src/hooks/use-coagent.ts
CHANGED
|
@@ -278,6 +278,11 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
|
|
|
278
278
|
agentName: name,
|
|
279
279
|
});
|
|
280
280
|
|
|
281
|
+
// Runtime client handles errors automatically via handleGQLErrors
|
|
282
|
+
if (result.error) {
|
|
283
|
+
return; // Don't process data on error
|
|
284
|
+
}
|
|
285
|
+
|
|
281
286
|
const newState = result.data?.loadAgentState?.state;
|
|
282
287
|
if (newState === lastLoadedState.current) return;
|
|
283
288
|
|
|
@@ -306,6 +311,42 @@ export function useCoAgent<T = any>(options: UseCoagentOptions<T>): UseCoagentRe
|
|
|
306
311
|
coagentStates[name] === undefined,
|
|
307
312
|
]);
|
|
308
313
|
|
|
314
|
+
// Sync config when runtime configuration changes
|
|
315
|
+
useEffect(() => {
|
|
316
|
+
const newConfig = options.config
|
|
317
|
+
? options.config
|
|
318
|
+
: options.configurable
|
|
319
|
+
? { configurable: options.configurable }
|
|
320
|
+
: undefined;
|
|
321
|
+
|
|
322
|
+
if (newConfig === undefined) return;
|
|
323
|
+
|
|
324
|
+
setCoagentStatesWithRef((prev) => {
|
|
325
|
+
const existing = prev[name] ?? {
|
|
326
|
+
name,
|
|
327
|
+
state: isInternalStateManagementWithInitial(options) ? options.initialState : {},
|
|
328
|
+
config: {},
|
|
329
|
+
running: false,
|
|
330
|
+
active: false,
|
|
331
|
+
threadId: undefined,
|
|
332
|
+
nodeName: undefined,
|
|
333
|
+
runId: undefined,
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
if (JSON.stringify(existing.config) === JSON.stringify(newConfig)) {
|
|
337
|
+
return prev;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
return {
|
|
341
|
+
...prev,
|
|
342
|
+
[name]: {
|
|
343
|
+
...existing,
|
|
344
|
+
config: newConfig,
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
}, [JSON.stringify(options.config), JSON.stringify(options.configurable)]);
|
|
349
|
+
|
|
309
350
|
const runAgentCallback = useAsyncCallback(
|
|
310
351
|
async (hint?: HintFunction) => {
|
|
311
352
|
await runAgent(name, context, appendMessage, runChatCompletion, hint);
|
|
@@ -159,11 +159,14 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
|
|
|
159
159
|
const { setAction, removeAction, actions, chatComponentsCache } = useCopilotContext();
|
|
160
160
|
const idRef = useRef<string>(randomId());
|
|
161
161
|
const renderAndWaitRef = useRef<RenderAndWaitForResponse | null>(null);
|
|
162
|
+
const activatingMessageIdRef = useRef<string | null>(null);
|
|
162
163
|
const { addToast } = useToast();
|
|
163
164
|
|
|
164
165
|
// clone the action to avoid mutating the original object
|
|
165
166
|
action = { ...action };
|
|
166
167
|
|
|
168
|
+
// const { currentlyActivatingHitlActionMessageIdRef } = useCopilotContext() as any; // <-- REMOVE THIS FOR NOW
|
|
169
|
+
|
|
167
170
|
// If the developer provides a renderAndWaitForResponse function, we transform the action
|
|
168
171
|
// to use a promise internally, so that we can treat it like a normal action.
|
|
169
172
|
if (
|
|
@@ -172,12 +175,21 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
|
|
|
172
175
|
// check if renderAndWaitForResponse is set
|
|
173
176
|
(action.renderAndWait || action.renderAndWaitForResponse)
|
|
174
177
|
) {
|
|
178
|
+
(action as any)._isRenderAndWait = true; // Internal flag to identify this action type later
|
|
175
179
|
const renderAndWait = action.renderAndWait || action.renderAndWaitForResponse;
|
|
176
180
|
// remove the renderAndWait function from the action
|
|
177
181
|
action.renderAndWait = undefined;
|
|
178
182
|
action.renderAndWaitForResponse = undefined;
|
|
183
|
+
|
|
184
|
+
// Add a method for use-chat.ts to set the activating message ID.
|
|
185
|
+
// This helps correlate the action instance with the message being processed by use-chat.
|
|
186
|
+
(action as any)._setActivatingMessageId = (id: string | null) => {
|
|
187
|
+
activatingMessageIdRef.current = id;
|
|
188
|
+
};
|
|
189
|
+
|
|
179
190
|
// add a handler that will be called when the action is executed
|
|
180
191
|
action.handler = useAsyncCallback(async () => {
|
|
192
|
+
const currentActivatingId = activatingMessageIdRef.current;
|
|
181
193
|
// we create a new promise when the handler is called
|
|
182
194
|
let resolve: (result: any) => void;
|
|
183
195
|
let reject: (error: any) => void;
|
|
@@ -185,26 +197,55 @@ export function useCopilotAction<const T extends Parameter[] | [] = []>(
|
|
|
185
197
|
resolve = resolvePromise;
|
|
186
198
|
reject = rejectPromise;
|
|
187
199
|
});
|
|
188
|
-
renderAndWaitRef.current = {
|
|
200
|
+
renderAndWaitRef.current = {
|
|
201
|
+
promise,
|
|
202
|
+
resolve: resolve!,
|
|
203
|
+
reject: reject!,
|
|
204
|
+
messageId: currentActivatingId,
|
|
205
|
+
};
|
|
189
206
|
// then we await the promise (it will be resolved in the original renderAndWait function)
|
|
190
|
-
|
|
207
|
+
const result = await promise;
|
|
208
|
+
return result;
|
|
191
209
|
}, []) as any;
|
|
192
210
|
|
|
193
211
|
// add a render function that will be called when the action is rendered
|
|
194
|
-
action.render = ((props: ActionRenderProps<T>): React.ReactElement => {
|
|
195
|
-
|
|
196
|
-
//
|
|
212
|
+
action.render = ((props: ActionRenderProps<T> & { messageId?: string }): React.ReactElement => {
|
|
213
|
+
const currentRenderMessageId = props.messageId;
|
|
214
|
+
// For renderAndWaitForResponse, the 'executing' state might be set by use-chat before
|
|
215
|
+
// this specific action instance's handler (and thus its promise) is ready.
|
|
216
|
+
// This logic adjusts the status to 'inProgress' if the current render
|
|
217
|
+
// isn't for the actively processing HITL action, preventing premature interaction.
|
|
197
218
|
let status = props.status;
|
|
198
|
-
if (props.status === "executing"
|
|
199
|
-
|
|
219
|
+
if (props.status === "executing") {
|
|
220
|
+
if (!renderAndWaitRef.current || !renderAndWaitRef.current.promise) {
|
|
221
|
+
status = "inProgress";
|
|
222
|
+
} else if (
|
|
223
|
+
renderAndWaitRef.current.messageId !== currentRenderMessageId &&
|
|
224
|
+
activatingMessageIdRef.current !== currentRenderMessageId
|
|
225
|
+
) {
|
|
226
|
+
status = "inProgress";
|
|
227
|
+
}
|
|
228
|
+
// If conditions met, status remains 'executing'
|
|
200
229
|
}
|
|
201
230
|
// Create type safe waitProps based on whether T extends empty array or not
|
|
202
231
|
const waitProps = {
|
|
203
232
|
status,
|
|
204
233
|
args: props.args,
|
|
205
234
|
result: props.result,
|
|
206
|
-
handler
|
|
207
|
-
|
|
235
|
+
// handler and respond should only be provided if this is the truly active instance
|
|
236
|
+
// and its promise infrastructure is ready.
|
|
237
|
+
handler:
|
|
238
|
+
status === "executing" &&
|
|
239
|
+
renderAndWaitRef.current &&
|
|
240
|
+
renderAndWaitRef.current.messageId === currentRenderMessageId
|
|
241
|
+
? renderAndWaitRef.current!.resolve
|
|
242
|
+
: undefined,
|
|
243
|
+
respond:
|
|
244
|
+
status === "executing" &&
|
|
245
|
+
renderAndWaitRef.current &&
|
|
246
|
+
renderAndWaitRef.current.messageId === currentRenderMessageId
|
|
247
|
+
? renderAndWaitRef.current!.resolve
|
|
248
|
+
: undefined,
|
|
208
249
|
} as T extends [] ? ActionRenderPropsNoArgsWait<T> : ActionRenderPropsWait<T>;
|
|
209
250
|
|
|
210
251
|
// Type guard to check if renderAndWait is for no args case
|
|
@@ -302,4 +343,5 @@ interface RenderAndWaitForResponse {
|
|
|
302
343
|
promise: Promise<any>;
|
|
303
344
|
resolve: (result: any) => void;
|
|
304
345
|
reject: (error: any) => void;
|
|
346
|
+
messageId: string | null;
|
|
305
347
|
}
|