@copilotkit/react-core
Version:
<img src="https://github.com/user-attachments/assets/0a6b64d9-e193-4940-a3f6-60334ac34084" alt="banner" style="border-radius: 12px; border: 2px solid #d6d4fa;" />
165 lines (149 loc) • 5.84 kB
text/typescript
import {
CopilotRuntimeClient,
CopilotRuntimeClientOptions,
GraphQLError,
} from "@copilotkit/runtime-client-gql";
import { useToast } from "../components/toast/toast-provider";
import { useMemo, useRef } from "react";
import {
ErrorVisibility,
CopilotKitApiDiscoveryError,
CopilotKitRemoteEndpointDiscoveryError,
CopilotKitAgentDiscoveryError,
CopilotKitError,
CopilotKitErrorCode,
CopilotErrorHandler,
CopilotErrorEvent,
} from "@copilotkit/shared";
import { shouldShowDevConsole } from "../utils/dev-console";
export interface CopilotRuntimeClientHookOptions extends CopilotRuntimeClientOptions {
showDevConsole?: boolean;
onError: CopilotErrorHandler;
}
export const useCopilotRuntimeClient = (options: CopilotRuntimeClientHookOptions) => {
const { setBannerError } = useToast();
const { showDevConsole, onError, ...runtimeOptions } = options;
// Deduplication state for structured errors
const lastStructuredErrorRef = useRef<{ message: string; timestamp: number } | null>(null);
// Helper function to trace UI errors
const traceUIError = async (error: CopilotKitError, originalError?: any) => {
try {
const errorEvent: CopilotErrorEvent = {
type: "error",
timestamp: Date.now(),
context: {
source: "ui",
request: {
operation: "runtimeClient",
url: runtimeOptions.url,
startTime: Date.now(),
},
technical: {
environment: "browser",
userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
stackTrace: originalError instanceof Error ? originalError.stack : undefined,
},
},
error,
};
await onError(errorEvent);
} catch (error) {
console.error("Error in onError handler:", error);
}
};
const runtimeClient = useMemo(() => {
return new CopilotRuntimeClient({
...runtimeOptions,
handleGQLErrors: (error) => {
if ((error as any).graphQLErrors?.length) {
const graphQLErrors = (error as any).graphQLErrors as GraphQLError[];
// Route all errors to banners for consistent UI
const routeError = (gqlError: GraphQLError) => {
const extensions = gqlError.extensions;
const visibility = extensions?.visibility as ErrorVisibility;
// Silent errors - just log
if (visibility === ErrorVisibility.SILENT) {
console.error("CopilotKit Silent Error:", gqlError.message);
return;
}
// All errors (including DEV_ONLY) show as banners for consistency
// Deduplicate to prevent spam
const now = Date.now();
const errorMessage = gqlError.message;
if (
lastStructuredErrorRef.current &&
lastStructuredErrorRef.current.message === errorMessage &&
now - lastStructuredErrorRef.current.timestamp < 150
) {
return; // Skip duplicate
}
lastStructuredErrorRef.current = { message: errorMessage, timestamp: now };
const ckError = createStructuredError(gqlError);
if (ckError) {
setBannerError(ckError);
// Trace the error
traceUIError(ckError, gqlError);
// TODO: if onError & renderError should work without key, insert here
} else {
// Fallback for unstructured errors
const fallbackError = new CopilotKitError({
message: gqlError.message,
code: CopilotKitErrorCode.UNKNOWN,
});
setBannerError(fallbackError);
// Trace the fallback error
traceUIError(fallbackError, gqlError);
// TODO: if onError & renderError should work without key, insert here
}
};
// Process all errors as banners
graphQLErrors.forEach(routeError);
} else {
// Route non-GraphQL errors to banner as well
const fallbackError = new CopilotKitError({
message: error?.message || String(error),
code: CopilotKitErrorCode.UNKNOWN,
});
setBannerError(fallbackError);
// Trace the non-GraphQL error
traceUIError(fallbackError, error);
// TODO: if onError & renderError should work without key, insert here
}
},
handleGQLWarning: (message: string) => {
console.warn(message);
// Show warnings as banners too for consistency
const warningError = new CopilotKitError({
message,
code: CopilotKitErrorCode.UNKNOWN,
});
setBannerError(warningError);
},
});
}, [runtimeOptions, setBannerError, onError]);
return runtimeClient;
};
// Create appropriate structured error from GraphQL error
function createStructuredError(gqlError: GraphQLError): CopilotKitError | null {
const extensions = gqlError.extensions;
const originalError = extensions?.originalError as any;
const message = originalError?.message || gqlError.message;
const code = extensions?.code as CopilotKitErrorCode;
if (code) {
return new CopilotKitError({ message, code });
}
// Legacy error detection by stack trace
if (originalError?.stack?.includes("CopilotApiDiscoveryError")) {
return new CopilotKitApiDiscoveryError({ message });
}
if (originalError?.stack?.includes("CopilotKitRemoteEndpointDiscoveryError")) {
return new CopilotKitRemoteEndpointDiscoveryError({ message });
}
if (originalError?.stack?.includes("CopilotKitAgentDiscoveryError")) {
return new CopilotKitAgentDiscoveryError({
agentName: "",
availableAgents: [],
});
}
return null;
}