@copilotkit/react-core 1.9.2-next.2 → 1.9.2-next.21
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 +162 -0
- package/dist/{chunk-UHQMV2CE.mjs → chunk-36MGCCPZ.mjs} +2 -2
- package/dist/{chunk-CCESTGAM.mjs → chunk-3OQM3NEK.mjs} +2 -2
- package/dist/{chunk-OUSWPVDT.mjs → chunk-4URMLOBR.mjs} +4 -4
- package/dist/chunk-57K2ZJ5F.mjs +348 -0
- package/dist/chunk-57K2ZJ5F.mjs.map +1 -0
- package/dist/{chunk-LZDDYZEY.mjs → chunk-5BSUSFHM.mjs} +2 -2
- package/dist/{chunk-C6F6EQNA.mjs → chunk-BVK7PLK6.mjs} +2 -2
- package/dist/{chunk-CMKIDDQL.mjs → chunk-DDIBJUWK.mjs} +11 -8
- package/dist/{chunk-CMKIDDQL.mjs.map → chunk-DDIBJUWK.mjs.map} +1 -1
- package/dist/{chunk-NNSXCFQO.mjs → chunk-DGON3GZX.mjs} +42 -8
- package/dist/chunk-DGON3GZX.mjs.map +1 -0
- package/dist/{chunk-6KGEF242.mjs → chunk-DKZTPL66.mjs} +3 -2
- package/dist/chunk-DKZTPL66.mjs.map +1 -0
- package/dist/{chunk-LDACFA2B.mjs → chunk-FN3UA2ZE.mjs} +3 -3
- package/dist/{chunk-RUY6MLHA.mjs → chunk-JWAXDYOW.mjs} +36 -6
- package/dist/chunk-JWAXDYOW.mjs.map +1 -0
- package/dist/{chunk-2FW7HH6W.mjs → chunk-KIXKBJUV.mjs} +3 -3
- package/dist/{chunk-MGIXEJWG.mjs → chunk-MTAJI7HV.mjs} +181 -68
- package/dist/chunk-MTAJI7HV.mjs.map +1 -0
- package/dist/{chunk-L6QAOAE4.mjs → chunk-N2M65NJ2.mjs} +69 -25
- package/dist/chunk-N2M65NJ2.mjs.map +1 -0
- package/dist/{chunk-T42PN5VN.mjs → chunk-NJA5ZLAZ.mjs} +29 -10
- package/dist/chunk-NJA5ZLAZ.mjs.map +1 -0
- package/dist/{chunk-4I7PLQF7.mjs → chunk-QKEH3O4S.mjs} +5 -5
- package/dist/{chunk-FRZZPPIV.mjs → chunk-RAQK4M64.mjs} +2 -2
- package/dist/{chunk-QQZLIEXK.mjs → chunk-SJJNFYGQ.mjs} +3 -3
- package/dist/{chunk-Q5D5XQFA.mjs → chunk-VDADWRS3.mjs} +2 -2
- package/dist/chunk-YAF2LATQ.mjs +310 -0
- package/dist/chunk-YAF2LATQ.mjs.map +1 -0
- package/dist/components/copilot-provider/copilot-messages.js +40 -5
- 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 +27 -3
- 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 +385 -254
- 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 +385 -254
- 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 +385 -254
- 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 +2 -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 +2 -1
- package/dist/context/index.js.map +1 -1
- package/dist/context/index.mjs +1 -1
- package/dist/{copilot-context-f9b2b4c3.d.ts → copilot-context-3ab4fdf5.d.ts} +6 -2
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +232 -71
- 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 +295 -156
- package/dist/hooks/use-chat.js.map +1 -1
- package/dist/hooks/use-chat.mjs +6 -6
- package/dist/hooks/use-coagent-state-render.js +2 -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 +207 -65
- package/dist/hooks/use-coagent.js.map +1 -1
- package/dist/hooks/use-coagent.mjs +14 -14
- package/dist/hooks/use-copilot-action.js +27 -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 +2 -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 +27 -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 +204 -65
- 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 +2 -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 -0
- package/dist/hooks/use-copilot-runtime-client.js +52 -2
- 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 +2 -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 +204 -65
- 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 +2 -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 +582 -321
- 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/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 +7 -3
- package/package.json +4 -3
- package/src/components/copilot-provider/__tests__/copilotkit-error.test.tsx +75 -0
- package/src/components/copilot-provider/copilot-messages.tsx +47 -6
- package/src/components/copilot-provider/copilotkit-props.tsx +27 -1
- package/src/components/copilot-provider/copilotkit.tsx +64 -18
- package/src/components/toast/toast-provider.tsx +49 -24
- package/src/components/usage-banner.tsx +144 -147
- package/src/context/copilot-context.tsx +8 -2
- package/src/hooks/use-chat.ts +247 -61
- package/src/hooks/use-coagent.ts +5 -0
- package/src/hooks/use-copilot-action.ts +51 -9
- package/src/hooks/use-copilot-runtime-client.ts +41 -40
- package/tsconfig.json +4 -8
- package/tsup.config.ts +6 -6
- package/dist/chunk-6KGEF242.mjs.map +0 -1
- package/dist/chunk-HD2GE3DK.mjs +0 -359
- package/dist/chunk-HD2GE3DK.mjs.map +0 -1
- package/dist/chunk-L6QAOAE4.mjs.map +0 -1
- package/dist/chunk-MGIXEJWG.mjs.map +0 -1
- package/dist/chunk-NNSXCFQO.mjs.map +0 -1
- package/dist/chunk-RUY6MLHA.mjs.map +0 -1
- package/dist/chunk-T42PN5VN.mjs.map +0 -1
- package/dist/chunk-VRXANACV.mjs +0 -277
- package/dist/chunk-VRXANACV.mjs.map +0 -1
- package/dist/utils/utils.test.d.ts +0 -2
- package/dist/utils/utils.test.js +0 -9
- package/dist/utils/utils.test.js.map +0 -1
- package/dist/utils/utils.test.mjs +0 -7
- package/dist/utils/utils.test.mjs.map +0 -1
- /package/dist/{chunk-UHQMV2CE.mjs.map → chunk-36MGCCPZ.mjs.map} +0 -0
- /package/dist/{chunk-CCESTGAM.mjs.map → chunk-3OQM3NEK.mjs.map} +0 -0
- /package/dist/{chunk-OUSWPVDT.mjs.map → chunk-4URMLOBR.mjs.map} +0 -0
- /package/dist/{chunk-LZDDYZEY.mjs.map → chunk-5BSUSFHM.mjs.map} +0 -0
- /package/dist/{chunk-C6F6EQNA.mjs.map → chunk-BVK7PLK6.mjs.map} +0 -0
- /package/dist/{chunk-LDACFA2B.mjs.map → chunk-FN3UA2ZE.mjs.map} +0 -0
- /package/dist/{chunk-2FW7HH6W.mjs.map → chunk-KIXKBJUV.mjs.map} +0 -0
- /package/dist/{chunk-4I7PLQF7.mjs.map → chunk-QKEH3O4S.mjs.map} +0 -0
- /package/dist/{chunk-FRZZPPIV.mjs.map → chunk-RAQK4M64.mjs.map} +0 -0
- /package/dist/{chunk-QQZLIEXK.mjs.map → chunk-SJJNFYGQ.mjs.map} +0 -0
- /package/dist/{chunk-Q5D5XQFA.mjs.map → chunk-VDADWRS3.mjs.map} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CopilotCloudConfig, FunctionCallHandler } from "@copilotkit/shared";
|
|
1
|
+
import { CopilotCloudConfig, FunctionCallHandler, CopilotErrorHandler } from "@copilotkit/shared";
|
|
2
2
|
import {
|
|
3
3
|
ActionRenderProps,
|
|
4
4
|
CatchAllActionRenderProps,
|
|
@@ -195,7 +195,7 @@ export interface CopilotContextParams {
|
|
|
195
195
|
/**
|
|
196
196
|
* The forwarded parameters to use for the task.
|
|
197
197
|
*/
|
|
198
|
-
forwardedParameters?: Pick<ForwardedParametersInput, "temperature"
|
|
198
|
+
forwardedParameters?: Partial<Pick<ForwardedParametersInput, "temperature">>;
|
|
199
199
|
availableAgents: Agent[];
|
|
200
200
|
|
|
201
201
|
/**
|
|
@@ -218,6 +218,11 @@ export interface CopilotContextParams {
|
|
|
218
218
|
langGraphInterruptAction: LangGraphInterruptAction | null;
|
|
219
219
|
setLangGraphInterruptAction: LangGraphInterruptActionSetter;
|
|
220
220
|
removeLangGraphInterruptAction: () => void;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Optional trace handler for comprehensive debugging and observability.
|
|
224
|
+
*/
|
|
225
|
+
onError?: CopilotErrorHandler;
|
|
221
226
|
}
|
|
222
227
|
|
|
223
228
|
const emptyCopilotContext: CopilotContextParams = {
|
|
@@ -288,6 +293,7 @@ const emptyCopilotContext: CopilotContextParams = {
|
|
|
288
293
|
langGraphInterruptAction: null,
|
|
289
294
|
setLangGraphInterruptAction: () => null,
|
|
290
295
|
removeLangGraphInterruptAction: () => null,
|
|
296
|
+
onError: undefined,
|
|
291
297
|
};
|
|
292
298
|
|
|
293
299
|
export const CopilotContext = React.createContext<CopilotContextParams>(emptyCopilotContext);
|
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) {
|
|
@@ -558,6 +658,55 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
558
658
|
|
|
559
659
|
let didExecuteAction = false;
|
|
560
660
|
|
|
661
|
+
// ----- Helper function to execute an action and manage its lifecycle -----
|
|
662
|
+
const executeActionFromMessage = async (
|
|
663
|
+
currentAction: FrontendAction<any>,
|
|
664
|
+
actionMessage: ActionExecutionMessage,
|
|
665
|
+
) => {
|
|
666
|
+
const isInterruptAction = interruptMessages.find((m) => m.id === actionMessage.id);
|
|
667
|
+
// Determine follow-up behavior: use action's specific setting if defined, otherwise default based on interrupt status.
|
|
668
|
+
followUp = currentAction?.followUp ?? !isInterruptAction;
|
|
669
|
+
|
|
670
|
+
// Call _setActivatingMessageId before executing the action for HITL correlation
|
|
671
|
+
if ((currentAction as any)?._setActivatingMessageId) {
|
|
672
|
+
(currentAction as any)._setActivatingMessageId(actionMessage.id);
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
const resultMessage = await executeAction({
|
|
676
|
+
onFunctionCall: onFunctionCall!,
|
|
677
|
+
message: actionMessage,
|
|
678
|
+
chatAbortControllerRef,
|
|
679
|
+
onError: (error: Error) => {
|
|
680
|
+
addErrorToast([error]);
|
|
681
|
+
// console.error is kept here as it's a genuine error in action execution
|
|
682
|
+
console.error(`Failed to execute action ${actionMessage.name}: ${error}`);
|
|
683
|
+
},
|
|
684
|
+
setMessages,
|
|
685
|
+
getFinalMessages: () => finalMessages,
|
|
686
|
+
isRenderAndWait: (currentAction as any)?._isRenderAndWait || false,
|
|
687
|
+
});
|
|
688
|
+
didExecuteAction = true;
|
|
689
|
+
const messageIndex = finalMessages.findIndex((msg) => msg.id === actionMessage.id);
|
|
690
|
+
finalMessages.splice(messageIndex + 1, 0, resultMessage);
|
|
691
|
+
|
|
692
|
+
// If the executed action was a renderAndWaitForResponse type, update messages immediately
|
|
693
|
+
// to reflect its completion in the UI, making it interactive promptly.
|
|
694
|
+
if ((currentAction as any)?._isRenderAndWait) {
|
|
695
|
+
const messagesForImmediateUpdate = [...finalMessages];
|
|
696
|
+
flushSync(() => {
|
|
697
|
+
setMessages(messagesForImmediateUpdate);
|
|
698
|
+
});
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// Clear _setActivatingMessageId after the action is done
|
|
702
|
+
if ((currentAction as any)?._setActivatingMessageId) {
|
|
703
|
+
(currentAction as any)._setActivatingMessageId(null);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
return resultMessage;
|
|
707
|
+
};
|
|
708
|
+
// ----------------------------------------------------------------------
|
|
709
|
+
|
|
561
710
|
// execute regular action executions that are specific to the frontend (last actions)
|
|
562
711
|
if (onFunctionCall) {
|
|
563
712
|
// Find consecutive action execution messages at the end
|
|
@@ -587,44 +736,38 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
587
736
|
? getPairedFeAction(actions, message)
|
|
588
737
|
: null;
|
|
589
738
|
|
|
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
739
|
// execution message which has an action registered with the hook (remote availability):
|
|
614
740
|
// execute that action first, and then the "paired FE action"
|
|
615
741
|
if (action && message.isActionExecutionMessage()) {
|
|
616
|
-
|
|
617
|
-
const
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
742
|
+
// For HITL actions, check if they've already been processed to avoid redundant handler calls.
|
|
743
|
+
const isRenderAndWaitAction = (action as any)?._isRenderAndWait || false;
|
|
744
|
+
const alreadyProcessed =
|
|
745
|
+
isRenderAndWaitAction &&
|
|
746
|
+
finalMessages.some(
|
|
747
|
+
(fm) => fm.isResultMessage() && fm.actionExecutionId === message.id,
|
|
748
|
+
);
|
|
749
|
+
|
|
750
|
+
if (alreadyProcessed) {
|
|
751
|
+
// Skip re-execution if already processed
|
|
752
|
+
} else {
|
|
753
|
+
// Call the single, externally defined executeActionFromMessage
|
|
754
|
+
const resultMessage = await executeActionFromMessage(
|
|
755
|
+
action,
|
|
756
|
+
message as ActionExecutionMessage,
|
|
757
|
+
);
|
|
758
|
+
const pairedFeAction = getPairedFeAction(actions, resultMessage);
|
|
759
|
+
|
|
760
|
+
if (pairedFeAction) {
|
|
761
|
+
const newExecutionMessage = new ActionExecutionMessage({
|
|
762
|
+
name: pairedFeAction.name,
|
|
763
|
+
arguments: parseJson(resultMessage.result, resultMessage.result),
|
|
764
|
+
status: message.status,
|
|
765
|
+
createdAt: message.createdAt,
|
|
766
|
+
parentMessageId: message.parentMessageId,
|
|
767
|
+
});
|
|
768
|
+
// Call the single, externally defined executeActionFromMessage
|
|
769
|
+
await executeActionFromMessage(pairedFeAction, newExecutionMessage);
|
|
770
|
+
}
|
|
628
771
|
}
|
|
629
772
|
} else if (message.isResultMessage() && currentResultMessagePairedFeAction) {
|
|
630
773
|
// Actions which are set up in runtime actions array: Grab the result, executed paired FE action with it as args.
|
|
@@ -635,6 +778,7 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
635
778
|
createdAt: message.createdAt,
|
|
636
779
|
});
|
|
637
780
|
finalMessages.push(newExecutionMessage);
|
|
781
|
+
// Call the single, externally defined executeActionFromMessage
|
|
638
782
|
await executeActionFromMessage(
|
|
639
783
|
currentResultMessagePairedFeAction,
|
|
640
784
|
newExecutionMessage,
|
|
@@ -645,10 +789,10 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
645
789
|
setMessages(finalMessages);
|
|
646
790
|
}
|
|
647
791
|
|
|
792
|
+
// Conditionally run chat completion again if followUp is not explicitly false
|
|
793
|
+
// and an action was executed or the last message is a server-side result (for non-agent runs).
|
|
648
794
|
if (
|
|
649
|
-
// if followUp is not explicitly false
|
|
650
795
|
followUp !== false &&
|
|
651
|
-
// and we executed an action
|
|
652
796
|
(didExecuteAction ||
|
|
653
797
|
// the last message is a server side result
|
|
654
798
|
(!isAgentRun &&
|
|
@@ -792,25 +936,47 @@ export function useChat(options: UseChatOptions): UseChatHelpers {
|
|
|
792
936
|
);
|
|
793
937
|
|
|
794
938
|
const reload = useAsyncCallback(
|
|
795
|
-
async (
|
|
939
|
+
async (reloadMessageId: string): Promise<void> => {
|
|
796
940
|
if (isLoading || messages.length === 0) {
|
|
797
941
|
return;
|
|
798
942
|
}
|
|
799
943
|
|
|
800
|
-
const
|
|
801
|
-
if (
|
|
802
|
-
console.warn(`Message with id ${
|
|
944
|
+
const reloadMessageIndex = messages.findIndex((msg) => msg.id === reloadMessageId);
|
|
945
|
+
if (reloadMessageIndex === -1) {
|
|
946
|
+
console.warn(`Message with id ${reloadMessageId} not found`);
|
|
803
947
|
return;
|
|
804
948
|
}
|
|
805
949
|
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
950
|
+
// @ts-expect-error -- message has role
|
|
951
|
+
const reloadMessageRole = messages[reloadMessageIndex].role;
|
|
952
|
+
if (reloadMessageRole !== MessageRole.Assistant) {
|
|
953
|
+
console.warn(`Regenerate cannot be performed on ${reloadMessageRole} role`);
|
|
954
|
+
return;
|
|
809
955
|
}
|
|
810
956
|
|
|
811
|
-
|
|
957
|
+
let historyCutoff: Message[] = [];
|
|
958
|
+
if (messages.length > 2) {
|
|
959
|
+
// message to regenerate from is now first.
|
|
960
|
+
// Work backwards to find the first the closest user message
|
|
961
|
+
const lastUserMessageBeforeRegenerate = messages
|
|
962
|
+
.slice(0, reloadMessageIndex)
|
|
963
|
+
.reverse()
|
|
964
|
+
.find(
|
|
965
|
+
(msg) =>
|
|
966
|
+
// @ts-expect-error -- message has role
|
|
967
|
+
msg.role === MessageRole.User,
|
|
968
|
+
);
|
|
969
|
+
const indexOfLastUserMessageBeforeRegenerate = messages.findIndex(
|
|
970
|
+
(msg) => msg.id === lastUserMessageBeforeRegenerate!.id,
|
|
971
|
+
);
|
|
812
972
|
|
|
813
|
-
|
|
973
|
+
// Include the user message, remove everything after it
|
|
974
|
+
historyCutoff = messages.slice(0, indexOfLastUserMessageBeforeRegenerate + 1);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
setMessages(historyCutoff);
|
|
978
|
+
|
|
979
|
+
return runChatCompletionAndHandleFunctionCall(historyCutoff);
|
|
814
980
|
},
|
|
815
981
|
[isLoading, messages, setMessages, runChatCompletionAndHandleFunctionCall],
|
|
816
982
|
);
|
|
@@ -858,26 +1024,46 @@ function constructFinalMessages(
|
|
|
858
1024
|
|
|
859
1025
|
async function executeAction({
|
|
860
1026
|
onFunctionCall,
|
|
861
|
-
previousMessages,
|
|
862
1027
|
message,
|
|
863
1028
|
chatAbortControllerRef,
|
|
864
1029
|
onError,
|
|
1030
|
+
setMessages,
|
|
1031
|
+
getFinalMessages,
|
|
1032
|
+
isRenderAndWait,
|
|
865
1033
|
}: {
|
|
866
1034
|
onFunctionCall: FunctionCallHandler;
|
|
867
|
-
previousMessages: Message[];
|
|
868
1035
|
message: ActionExecutionMessage;
|
|
869
1036
|
chatAbortControllerRef: React.MutableRefObject<AbortController | null>;
|
|
870
1037
|
onError: (error: Error) => void;
|
|
1038
|
+
setMessages: React.Dispatch<React.SetStateAction<Message[]>>;
|
|
1039
|
+
getFinalMessages: () => Message[];
|
|
1040
|
+
isRenderAndWait: boolean;
|
|
871
1041
|
}) {
|
|
872
1042
|
let result: any;
|
|
873
1043
|
let error: Error | null = null;
|
|
1044
|
+
|
|
1045
|
+
const currentMessagesForHandler = getFinalMessages();
|
|
1046
|
+
|
|
1047
|
+
// The handler (onFunctionCall) runs its synchronous part here, potentially setting up
|
|
1048
|
+
// renderAndWaitRef.current for HITL actions via useCopilotAction's transformed handler.
|
|
1049
|
+
const handlerReturnedPromise = onFunctionCall({
|
|
1050
|
+
messages: currentMessagesForHandler,
|
|
1051
|
+
name: message.name,
|
|
1052
|
+
args: message.arguments,
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
// For HITL actions, call flushSync immediately after their handler has set up the promise
|
|
1056
|
+
// and before awaiting the promise. This ensures the UI updates to an interactive state.
|
|
1057
|
+
if (isRenderAndWait) {
|
|
1058
|
+
const currentMessagesForRender = getFinalMessages();
|
|
1059
|
+
flushSync(() => {
|
|
1060
|
+
setMessages([...currentMessagesForRender]);
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
|
|
874
1064
|
try {
|
|
875
1065
|
result = await Promise.race([
|
|
876
|
-
|
|
877
|
-
messages: previousMessages,
|
|
878
|
-
name: message.name,
|
|
879
|
-
args: message.arguments,
|
|
880
|
-
}),
|
|
1066
|
+
handlerReturnedPromise, // Await the promise returned by the handler
|
|
881
1067
|
new Promise((resolve) =>
|
|
882
1068
|
chatAbortControllerRef.current?.signal.addEventListener("abort", () =>
|
|
883
1069
|
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
|
|
|
@@ -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
|
}
|