@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;" />
1,462 lines (1,446 loc) • 64.1 kB
JavaScript
"use client";
import { B as useInterrupt, G as useAgent, H as useSuggestions, J as useFrontendTool$1, K as useHumanInTheLoop$1, V as useConfigureSuggestions, X as useRenderCustomMessages, a as ThreadsProvider, c as CoAgentStateRendersProvider, d as shouldShowDevConsole, f as useToast, g as useCopilotContext, gt as useCopilotChatConfiguration, h as CopilotContext, i as ThreadsContext, l as useCoAgentStateRenders, lt as defineToolCallRenderer, m as useCopilotMessagesContext, n as defaultCopilotContextCategories, o as useThreads, ot as useRenderToolCall$1, p as CopilotMessagesContext, r as CoAgentStateRenderBridge, s as CoAgentStateRendersContext, t as CopilotKit, u as useAsyncCallback, ut as useCopilotKit } from "./copilotkit-B5RsC6la.mjs";
import React, { Fragment, createElement, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
import { CopilotKitCoreRuntimeConnectionStatus, ToolCallStatus } from "@copilotkit/core";
import { AGUIConnectNotImplementedError, HttpAgent } from "@ag-ui/client";
import { CopilotKitAgentDiscoveryError, CopilotKitApiDiscoveryError, CopilotKitError, CopilotKitErrorCode, CopilotKitRemoteEndpointDiscoveryError, ErrorVisibility, Severity, actionParametersToJsonSchema, getZodParameters, parseJson, randomId, styledConsole } from "@copilotkit/shared";
import { ActionInputAvailability, CopilotRequestType, CopilotRuntimeClient, Message as Message$1, MetaEventName, Role, TextMessage, convertGqlOutputToMessages, convertMessagesToGqlInput, filterAgentStateMessages, gqlToAGUI } from "@copilotkit/runtime-client-gql";
//#region src/utils/suggestions-constants.ts
/**
* Constants for suggestions retry logic
*/
const SUGGESTION_RETRY_CONFIG = {
MAX_RETRIES: 3,
COOLDOWN_MS: 5e3
};
//#endregion
//#region src/hooks/use-lazy-tool-renderer.tsx
function useLazyToolRenderer() {
const renderToolCall = useRenderToolCall$1();
return useCallback((message, messages) => {
if (!message?.toolCalls?.length) return null;
const toolCall = message.toolCalls[0];
if (!toolCall) return null;
const toolMessage = messages?.find((m) => m.role === "tool" && m.toolCallId === toolCall.id);
return () => renderToolCall({
toolCall,
toolMessage
});
}, [renderToolCall]);
}
//#endregion
//#region src/hooks/use-copilot-chat_internal.ts
function useCopilotChatInternal({ suggestions, onInProgress, onSubmitMessage, onStopGeneration, onReloadMessages } = {}) {
const { copilotkit } = useCopilotKit();
const { threadId, agentSession } = useCopilotContext();
const existingConfig = useCopilotChatConfiguration();
const [agentAvailable, setAgentAvailable] = useState(false);
const resolvedAgentId = existingConfig?.agentId ?? "default";
const { agent } = useAgent({ agentId: resolvedAgentId });
const lastConnectedAgentRef = useRef(null);
useEffect(() => {
let detached = false;
const connectAbortController = new AbortController();
if (agent instanceof HttpAgent) agent.abortController = connectAbortController;
const connect = async (agent) => {
setAgentAvailable(false);
try {
await copilotkit.connectAgent({ agent });
if (!detached) setAgentAvailable(true);
} catch (error) {
if (detached) return;
if (error instanceof AGUIConnectNotImplementedError) {} else console.error("CopilotChat: connectAgent failed", error);
}
};
if (agent && agent !== lastConnectedAgentRef.current && copilotkit.runtimeConnectionStatus === CopilotKitCoreRuntimeConnectionStatus.Connected) {
lastConnectedAgentRef.current = agent;
connect(agent);
}
return () => {
lastConnectedAgentRef.current = null;
detached = true;
connectAbortController.abort();
agent?.detachActiveRun();
};
}, [
existingConfig?.threadId,
agent,
copilotkit,
copilotkit.runtimeConnectionStatus,
resolvedAgentId
]);
useEffect(() => {
onInProgress?.(Boolean(agent?.isRunning));
}, [agent?.isRunning, onInProgress]);
const [interrupt, setInterrupt] = useState(null);
useEffect(() => {
setInterrupt(copilotkit.interruptElement);
const subscription = copilotkit.subscribe({ onInterruptElementChanged: ({ interruptElement }) => {
setInterrupt(interruptElement);
} });
return () => subscription.unsubscribe();
}, [copilotkit]);
const reset = () => {
agent?.setMessages([]);
agent?.setState(null);
};
const latestDelete = useUpdatedRef(useCallback((messageId) => {
const filteredMessages = (agent?.messages ?? []).filter((message) => message.id !== messageId);
agent?.setMessages(filteredMessages);
}, [agent?.setMessages, agent?.messages]));
const latestDeleteFunc = useCallback((messageId) => {
return latestDelete.current(messageId);
}, [latestDelete]);
const currentSuggestions = useSuggestions({ agentId: resolvedAgentId });
const reload = useAsyncCallback(async (reloadMessageId) => {
if (!agent) return;
const messages = agent?.messages ?? [];
if (agent.isRunning || messages.length === 0) return;
const reloadMessageIndex = messages.findIndex((msg) => msg.id === reloadMessageId);
if (reloadMessageIndex === -1) {
console.warn(`Message with id ${reloadMessageId} not found`);
return;
}
const reloadMessageRole = messages[reloadMessageIndex].role;
if (reloadMessageRole !== "assistant") {
console.warn(`Regenerate cannot be performed on ${reloadMessageRole} role`);
return;
}
let historyCutoff = [messages[0]];
if (messages.length > 2 && reloadMessageIndex !== 0) {
const lastUserMessageBeforeRegenerate = messages.slice(0, reloadMessageIndex).toReversed().find((msg) => msg.role === "user");
if (!lastUserMessageBeforeRegenerate) historyCutoff = [messages[0]];
else {
const indexOfLastUserMessageBeforeRegenerate = messages.findIndex((msg) => msg.id === lastUserMessageBeforeRegenerate.id);
historyCutoff = messages.slice(0, indexOfLastUserMessageBeforeRegenerate + 1);
}
} else if (messages.length > 2 && reloadMessageIndex === 0) historyCutoff = [messages[0], messages[1]];
agent?.setMessages(historyCutoff);
if (agent) try {
await copilotkit.runAgent({ agent });
} catch (error) {
console.error("CopilotChat: runAgent failed during reload", error);
}
}, [
agent?.messages.length,
agent?.isRunning,
agent?.setMessages,
copilotkit?.runAgent
]);
const latestSendMessageFunc = useAsyncCallback(async (message, options) => {
if (!agent) return;
const followUp = options?.followUp ?? true;
if (options?.clearSuggestions) copilotkit.clearSuggestions(resolvedAgentId);
if (onSubmitMessage) {
const content = typeof message.content === "string" ? message.content : message.content && "text" in message.content ? message.content.text : message.content && "filename" in message.content ? message.content.filename : "";
try {
await onSubmitMessage(content);
} catch (error) {
console.error("Error in onSubmitMessage:", error);
}
}
agent?.addMessage(message);
if (followUp) try {
await copilotkit.runAgent({ agent });
} catch (error) {
console.error("CopilotChat: runAgent failed", error);
}
}, [
agent,
copilotkit,
resolvedAgentId,
onSubmitMessage
]);
const latestAppendFunc = useAsyncCallback(async (message, options) => {
return latestSendMessageFunc(gqlToAGUI([message])[0], options);
}, [latestSendMessageFunc]);
const latestSetMessagesFunc = useCallback((messages) => {
if (messages.every((message) => message instanceof Message$1)) return agent?.setMessages?.(gqlToAGUI(messages));
return agent?.setMessages?.(messages);
}, [agent?.setMessages, agent]);
const latestReload = useUpdatedRef(reload);
const latestReloadFunc = useAsyncCallback(async (messageId) => {
onReloadMessages?.({
messageId,
currentAgentName: agent?.agentId,
messages: agent?.messages ?? []
});
return await latestReload.current(messageId);
}, [
latestReload,
agent,
onReloadMessages
]);
const latestStopFunc = useCallback(() => {
onStopGeneration?.({
currentAgentName: agent?.agentId,
messages: agent?.messages ?? []
});
return agent?.abortRun?.();
}, [onStopGeneration, agent]);
const latestReset = useUpdatedRef(reset);
const latestResetFunc = useCallback(() => {
return latestReset.current();
}, [latestReset]);
const lazyToolRendered = useLazyToolRenderer();
const renderCustomMessage = useRenderCustomMessages();
const legacyCustomMessageRenderer = useLegacyCoagentRenderer({
copilotkit,
agent,
agentId: resolvedAgentId,
threadId: existingConfig?.threadId ?? threadId
});
const allMessages = agent?.messages ?? [];
const resolvedMessages = useMemo(() => {
let processedMessages = allMessages.map((message) => {
if (message.role !== "assistant") return message;
const lazyRendered = lazyToolRendered(message, allMessages);
if (lazyRendered) {
const renderedGenUi = lazyRendered();
if (renderedGenUi) return {
...message,
generativeUI: () => renderedGenUi
};
}
const bridgeRenderer = legacyCustomMessageRenderer || renderCustomMessage ? () => {
if (legacyCustomMessageRenderer) return legacyCustomMessageRenderer({
message,
position: "before"
});
try {
return renderCustomMessage?.({
message,
position: "before"
}) ?? null;
} catch (error) {
console.warn("[CopilotKit] renderCustomMessages failed, falling back to legacy renderer", error);
return null;
}
} : null;
if (bridgeRenderer) return {
...message,
generativeUI: bridgeRenderer,
generativeUIPosition: "before"
};
return message;
});
const hasAssistantMessages = processedMessages.some((msg) => msg.role === "assistant");
const canUseCustomRenderer = Boolean(renderCustomMessage && copilotkit?.getAgent?.(resolvedAgentId));
const placeholderRenderer = legacyCustomMessageRenderer ? legacyCustomMessageRenderer : canUseCustomRenderer ? renderCustomMessage : null;
const shouldRenderPlaceholder = Boolean(agent?.isRunning) || Boolean(agent?.state && Object.keys(agent.state).length);
const effectiveThreadId = threadId ?? agent?.threadId ?? "default";
let latestUserIndex = -1;
for (let i = processedMessages.length - 1; i >= 0; i -= 1) if (processedMessages[i].role === "user") {
latestUserIndex = i;
break;
}
const latestUserMessageId = latestUserIndex >= 0 ? processedMessages[latestUserIndex].id : void 0;
const currentRunId = latestUserMessageId ? copilotkit.getRunIdForMessage(resolvedAgentId, effectiveThreadId, latestUserMessageId) || `pending:${latestUserMessageId}` : void 0;
const hasAssistantForCurrentRun = latestUserIndex >= 0 ? processedMessages.slice(latestUserIndex + 1).some((msg) => msg.role === "assistant") : hasAssistantMessages;
if (placeholderRenderer && shouldRenderPlaceholder && !hasAssistantForCurrentRun) {
const placeholderMessage = {
id: currentRunId ? `coagent-state-render-${resolvedAgentId}-${currentRunId}` : `coagent-state-render-${resolvedAgentId}`,
role: "assistant",
content: "",
name: "coagent-state-render",
runId: currentRunId
};
processedMessages = [...processedMessages, {
...placeholderMessage,
generativeUIPosition: "before",
generativeUI: () => placeholderRenderer({
message: placeholderMessage,
position: "before"
})
}];
}
return processedMessages;
}, [
agent?.messages,
lazyToolRendered,
allMessages,
renderCustomMessage,
legacyCustomMessageRenderer,
resolvedAgentId,
copilotkit,
agent?.isRunning,
agent?.state
]);
const renderedSuggestions = useMemo(() => {
if (Array.isArray(suggestions)) return {
suggestions: suggestions.map((s) => ({
...s,
isLoading: false
})),
isLoading: false
};
return currentSuggestions;
}, [suggestions, currentSuggestions]);
return {
messages: resolvedMessages,
sendMessage: latestSendMessageFunc,
appendMessage: latestAppendFunc,
setMessages: latestSetMessagesFunc,
reloadMessages: latestReloadFunc,
stopGeneration: latestStopFunc,
reset: latestResetFunc,
deleteMessage: latestDeleteFunc,
isAvailable: agentAvailable,
isLoading: Boolean(agent?.isRunning),
suggestions: renderedSuggestions.suggestions,
setSuggestions: (suggestions) => copilotkit.addSuggestionsConfig({ suggestions }),
generateSuggestions: async () => copilotkit.reloadSuggestions(resolvedAgentId),
resetSuggestions: () => copilotkit.clearSuggestions(resolvedAgentId),
isLoadingSuggestions: renderedSuggestions.isLoading,
interrupt,
agent,
threadId
};
}
function useUpdatedRef(value) {
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return ref;
}
function useLegacyCoagentRenderer({ copilotkit, agent, agentId, threadId }) {
return useMemo(() => {
if (!copilotkit || !agent) return null;
return ({ message, position }) => {
const effectiveThreadId = threadId ?? agent.threadId ?? "default";
const providedRunId = message.runId;
return createElement(CoAgentStateRenderBridge, {
message,
position,
runId: (providedRunId ? providedRunId : copilotkit.getRunIdForMessage(agentId, effectiveThreadId, message.id)) || `pending:${message.id}`,
messageIndex: Math.max(agent.messages.findIndex((msg) => msg.id === message.id), 0),
messageIndexInRun: 0,
numberOfMessagesInRun: 1,
agentId,
stateSnapshot: message.state
});
};
}, [
agent,
agentId,
copilotkit,
threadId
]);
}
//#endregion
//#region src/hooks/use-copilot-chat.ts
/**
* A lightweight React hook for headless chat interactions.
* Perfect for programmatic messaging, background operations, and custom UI implementations.
*
* **Open Source Friendly** - Works without requiring a `publicApiKey`.
*/
function useCopilotChat(options = {}) {
const { visibleMessages, appendMessage, reloadMessages, stopGeneration, reset, isLoading, isAvailable, runChatCompletion, mcpServers, setMcpServers } = useCopilotChatInternal(options);
return {
visibleMessages,
appendMessage,
reloadMessages,
stopGeneration,
reset,
isLoading,
isAvailable,
runChatCompletion,
mcpServers,
setMcpServers
};
}
//#endregion
//#region src/hooks/use-copilot-chat-headless_c.ts
/**
* `useCopilotChatHeadless_c` is for building fully custom UI (headless UI) implementations.
*
* <Callout title="This is a premium-only feature">
* Sign up for free on [Copilot Cloud](https://cloud.copilotkit.ai) to get your public license key or read more about <a href="/premium/overview">premium features</a>.
*
* Usage is generous, **free** to get started, and works with **either self-hosted or Copilot Cloud** environments.
* </Callout>
*
* ## Key Features
*
* - **Fully headless**: Build your own fully custom UI's for your agentic applications.
* - **Advanced Suggestions**: Direct access to suggestions array with full control
* - **Interrupt Handling**: Support for advanced interrupt functionality
* - **MCP Server Support**: Model Context Protocol server configurations
* - **Chat Controls**: Complete set of chat management functions
* - **Loading States**: Comprehensive loading state management
*
*
* ## Usage
*
* ### Basic Setup
*
* ```tsx
* import { CopilotKit } from "@copilotkit/react-core";
* import { useCopilotChatHeadless_c } from "@copilotkit/react-core";
*
* export function App() {
* return (
* <CopilotKit publicApiKey="your-free-public-license-key">
* <YourComponent />
* </CopilotKit>
* );
* }
*
* export function YourComponent() {
* const { messages, sendMessage, isLoading } = useCopilotChatHeadless_c();
*
* const handleSendMessage = async () => {
* await sendMessage({
* id: "123",
* role: "user",
* content: "Hello World",
* });
* };
*
* return (
* <div>
* {messages.map(msg => <div key={msg.id}>{msg.content}</div>)}
* <button onClick={handleSendMessage} disabled={isLoading}>
* Send Message
* </button>
* </div>
* );
* }
* ```
*
* ### Working with Suggestions
*
* ```tsx
* import { useCopilotChatHeadless_c, useCopilotChatSuggestions } from "@copilotkit/react-core";
*
* export function SuggestionExample() {
* const {
* suggestions,
* setSuggestions,
* generateSuggestions,
* isLoadingSuggestions
* } = useCopilotChatHeadless_c();
*
* // Configure AI suggestion generation
* useCopilotChatSuggestions({
* instructions: "Suggest helpful actions based on the current context",
* maxSuggestions: 3
* });
*
* return (
* <div>
* {suggestions.map(suggestion => (
* <button key={suggestion.title}>{suggestion.title}</button>
* ))}
* <button onClick={generateSuggestions} disabled={isLoadingSuggestions}>
* Generate Suggestions
* </button>
* </div>
* );
* }
* ```
*
* ## Return Values
* The following properties are returned from the hook:
*
* <PropertyReference name="messages" type="Message[]">
* The messages currently in the chat in AG-UI format
* </PropertyReference>
*
* <PropertyReference name="sendMessage" type="(message: Message, options?) => Promise<void>">
* Send a new message to the chat and trigger AI response
* </PropertyReference>
*
* <PropertyReference name="setMessages" type="(messages: Message[] | DeprecatedGqlMessage[]) => void">
* Replace all messages in the chat with new array
* </PropertyReference>
*
* <PropertyReference name="deleteMessage" type="(messageId: string) => void">
* Remove a specific message by ID from the chat
* </PropertyReference>
*
* <PropertyReference name="reloadMessages" type="(messageId: string) => Promise<void>">
* Regenerate the response for a specific message by ID
* </PropertyReference>
*
* <PropertyReference name="stopGeneration" type="() => void">
* Stop the current message generation process
* </PropertyReference>
*
* <PropertyReference name="reset" type="() => void">
* Clear all messages and reset chat state completely
* </PropertyReference>
*
* <PropertyReference name="isLoading" type="boolean">
* Whether the chat is currently generating a response
* </PropertyReference>
*
* <PropertyReference name="runChatCompletion" type="() => Promise<Message[]>">
* Manually trigger chat completion for advanced usage
* </PropertyReference>
*
* <PropertyReference name="mcpServers" type="MCPServerConfig[]">
* Array of Model Context Protocol server configurations
* </PropertyReference>
*
* <PropertyReference name="setMcpServers" type="(servers: MCPServerConfig[]) => void">
* Update MCP server configurations for enhanced context
* </PropertyReference>
*
* <PropertyReference name="suggestions" type="SuggestionItem[]">
* Current suggestions array for reading or manual control
* </PropertyReference>
*
* <PropertyReference name="setSuggestions" type="(suggestions: SuggestionItem[]) => void">
* Manually set suggestions for custom workflows
* </PropertyReference>
*
* <PropertyReference name="generateSuggestions" type="() => Promise<void>">
* Trigger AI-powered suggestion generation using configured settings
* </PropertyReference>
*
* <PropertyReference name="resetSuggestions" type="() => void">
* Clear all current suggestions and reset generation state
* </PropertyReference>
*
* <PropertyReference name="isLoadingSuggestions" type="boolean">
* Whether suggestions are currently being generated
* </PropertyReference>
*
* <PropertyReference name="interrupt" type="string | React.ReactElement | null">
* Interrupt content for human-in-the-loop workflows
* </PropertyReference>
*/
const createNonFunctionalReturn = () => ({
visibleMessages: [],
messages: [],
sendMessage: async () => {},
appendMessage: async () => {},
setMessages: () => {},
deleteMessage: () => {},
reloadMessages: async () => {},
stopGeneration: () => {},
reset: () => {},
isLoading: false,
isAvailable: false,
runChatCompletion: async () => [],
mcpServers: [],
setMcpServers: () => {},
suggestions: [],
setSuggestions: () => {},
generateSuggestions: async () => {},
resetSuggestions: () => {},
isLoadingSuggestions: false,
interrupt: null
});
/**
* Enterprise React hook that provides complete chat functionality for fully custom UI implementations.
* Includes all advanced features like direct message access, suggestions array, interrupt handling, and MCP support.
*
* **Requires a publicApiKey** - Sign up for free at https://cloud.copilotkit.ai/
*
* @param options - Configuration options for the chat
* @returns Complete chat interface with all enterprise features
*
* @example
* ```tsx
* const { messages, sendMessage, suggestions, interrupt } = useCopilotChatHeadless_c();
* ```
*/
function useCopilotChatHeadless_c(options = {}) {
const { copilotApiConfig, setBannerError } = useCopilotContext();
const hasPublicApiKey = Boolean(copilotApiConfig.publicApiKey);
const internalResult = useCopilotChatInternal(options);
useEffect(() => {
if (!hasPublicApiKey) {
setBannerError(new CopilotKitError({
message: "You're using useCopilotChatHeadless_c, a premium-only feature, which offers extensive headless chat capabilities. To continue, you'll need to provide a free public license key.",
code: CopilotKitErrorCode.MISSING_PUBLIC_API_KEY_ERROR,
severity: Severity.WARNING,
visibility: ErrorVisibility.BANNER
}));
styledConsole.logCopilotKitPlatformMessage();
} else setBannerError(null);
}, [hasPublicApiKey]);
if (hasPublicApiKey) return internalResult;
return createNonFunctionalReturn();
}
//#endregion
//#region src/hooks/use-frontend-tool.ts
function useFrontendTool(tool, dependencies) {
const { name, description, parameters, render, followUp, available } = tool;
const zodParameters = getZodParameters(parameters);
const renderRef = useRef(render);
useEffect(() => {
renderRef.current = render;
}, [render, ...dependencies ?? []]);
const normalizedRender = useMemo(() => {
if (typeof render === "undefined") return;
return ((args) => {
const currentRender = renderRef.current;
if (typeof currentRender === "undefined") return null;
if (typeof currentRender === "string") return React.createElement(React.Fragment, null, currentRender);
const rendered = currentRender({
...args,
result: typeof args.result === "string" ? parseJson(args.result, args.result) : args.result
});
if (typeof rendered === "string") return React.createElement(React.Fragment, null, rendered);
return rendered ?? null;
});
}, []);
const handlerRef = useRef(tool.handler);
useEffect(() => {
handlerRef.current = tool.handler;
}, [tool.handler, ...dependencies ?? []]);
useFrontendTool$1({
name,
description,
parameters: zodParameters,
handler: tool.handler ? (args) => handlerRef.current?.(args) : void 0,
followUp,
render: normalizedRender,
available: available === void 0 ? void 0 : available !== "disabled"
});
}
//#endregion
//#region src/hooks/use-render-tool-call.ts
function useRenderToolCall(tool, dependencies) {
const { copilotkit } = useCopilotKit();
const hasAddedRef = useRef(false);
useEffect(() => {
const { name, parameters, render } = tool;
const zodParameters = getZodParameters(parameters);
const renderToolCall = name === "*" ? defineToolCallRenderer({
name: "*",
render: ((args) => {
return render({
...args,
result: args.result ? parseJson(args.result, args.result) : args.result
});
})
}) : defineToolCallRenderer({
name,
args: zodParameters,
render: ((args) => {
return render({
...args,
result: args.result ? parseJson(args.result, args.result) : args.result
});
})
});
const existingIndex = copilotkit.renderToolCalls.findIndex((r) => r.name === name);
if (existingIndex !== -1) copilotkit.renderToolCalls.splice(existingIndex, 1);
copilotkit.renderToolCalls.push(renderToolCall);
hasAddedRef.current = true;
return () => {
if (hasAddedRef.current) {
const index = copilotkit.renderToolCalls.findIndex((r) => r.name === name);
if (index !== -1) copilotkit.renderToolCalls.splice(index, 1);
hasAddedRef.current = false;
}
};
}, [tool, ...dependencies ?? []]);
}
//#endregion
//#region src/hooks/use-human-in-the-loop.ts
function useHumanInTheLoop(tool, dependencies) {
const { render, ...toolRest } = tool;
const { name, description, parameters, followUp } = toolRest;
const zodParameters = getZodParameters(parameters);
const renderRef = useRef(null);
useEffect(() => {
renderRef.current = (args) => {
if (typeof render === "string") return React.createElement(React.Fragment, null, render);
if (!render) return null;
const rendered = render((() => {
const mappedArgs = args.args;
switch (args.status) {
case ToolCallStatus.InProgress: return {
args: mappedArgs,
respond: args.respond,
status: args.status,
handler: void 0
};
case ToolCallStatus.Executing: return {
args: mappedArgs,
respond: args.respond,
status: args.status,
handler: () => {}
};
case ToolCallStatus.Complete: return {
args: mappedArgs,
respond: args.respond,
status: args.status,
result: args.result ? parseJson(args.result, args.result) : args.result,
handler: void 0
};
default: throw new CopilotKitError({
code: CopilotKitErrorCode.UNKNOWN,
message: `Invalid tool call status: ${args.status}`
});
}
})());
if (typeof rendered === "string") return React.createElement(React.Fragment, null, rendered);
return rendered ?? null;
};
}, [render, ...dependencies ?? []]);
useHumanInTheLoop$1({
name,
description,
followUp,
parameters: zodParameters,
render: ((args) => renderRef.current?.(args) ?? null)
});
}
//#endregion
//#region src/hooks/use-copilot-action.ts
/**
* Example usage of useCopilotAction with complex parameters:
*
* @example
* useCopilotAction({
* name: "myAction",
* parameters: [
* { name: "arg1", type: "string", enum: ["option1", "option2", "option3"], required: false },
* { name: "arg2", type: "number" },
* {
* name: "arg3",
* type: "object",
* attributes: [
* { name: "nestedArg1", type: "boolean" },
* { name: "xyz", required: false },
* ],
* },
* { name: "arg4", type: "number[]" },
* ],
* handler: ({ arg1, arg2, arg3, arg4 }) => {
* const x = arg3.nestedArg1;
* const z = arg3.xyz;
* console.log(arg1, arg2, arg3);
* },
* });
*
* @example
* // Simple action without parameters
* useCopilotAction({
* name: "myAction",
* handler: () => {
* console.log("No parameters provided.");
* },
* });
*
* @example
* // Interactive action with UI rendering and response handling
* useCopilotAction({
* name: "handleMeeting",
* description: "Handle a meeting by booking or canceling",
* parameters: [
* {
* name: "meeting",
* type: "string",
* description: "The meeting to handle",
* required: true,
* },
* {
* name: "date",
* type: "string",
* description: "The date of the meeting",
* required: true,
* },
* {
* name: "title",
* type: "string",
* description: "The title of the meeting",
* required: true,
* },
* ],
* renderAndWaitForResponse: ({ args, respond, status }) => {
* const { meeting, date, title } = args;
* return (
* <MeetingConfirmationDialog
* meeting={meeting}
* date={date}
* title={title}
* onConfirm={() => respond('meeting confirmed')}
* onCancel={() => respond('meeting canceled')}
* />
* );
* },
* });
*
* @example
* // Catch all action allows you to render actions that are not defined in the frontend
* useCopilotAction({
* name: "*",
* render: ({ name, args, status, result, handler, respond }) => {
* return <div>Rendering action: {name}</div>;
* },
* });
*/
/**
* <img src="https://cdn.copilotkit.ai/docs/copilotkit/images/use-copilot-action/useCopilotAction.gif" width="500" />
* `useCopilotAction` is a React hook that you can use in your application to provide
* custom actions that can be called by the AI. Essentially, it allows the Copilot to
* execute these actions contextually during a chat, based on the user's interactions
* and needs.
*
* Here's how it works:
*
* Use `useCopilotAction` to set up actions that the Copilot can call. To provide
* more context to the Copilot, you can provide it with a `description` (for example to explain
* what the action does, under which conditions it can be called, etc.).
*
* Then you define the parameters of the action, which can be simple, e.g. primitives like strings or numbers,
* or complex, e.g. objects or arrays.
*
* Finally, you provide a `handler` function that receives the parameters and returns a result.
* CopilotKit takes care of automatically inferring the parameter types, so you get type safety
* and autocompletion for free.
*
* To render a custom UI for the action, you can provide a `render()` function. This function
* lets you render a custom component or return a string to display.
*
* ## Usage
*
* ### Simple Usage
*
* ```tsx
* useCopilotAction({
* name: "sayHello",
* description: "Say hello to someone.",
* parameters: [
* {
* name: "name",
* type: "string",
* description: "name of the person to say greet",
* },
* ],
* handler: async ({ name }) => {
* alert(`Hello, ${name}!`);
* },
* });
* ```
*
* ## Generative UI
*
* This hooks enables you to dynamically generate UI elements and render them in the copilot chat. For more information, check out the [Generative UI](/guides/generative-ui) page.
*/
function getActionConfig(action) {
if (action.name === "*") return {
type: "render",
action
};
if ("renderAndWaitForResponse" in action || "renderAndWait" in action) {
let render = action.render;
if (!render && "renderAndWaitForResponse" in action) render = action.renderAndWaitForResponse;
if (!render && "renderAndWait" in action) render = action.renderAndWait;
return {
type: "hitl",
action: {
...action,
render
}
};
}
if ("available" in action) {
if (action.available === "enabled" || action.available === "remote") return {
type: "frontend",
action
};
if (action.available === "frontend" || action.available === "disabled") return {
type: "render",
action
};
}
if ("handler" in action) return {
type: "frontend",
action
};
throw new Error("Invalid action configuration");
}
/**
* useCopilotAction is a legacy hook maintained for backwards compatibility.
*
* To avoid violating React's Rules of Hooks (which prohibit conditional hook calls),
* we use a registration pattern:
* 1. This hook registers the action configuration with the CopilotContext
* 2. A renderer component in CopilotKit actually renders the appropriate hook wrapper
* 3. React properly manages hook state since components are rendered, not conditionally called
*
* This allows action types to change between renders without corrupting React's hook state.
*/
function useCopilotAction(action, dependencies) {
const [initialActionConfig] = useState(getActionConfig(action));
const currentActionConfig = getActionConfig(action);
/**
* Calling hooks conditionally violates React's Rules of Hooks. This rule exists because
* React maintains the call stack for hooks like useEffect or useState, and conditionally
* calling a hook would result in inconsistent call stacks between renders.
*
* Unfortunately, useCopilotAction _has_ to conditionally call a hook based on the
* supplied parameters. In order to avoid breaking React's call stack tracking, while
* breaking the Rule of Hooks, we use a ref to store the initial action configuration
* and throw an error if the _configuration_ changes such that we would call a different hook.
*/
if (initialActionConfig.type !== currentActionConfig.type) throw new Error("Action configuration changed between renders");
switch (currentActionConfig.type) {
case "render": return useRenderToolCall(currentActionConfig.action, dependencies);
case "hitl": return useHumanInTheLoop(currentActionConfig.action, dependencies);
case "frontend": return useFrontendTool(currentActionConfig.action, dependencies);
default: throw new Error("Invalid action configuration");
}
}
//#endregion
//#region src/hooks/use-coagent-state-render.ts
/**
* The useCoAgentStateRender hook allows you to render UI or text based components on a Agentic Copilot's state in the chat.
* This is particularly useful for showing intermediate state or progress during Agentic Copilot operations.
*
* ## Usage
*
* ### Simple Usage
*
* ```tsx
* import { useCoAgentStateRender } from "@copilotkit/react-core";
*
* type YourAgentState = {
* agent_state_property: string;
* }
*
* useCoAgentStateRender<YourAgentState>({
* name: "basic_agent",
* nodeName: "optionally_specify_a_specific_node",
* render: ({ status, state, nodeName }) => {
* return (
* <YourComponent
* agentStateProperty={state.agent_state_property}
* status={status}
* nodeName={nodeName}
* />
* );
* },
* });
* ```
*
* This allows for you to render UI components or text based on what is happening within the agent.
*
* ### Example
* A great example of this is in our Perplexity Clone where we render the progress of an agent's internet search as it is happening.
* You can play around with it below or learn how to build it with its [demo](/coagents/videos/perplexity-clone).
*
* <Callout type="info">
* This example is hosted on Vercel and may take a few seconds to load.
* </Callout>
*
* <iframe src="https://examples-coagents-ai-researcher-ui.vercel.app/" className="w-full rounded-lg border h-[700px] my-4" />
*/
/**
* This hook is used to render agent state with custom UI components or text. This is particularly
* useful for showing intermediate state or progress during Agentic Copilot operations.
* To get started using rendering intermediate state through this hook, checkout the documentation.
*
* https://docs.copilotkit.ai/langgraph-python/shared-state/predictive-state-updates
*/
function useCoAgentStateRender(action, dependencies) {
const { chatComponentsCache, availableAgents } = useContext(CopilotContext);
const { setCoAgentStateRender, removeCoAgentStateRender, coAgentStateRenders } = useCoAgentStateRenders();
const idRef = useRef(randomId());
const { setBannerError, addToast } = useToast();
useEffect(() => {
if (availableAgents?.length && !availableAgents.some((a) => a.name === action.name)) {
`${action.name}`;
setBannerError(new CopilotKitAgentDiscoveryError({
agentName: action.name,
availableAgents: availableAgents.map((a) => ({
name: a.name,
id: a.id
}))
}));
}
}, [availableAgents]);
const key = `${action.name}-${action.nodeName || "global"}`;
if (dependencies === void 0) {
if (coAgentStateRenders[idRef.current]) {
coAgentStateRenders[idRef.current].handler = action.handler;
if (typeof action.render === "function") {
if (chatComponentsCache.current !== null) chatComponentsCache.current.coAgentStateRenders[key] = action.render;
}
}
}
useEffect(() => {
const currentId = idRef.current;
if (Object.entries(coAgentStateRenders).some(([id, otherAction]) => {
if (id === currentId) return false;
if (otherAction.name !== action.name) return false;
const hasNodeName = !!action.nodeName;
const hasOtherNodeName = !!otherAction.nodeName;
if (!hasNodeName && !hasOtherNodeName) return true;
if (hasNodeName !== hasOtherNodeName) return false;
return action.nodeName === otherAction.nodeName;
})) addToast({
type: "warning",
message: action.nodeName ? `Found multiple state renders for agent ${action.name} and node ${action.nodeName}. State renders might get overridden` : `Found multiple state renders for agent ${action.name}. State renders might get overridden`,
id: `dup-action-${action.name}`
});
}, [coAgentStateRenders]);
useEffect(() => {
setCoAgentStateRender(idRef.current, action);
if (chatComponentsCache.current !== null && action.render !== void 0) chatComponentsCache.current.coAgentStateRenders[key] = action.render;
return () => {
removeCoAgentStateRender(idRef.current);
};
}, [
setCoAgentStateRender,
removeCoAgentStateRender,
action.name,
typeof action.render === "string" ? action.render : void 0,
...dependencies || []
]);
}
//#endregion
//#region src/hooks/use-make-copilot-document-readable.ts
/**
* Makes a document readable by Copilot.
* @param document The document to make readable.
* @param categories The categories to associate with the document.
* @param dependencies The dependencies to use for the effect.
* @returns The id of the document.
*/
function useMakeCopilotDocumentReadable(document, categories, dependencies = []) {
const { addDocumentContext, removeDocumentContext } = useCopilotContext();
const idRef = useRef(void 0);
useEffect(() => {
const id = addDocumentContext(document, categories);
idRef.current = id;
return () => {
removeDocumentContext(id);
};
}, [
addDocumentContext,
removeDocumentContext,
...dependencies
]);
return idRef.current;
}
//#endregion
//#region src/hooks/use-copilot-readable.ts
/**
* `useCopilotReadable` is a React hook that provides app-state and other information
* to the Copilot. Optionally, the hook can also handle hierarchical state within your
* application, passing these parent-child relationships to the Copilot.
*
* ## Usage
*
* ### Simple Usage
*
* In its most basic usage, useCopilotReadable accepts a single string argument
* representing any piece of app state, making it available for the Copilot to use
* as context when responding to user input.
*
* ```tsx
* import { useCopilotReadable } from "@copilotkit/react-core";
*
* export function MyComponent() {
* const [employees, setEmployees] = useState([]);
*
* useCopilotReadable({
* description: "The list of employees",
* value: employees,
* });
* }
* ```
*
* ### Nested Components
*
* Optionally, you can maintain the hierarchical structure of information by passing
* `parentId`. This allows you to use `useCopilotReadable` in nested components:
*
* ```tsx /employeeContextId/1 {17,23}
* import { useCopilotReadable } from "@copilotkit/react-core";
*
* function Employee(props: EmployeeProps) {
* const { employeeName, workProfile, metadata } = props;
*
* // propagate any information to copilot
* const employeeContextId = useCopilotReadable({
* description: "Employee name",
* value: employeeName
* });
*
* // Pass a parentID to maintain a hierarchical structure.
* // Especially useful with child React components, list elements, etc.
* useCopilotReadable({
* description: "Work profile",
* value: workProfile.description(),
* parentId: employeeContextId
* });
*
* useCopilotReadable({
* description: "Employee metadata",
* value: metadata.description(),
* parentId: employeeContextId
* });
*
* return (
* // Render as usual...
* );
* }
* ```
*/
/**
* Adds the given information to the Copilot context to make it readable by Copilot.
*/
function useCopilotReadable({ description, value, convert, available }, dependencies) {
const { copilotkit } = useCopilotKit();
const ctxIdRef = useRef(void 0);
useEffect(() => {
if (!copilotkit) return;
const found = Object.entries(copilotkit.context).find(([id, ctxItem]) => {
return JSON.stringify({
description,
value
}) == JSON.stringify(ctxItem);
});
if (found) {
ctxIdRef.current = found[0];
if (available === "disabled") copilotkit.removeContext(ctxIdRef.current);
return;
}
if (!found && available === "disabled") return;
ctxIdRef.current = copilotkit.addContext({
description,
value: (convert ?? JSON.stringify)(value)
});
return () => {
if (!ctxIdRef.current) return;
copilotkit.removeContext(ctxIdRef.current);
};
}, [
description,
value,
convert
]);
return ctxIdRef.current;
}
//#endregion
//#region src/hooks/use-agent-nodename.ts
function useAgentNodeName(agentName) {
const { agent } = useAgent({ agentId: agentName });
const nodeNameRef = useRef("start");
useEffect(() => {
if (!agent) return;
const subscription = agent.subscribe({
onStepStartedEvent: ({ event }) => {
nodeNameRef.current = event.stepName;
},
onRunStartedEvent: () => {
nodeNameRef.current = "start";
},
onRunFinishedEvent: () => {
nodeNameRef.current = "end";
},
onRunErrorEvent: () => {
nodeNameRef.current = "end";
}
});
return () => {
subscription.unsubscribe();
};
}, [agent]);
return nodeNameRef.current;
}
//#endregion
//#region src/hooks/use-coagent.ts
/**
* <Callout type="info">
* Usage of this hook assumes some additional setup in your application, for more information
* on that see the CoAgents <span className="text-blue-500">[getting started guide](/coagents/quickstart/langgraph)</span>.
* </Callout>
* <Frame className="my-12">
* <img
* src="https://cdn.copilotkit.ai/docs/copilotkit/images/coagents/SharedStateCoAgents.gif"
* alt="CoAgents demonstration"
* className="w-auto"
* />
* </Frame>
*
* This hook is used to integrate an agent into your application. With its use, you can
* render and update the state of an agent, allowing for a dynamic and interactive experience.
* We call these shared state experiences agentic copilots, or CoAgents for short.
*
* ## Usage
*
* ### Simple Usage
*
* ```tsx
* import { useCoAgent } from "@copilotkit/react-core";
*
* type AgentState = {
* count: number;
* }
*
* const agent = useCoAgent<AgentState>({
* name: "my-agent",
* initialState: {
* count: 0,
* },
* });
*
* ```
*
* `useCoAgent` returns an object with the following properties:
*
* ```tsx
* const {
* name, // The name of the agent currently being used.
* nodeName, // The name of the current LangGraph node.
* state, // The current state of the agent.
* setState, // A function to update the state of the agent.
* running, // A boolean indicating if the agent is currently running.
* start, // A function to start the agent.
* stop, // A function to stop the agent.
* run, // A function to re-run the agent. Takes a HintFunction to inform the agent why it is being re-run.
* } = agent;
* ```
*
* Finally we can leverage these properties to create reactive experiences with the agent!
*
* ```tsx
* const { state, setState } = useCoAgent<AgentState>({
* name: "my-agent",
* initialState: {
* count: 0,
* },
* });
*
* return (
* <div>
* <p>Count: {state.count}</p>
* <button onClick={() => setState({ count: state.count + 1 })}>Increment</button>
* </div>
* );
* ```
*
* This reactivity is bidirectional, meaning that changes to the state from the agent will be reflected in the UI and vice versa.
*
* ## Parameters
* <PropertyReference name="options" type="UseCoagentOptions<T>" required>
* The options to use when creating the coagent.
* <PropertyReference name="name" type="string" required>
* The name of the agent to use.
* </PropertyReference>
* <PropertyReference name="initialState" type="T | any">
* The initial state of the agent.
* </PropertyReference>
* <PropertyReference name="state" type="T | any">
* State to manage externally if you are using this hook with external state management.
* </PropertyReference>
* <PropertyReference name="setState" type="(newState: T | ((prevState: T | undefined) => T)) => void">
* A function to update the state of the agent if you are using this hook with external state management.
* </PropertyReference>
* </PropertyReference>
*/
/**
* This hook is used to integrate an agent into your application. With its use, you can
* render and update the state of the agent, allowing for a dynamic and interactive experience.
* We call these shared state experiences "agentic copilots". To get started using agentic copilots, which
* we refer to as CoAgents, checkout the documentation at https://docs.copilotkit.ai/langgraph-python/quickstart.
*/
function useCoAgent(options) {
const { agent } = useAgent({ agentId: options.name });
const { copilotkit } = useCopilotKit();
const nodeName = useAgentNodeName(options.name);
const handleStateUpdate = useCallback((newState) => {
if (!agent) return;
if (typeof newState === "function") {
const updater = newState;
agent.setState(updater(agent.state));
} else agent.setState({
...agent.state,
...newState
});
}, [agent?.state, agent?.setState]);
useEffect(() => {
if (!options.config && !options.configurable) return;
let config = options.config ?? {};
if (options.configurable) config = {
...config,
configurable: {
...options.configurable,
...config.configurable
}
};
copilotkit.setProperties(config);
}, [options.config, options.configurable]);
useEffect(() => {
if (agent?.state && isExternalStateManagement(options) && JSON.stringify(options.state) !== JSON.stringify(agent.state)) handleStateUpdate(options.state);
}, [
agent,
useMemo(() => isExternalStateManagement(options) ? JSON.stringify(options.state) : void 0, [isExternalStateManagement(options) ? JSON.stringify(options.state) : void 0]),
handleStateUpdate
]);
const hasStateValues = useCallback((value) => {
return Boolean(value && Object.keys(value).length);
}, []);
const initialStateRef = useRef(isExternalStateManagement(options) ? options.state : "initialState" in options ? options.initialState : void 0);
useEffect(() => {
if (isExternalStateManagement(options)) initialStateRef.current = options.state;
else if ("initialState" in options) initialStateRef.current = options.initialState;
}, [isExternalStateManagement(options) ? JSON.stringify(options.state) : "initialState" in options ? JSON.stringify(options.initialState) : void 0]);
useEffect(() => {
if (!agent) return;
const subscription = agent.subscribe({
onStateChanged: (args) => {
if (isExternalStateManagement(options)) options.setState(args.state);
},
onRunInitialized: (args) => {
if (hasStateValues(args.state)) {
handleStateUpdate(args.state);
return;
}
if (hasStateValues(agent.state)) return;
if (initialStateRef.current !== void 0) handleStateUpdate(initialStateRef.current);
}
});
return () => {
subscription.unsubscribe();
};
}, [
agent,
handleStateUpdate,
hasStateValues
]);
return useMemo(() => {
if (!agent) {
const noop = () => {};
const noopAsync = async () => {};
const initialState = ("state" in options && options.state) ?? ("initialState" in options && options.initialState) ?? {};
return {
name: options.name,
nodeName,
threadId: void 0,
running: false,
state: initialState,
setState: noop,
start: noop,
stop: noop,
run: noopAsync
};
}
return {
name: agent?.agentId ?? options.name,
nodeName,
threadId: agent.threadId,
running: agent.isRunning,
state: agent.state,
setState: handleStateUpdate,
start: agent.runAgent,
stop: agent.abortRun,
run: agent.runAgent
};
}, [
agent?.state,
agent?.runAgent,
agent?.abortRun,
agent?.runAgent,
agent?.threadId,
agent?.isRunning,
agent?.agentId,
handleStateUpdate,
options.name
]);
}
const isExternalStateManagement = (options) => {
return "state" in options && "setState" in options;
};
//#endregion
//#region src/hooks/use-copilot-runtime-client.ts
const useCopilotRuntimeClient = (options) => {
const { setBannerError } = useToast();
const { showDevConsole, onError, ...runtimeOptions } = options;
const lastStructuredErrorRef = useRef(null);
const traceUIError = async (error, originalError) => {
try {
await onError({
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 : void 0,
stackTrace: originalError instanceof Error ? originalError.stack : void 0
}
},
error
});
} catch (error) {
console.error("Error in onError handler:", error);
}
};
return useMemo(() => {
return new CopilotRuntimeClient({
...runtimeOptions,
handleGQLErrors: (error) => {
if (error.graphQLErrors?.length) {
const graphQLErrors = error.graphQLErrors;
const routeError = (gqlError) => {
if (gqlError.extensions?.visibility === ErrorVisibility.SILENT) {
console.error("CopilotKit Silent Error:", gqlError.message);
return;
}
const now = Date.now();
const errorMessage = gqlError.message;
if (lastStructuredErrorRef.current && lastStructuredErrorRef.current.message === errorMessage && now - lastStructuredErrorRef.current.timestamp < 150) return;
lastStructuredErrorRef.current = {
message: errorMessage,
timestamp: now
};
const ckError = createStructuredError(gqlError);
if (ckError) {
setBannerError(ckError);
traceUIError(ckError, gqlError);
} else {
const fallbackError = new CopilotKitError({
message: gqlError.message,
code: CopilotKitErrorCode.UNKNOWN
});
setBannerError(fallbackError);
traceUIError(fallbackError, gqlError);
}
};
graphQLErrors.forEach(routeError);
} else {
const fallbackError = new CopilotKitError({
message: error?.message || String(error),
code: CopilotKitErrorCode.UNKNOWN
});
setBannerError(fallbackError);
traceUIError(fallbackError, error);
}
},
handleGQLWarning: (message) => {
console.warn(message);
setBannerError(new CopilotKitError({
message,
code: CopilotKitErrorCode.UNKNOWN
}));
}
});
}, [
runtimeOptions,
setBannerError,
onError
]);
};
function createStructuredError(gqlError) {
const extensions = gqlError.extensions;
const originalError = extensions?.originalError;
const message = originalError?.messag