@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,186 lines (1,170 loc) • 40.1 kB
JavaScript
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
//#region \0rolldown/runtime.js
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
key = keys[i];
if (!__hasOwnProp.call(to, key) && key !== except) {
__defProp(to, key, {
get: ((k) => from[k]).bind(null, key),
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
});
}
}
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
value: mod,
enumerable: true
}) : target, mod));
//#endregion
let _copilotkit_react_core_v2_context = require("@copilotkit/react-core/v2/context");
let react = require("react");
react = __toESM(react);
let _copilotkit_shared = require("@copilotkit/shared");
let tailwind_merge = require("tailwind-merge");
let react_jsx_runtime = require("react/jsx-runtime");
let _ag_ui_client = require("@ag-ui/client");
let _copilotkit_core = require("@copilotkit/core");
let zod = require("zod");
//#region src/v2/lib/slots.tsx
/**
* Shallow equality comparison for objects.
*/
function shallowEqual(obj1, obj2) {
const keys1 = Object.keys(obj1);
const keys2 = Object.keys(obj2);
if (keys1.length !== keys2.length) return false;
for (const key of keys1) if (obj1[key] !== obj2[key]) return false;
return true;
}
/**
* Returns true only for plain JS objects (`{}`), excluding arrays, Dates,
* class instances, and other exotic objects that happen to have typeof "object".
*/
function isPlainObject(obj) {
return obj !== null && typeof obj === "object" && Object.prototype.toString.call(obj) === "[object Object]";
}
/**
* Returns the same reference as long as the value is shallowly equal to the
* previous render's value.
*
* - Identical references bail out immediately (O(1)).
* - Plain objects ({}) are shallow-compared key-by-key.
* - Arrays, Dates, class instances, functions, and primitives are compared by
* reference only — shallowEqual is never called on non-plain objects, which
* avoids incorrect equality for e.g. [1,2] vs [1,2] (different arrays).
*
* Typical use: stabilize inline slot props so MemoizedSlotWrapper's shallow
* equality check isn't defeated by a new object reference on every render.
*/
function useShallowStableRef(value) {
const ref = (0, react.useRef)(value);
if (ref.current === value) return ref.current;
if (isPlainObject(ref.current) && isPlainObject(value)) {
if (shallowEqual(ref.current, value)) return ref.current;
}
ref.current = value;
return ref.current;
}
/**
* Check if a value is a React component type (function, class, forwardRef, memo, etc.)
*/
function isReactComponentType(value) {
if (typeof value === "function") return true;
if (value && typeof value === "object" && "$$typeof" in value && !react.default.isValidElement(value)) return true;
return false;
}
/**
* Internal function to render a slot value as a React element (non-memoized).
*/
function renderSlotElement(slot, DefaultComponent, props) {
if (typeof slot === "string") {
const existingClassName = props.className;
return react.default.createElement(DefaultComponent, {
...props,
className: (0, tailwind_merge.twMerge)(existingClassName, slot)
});
}
if (isReactComponentType(slot)) return react.default.createElement(slot, props);
if (slot && typeof slot === "object" && !react.default.isValidElement(slot)) return react.default.createElement(DefaultComponent, {
...props,
...slot
});
return react.default.createElement(DefaultComponent, props);
}
/**
* Internal memoized wrapper component for renderSlot.
* Uses forwardRef to support ref forwarding.
*/
const MemoizedSlotWrapper = react.default.memo(react.default.forwardRef(function MemoizedSlotWrapper(props, ref) {
const { $slot, $component, ...rest } = props;
return renderSlotElement($slot, $component, ref !== null ? {
...rest,
ref
} : rest);
}), (prev, next) => {
if (prev.$slot !== next.$slot) return false;
if (prev.$component !== next.$component) return false;
const { $slot: _ps, $component: _pc, ...prevRest } = prev;
const { $slot: _ns, $component: _nc, ...nextRest } = next;
return shallowEqual(prevRest, nextRest);
});
//#endregion
//#region src/v2/providers/CopilotChatConfigurationProvider.tsx
const CopilotChatDefaultLabels = {
chatInputPlaceholder: "Type a message...",
chatInputToolbarStartTranscribeButtonLabel: "Transcribe",
chatInputToolbarCancelTranscribeButtonLabel: "Cancel",
chatInputToolbarFinishTranscribeButtonLabel: "Finish",
chatInputToolbarAddButtonLabel: "Add attachments",
chatInputToolbarToolsButtonLabel: "Tools",
assistantMessageToolbarCopyCodeLabel: "Copy",
assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
assistantMessageToolbarCopyMessageLabel: "Copy",
assistantMessageToolbarThumbsUpLabel: "Good response",
assistantMessageToolbarThumbsDownLabel: "Bad response",
assistantMessageToolbarReadAloudLabel: "Read aloud",
assistantMessageToolbarRegenerateLabel: "Regenerate",
userMessageToolbarCopyMessageLabel: "Copy",
userMessageToolbarEditMessageLabel: "Edit",
chatDisclaimerText: "AI can make mistakes. Please verify important information.",
chatToggleOpenLabel: "Open chat",
chatToggleCloseLabel: "Close chat",
modalHeaderTitle: "CopilotKit Chat",
welcomeMessageText: "How can I help you today?"
};
const CopilotChatConfiguration = (0, react.createContext)(null);
const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
const stableLabels = useShallowStableRef(labels);
const mergedLabels = (0, react.useMemo)(() => ({
...CopilotChatDefaultLabels,
...parentConfig?.labels,
...stableLabels
}), [stableLabels, parentConfig?.labels]);
const resolvedAgentId = agentId ?? parentConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
const resolvedThreadId = (0, react.useMemo)(() => {
if (threadId) return threadId;
if (parentConfig?.threadId) return parentConfig.threadId;
return (0, _copilotkit_shared.randomUUID)();
}, [threadId, parentConfig?.threadId]);
const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId;
const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen ?? true);
const hasExplicitDefault = isModalDefaultOpen !== void 0;
const setAndSync = (0, react.useCallback)((open) => {
setInternalModalOpen(open);
parentConfig?.setModalOpen(open);
}, [parentConfig?.setModalOpen]);
const isMounted = (0, react.useRef)(false);
(0, react.useEffect)(() => {
if (!hasExplicitDefault) return;
if (!isMounted.current) {
isMounted.current = true;
return;
}
if (parentConfig?.isModalOpen === void 0) return;
setInternalModalOpen(parentConfig.isModalOpen);
}, [parentConfig?.isModalOpen, hasExplicitDefault]);
const resolvedIsModalOpen = hasExplicitDefault ? internalModalOpen : parentConfig?.isModalOpen ?? internalModalOpen;
const resolvedSetModalOpen = hasExplicitDefault ? setAndSync : parentConfig?.setModalOpen ?? setInternalModalOpen;
const configurationValue = (0, react.useMemo)(() => ({
labels: mergedLabels,
agentId: resolvedAgentId,
threadId: resolvedThreadId,
hasExplicitThreadId: resolvedHasExplicitThreadId,
isModalOpen: resolvedIsModalOpen,
setModalOpen: resolvedSetModalOpen
}), [
mergedLabels,
resolvedAgentId,
resolvedThreadId,
resolvedHasExplicitThreadId,
resolvedIsModalOpen,
resolvedSetModalOpen
]);
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfiguration.Provider, {
value: configurationValue,
children
});
};
const useCopilotChatConfiguration = () => {
return (0, react.useContext)(CopilotChatConfiguration);
};
//#endregion
//#region src/v2/hooks/use-agent.tsx
let UseAgentUpdate = /* @__PURE__ */ function(UseAgentUpdate) {
UseAgentUpdate["OnMessagesChanged"] = "OnMessagesChanged";
UseAgentUpdate["OnStateChanged"] = "OnStateChanged";
UseAgentUpdate["OnRunStatusChanged"] = "OnRunStatusChanged";
return UseAgentUpdate;
}({});
const ALL_UPDATES = [
UseAgentUpdate.OnMessagesChanged,
UseAgentUpdate.OnStateChanged,
UseAgentUpdate.OnRunStatusChanged
];
function useAgent({ agentId, updates, throttleMs } = {}) {
agentId ??= _copilotkit_shared.DEFAULT_AGENT_ID;
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const providerThrottleMs = copilotkit.defaultThrottleMs;
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
const updateFlags = (0, react.useMemo)(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
const agent = (0, react.useMemo)(() => {
const existing = copilotkit.getAgent(agentId);
if (existing) {
provisionalAgentCache.current.delete(agentId);
return existing;
}
const isRuntimeConfigured = copilotkit.runtimeUrl !== void 0;
const status = copilotkit.runtimeConnectionStatus;
if (isRuntimeConfigured && (status === _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
const cached = provisionalAgentCache.current.get(agentId);
if (cached) {
cached.headers = { ...copilotkit.headers };
return cached;
}
const provisional = new _copilotkit_core.ProxiedCopilotRuntimeAgent({
runtimeUrl: copilotkit.runtimeUrl,
agentId,
transport: copilotkit.runtimeTransport,
runtimeMode: "pending"
});
provisional.headers = { ...copilotkit.headers };
provisionalAgentCache.current.set(agentId, provisional);
return provisional;
}
if (isRuntimeConfigured && status === _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Error) {
const cached = provisionalAgentCache.current.get(agentId);
if (cached) {
cached.headers = { ...copilotkit.headers };
return cached;
}
const provisional = new _copilotkit_core.ProxiedCopilotRuntimeAgent({
runtimeUrl: copilotkit.runtimeUrl,
agentId,
transport: copilotkit.runtimeTransport,
runtimeMode: "pending"
});
provisional.headers = { ...copilotkit.headers };
provisionalAgentCache.current.set(agentId, provisional);
return provisional;
}
const knownAgents = Object.keys(copilotkit.agents ?? {});
const runtimePart = isRuntimeConfigured ? `runtimeUrl=${copilotkit.runtimeUrl}` : "no runtimeUrl";
throw new Error(`useAgent: Agent '${agentId}' not found after runtime sync (${runtimePart}). ` + (knownAgents.length ? `Known agents: [${knownAgents.join(", ")}]` : "No agents registered.") + " Verify your runtime /info and/or agents__unsafe_dev_only.");
}, [
agentId,
copilotkit.agents,
copilotkit.runtimeConnectionStatus,
copilotkit.runtimeUrl,
copilotkit.runtimeTransport,
JSON.stringify(copilotkit.headers)
]);
(0, react.useEffect)(() => {
if (updateFlags.length === 0) return;
let active = true;
const handlers = {};
let batchScheduled = false;
const batchedForceUpdate = () => {
if (!active) return;
if (!batchScheduled) {
batchScheduled = true;
queueMicrotask(() => {
batchScheduled = false;
if (active) forceUpdate();
});
}
};
if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = batchedForceUpdate;
if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
handlers.onRunInitialized = batchedForceUpdate;
handlers.onRunFinalized = batchedForceUpdate;
handlers.onRunFailed = batchedForceUpdate;
handlers.onRunErrorEvent = batchedForceUpdate;
}
const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
return () => {
active = false;
subscription.unsubscribe();
};
}, [
agent,
forceUpdate,
throttleMs,
providerThrottleMs,
updateFlags
]);
(0, react.useEffect)(() => {
if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...copilotkit.headers };
}, [agent, JSON.stringify(copilotkit.headers)]);
const chatConfig = useCopilotChatConfiguration();
const configThreadId = chatConfig?.threadId;
const configHasExplicitThreadId = chatConfig?.hasExplicitThreadId;
(0, react.useEffect)(() => {
if (!configHasExplicitThreadId || !configThreadId) return;
agent.threadId = configThreadId;
}, [
agent,
configThreadId,
configHasExplicitThreadId
]);
return { agent };
}
//#endregion
//#region src/v2/hooks/use-frontend-tool.tsx
const EMPTY_DEPS$1 = [];
function useFrontendTool(tool, deps) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const extraDeps = deps ?? EMPTY_DEPS$1;
(0, react.useEffect)(() => {
const name = tool.name;
if (copilotkit.getTool({
toolName: name,
agentId: tool.agentId
})) {
console.warn(`Tool '${name}' already exists for agent '${tool.agentId || "global"}'. Overriding with latest registration.`);
copilotkit.removeTool(name, tool.agentId);
}
copilotkit.addTool(tool);
if (tool.render) copilotkit.addHookRenderToolCall({
name,
args: tool.parameters,
agentId: tool.agentId,
render: tool.render
});
return () => {
copilotkit.removeTool(name, tool.agentId);
};
}, [
tool.name,
tool.available,
copilotkit,
JSON.stringify(extraDeps)
]);
}
//#endregion
//#region src/v2/hooks/use-component.tsx
/**
* Registers a React component as a frontend tool renderer in chat.
*
* This hook is a convenience wrapper around `useFrontendTool` that:
* - builds a model-facing tool description,
* - forwards optional schema parameters (any Standard Schema V1 compatible library),
* - renders your component with tool call parameters.
*
* Use this when you want to display a typed visual component for a tool call
* without manually wiring a full frontend tool object.
*
* When `parameters` is provided, render props are inferred from the schema.
* When omitted, the render component may accept any props.
*
* @typeParam TSchema - Schema describing tool parameters, or `undefined` when no schema is given.
* @param config - Tool registration config.
* @param deps - Optional dependencies to refresh registration (same semantics as `useEffect`).
*
* @example
* ```tsx
* // Without parameters — render accepts any props
* useComponent({
* name: "showGreeting",
* render: ({ message }: { message: string }) => <div>{message}</div>,
* });
* ```
*
* @example
* ```tsx
* // With parameters — render props inferred from schema
* useComponent({
* name: "showWeatherCard",
* parameters: z.object({ city: z.string() }),
* render: ({ city }) => <div>{city}</div>,
* });
* ```
*
* @example
* ```tsx
* useComponent(
* {
* name: "renderProfile",
* parameters: z.object({ userId: z.string() }),
* render: ProfileCard,
* agentId: "support-agent",
* },
* [selectedAgentId],
* );
* ```
*/
function useComponent(config, deps) {
const prefix = `Use this tool to display the "${config.name}" component in the chat. This tool renders a visual UI component for the user.`;
const fullDescription = config.description ? `${prefix}\n\n${config.description}` : prefix;
useFrontendTool({
name: config.name,
description: fullDescription,
parameters: config.parameters,
render: ({ args }) => {
const Component = config.render;
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...args });
},
agentId: config.agentId,
followUp: config.followUp
}, deps);
}
//#endregion
//#region src/v2/hooks/use-human-in-the-loop.tsx
function useHumanInTheLoop(tool, deps) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const resolvePromiseRef = (0, react.useRef)(null);
const respond = (0, react.useCallback)(async (result) => {
if (resolvePromiseRef.current) {
resolvePromiseRef.current(result);
resolvePromiseRef.current = null;
}
}, []);
const handler = (0, react.useCallback)(async () => {
return new Promise((resolve) => {
resolvePromiseRef.current = resolve;
});
}, []);
const RenderComponent = (0, react.useCallback)((props) => {
const ToolComponent = tool.render;
if (props.status === "inProgress") {
const enhancedProps = {
...props,
name: tool.name,
description: tool.description || "",
respond: void 0
};
return react.default.createElement(ToolComponent, enhancedProps);
} else if (props.status === "executing") {
const enhancedProps = {
...props,
name: tool.name,
description: tool.description || "",
respond
};
return react.default.createElement(ToolComponent, enhancedProps);
} else if (props.status === "complete") {
const enhancedProps = {
...props,
name: tool.name,
description: tool.description || "",
respond: void 0
};
return react.default.createElement(ToolComponent, enhancedProps);
}
return react.default.createElement(ToolComponent, props);
}, [
tool.render,
tool.name,
tool.description,
respond
]);
useFrontendTool({
...tool,
handler,
render: RenderComponent
}, deps);
(0, react.useEffect)(() => {
return () => {
copilotkit.removeHookRenderToolCall(tool.name, tool.agentId);
};
}, [
copilotkit,
tool.name,
tool.agentId
]);
}
//#endregion
//#region src/v2/hooks/use-interrupt.tsx
const INTERRUPT_EVENT_NAME = "on_interrupt";
function isPromiseLike(value) {
return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
}
/**
* Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.
*
* The hook listens to custom events on the active agent, stores interrupt payloads per run,
* and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume
* execution with user-provided data.
*
* - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.
* - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.
*
* `event.value` is typed as `any` since the interrupt payload shape depends on your agent.
* Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.
*
* @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.
* @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).
* @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).
* Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always
* either the handler's resolved return value or `null` (including when no handler is provided,
* when filtering skips the interrupt, or when handler execution fails).
*
* @example
* ```tsx
* import { useInterrupt } from "@copilotkit/react-core/v2";
*
* function InterruptUI() {
* useInterrupt({
* render: ({ event, resolve }) => (
* <div>
* <p>{event.value.question}</p>
* <button onClick={() => resolve({ approved: true })}>Approve</button>
* <button onClick={() => resolve({ approved: false })}>Reject</button>
* </div>
* ),
* });
*
* return null;
* }
* ```
*
* @example
* ```tsx
* import { useInterrupt } from "@copilotkit/react-core/v2";
*
* function CustomPanel() {
* const interruptElement = useInterrupt({
* renderInChat: false,
* enabled: (event) => event.value.startsWith("approval:"),
* handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
* render: ({ event, result, resolve }) => (
* <aside>
* <strong>{result?.label ?? ""}</strong>
* <button onClick={() => resolve({ value: event.value })}>Continue</button>
* </aside>
* ),
* });
*
* return <>{interruptElement}</>;
* }
* ```
*/
function useInterrupt(config) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const { agent } = useAgent({ agentId: config.agentId });
const [pendingEvent, setPendingEvent] = (0, react.useState)(null);
const pendingEventRef = (0, react.useRef)(pendingEvent);
pendingEventRef.current = pendingEvent;
const [handlerResult, setHandlerResult] = (0, react.useState)(null);
(0, react.useEffect)(() => {
let localInterrupt = null;
const subscription = agent.subscribe({
onCustomEvent: ({ event }) => {
if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
name: event.name,
value: event.value
};
},
onRunStartedEvent: () => {
localInterrupt = null;
setPendingEvent(null);
},
onRunFinalized: () => {
if (localInterrupt) {
setPendingEvent(localInterrupt);
localInterrupt = null;
}
},
onRunFailed: () => {
localInterrupt = null;
}
});
return () => subscription.unsubscribe();
}, [agent]);
const resolve = (0, react.useCallback)((response) => {
copilotkit.runAgent({
agent,
forwardedProps: { command: {
resume: response,
interruptEvent: pendingEventRef.current?.value
} }
});
}, [agent, copilotkit]);
const renderRef = (0, react.useRef)(config.render);
renderRef.current = config.render;
const enabledRef = (0, react.useRef)(config.enabled);
enabledRef.current = config.enabled;
const handlerRef = (0, react.useRef)(config.handler);
handlerRef.current = config.handler;
const resolveRef = (0, react.useRef)(resolve);
resolveRef.current = resolve;
const isEnabled = (event) => {
const predicate = enabledRef.current;
if (!predicate) return true;
try {
return predicate(event);
} catch (err) {
console.error("[CopilotKit] useInterrupt enabled predicate threw; treating interrupt as disabled:", err);
return false;
}
};
(0, react.useEffect)(() => {
if (!pendingEvent) {
setHandlerResult(null);
return;
}
if (!isEnabled(pendingEvent)) {
setHandlerResult(null);
return;
}
const handler = handlerRef.current;
if (!handler) {
setHandlerResult(null);
return;
}
let cancelled = false;
let maybePromise;
try {
maybePromise = handler({
event: pendingEvent,
resolve: resolveRef.current
});
} catch (err) {
console.error("[CopilotKit] useInterrupt handler threw; result will be null:", err);
if (!cancelled) setHandlerResult(null);
return () => {
cancelled = true;
};
}
if (isPromiseLike(maybePromise)) Promise.resolve(maybePromise).then((resolved) => {
if (!cancelled) setHandlerResult(resolved);
}).catch((err) => {
console.error("[CopilotKit] useInterrupt handler rejected; result will be null:", err);
if (!cancelled) setHandlerResult(null);
});
else setHandlerResult(maybePromise);
return () => {
cancelled = true;
};
}, [pendingEvent]);
const element = (0, react.useMemo)(() => {
if (!pendingEvent) return null;
if (!isEnabled(pendingEvent)) return null;
return renderRef.current({
event: pendingEvent,
result: handlerResult,
resolve
});
}, [
pendingEvent,
handlerResult,
resolve
]);
(0, react.useEffect)(() => {
if (config.renderInChat === false) return;
copilotkit.setInterruptElement(element);
}, [
element,
config.renderInChat,
copilotkit
]);
(0, react.useEffect)(() => {
if (config.renderInChat === false) return;
return () => {
copilotkit.setInterruptElement(null);
};
}, []);
if (config.renderInChat === false) return element;
}
//#endregion
//#region src/v2/hooks/use-suggestions.tsx
function useSuggestions({ agentId } = {}) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const config = useCopilotChatConfiguration();
const resolvedAgentId = (0, react.useMemo)(() => agentId ?? config?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID, [agentId, config?.agentId]);
const [suggestions, setSuggestions] = (0, react.useState)(() => {
return copilotkit.getSuggestions(resolvedAgentId).suggestions;
});
const [isLoading, setIsLoading] = (0, react.useState)(() => {
return copilotkit.getSuggestions(resolvedAgentId).isLoading;
});
(0, react.useEffect)(() => {
const result = copilotkit.getSuggestions(resolvedAgentId);
setSuggestions(result.suggestions);
setIsLoading(result.isLoading);
}, [copilotkit, resolvedAgentId]);
(0, react.useEffect)(() => {
const subscription = copilotkit.subscribe({
onSuggestionsChanged: ({ agentId: changedAgentId, suggestions }) => {
if (changedAgentId !== resolvedAgentId) return;
setSuggestions(suggestions);
},
onSuggestionsStartedLoading: ({ agentId: changedAgentId }) => {
if (changedAgentId !== resolvedAgentId) return;
setIsLoading(true);
},
onSuggestionsFinishedLoading: ({ agentId: changedAgentId }) => {
if (changedAgentId !== resolvedAgentId) return;
setIsLoading(false);
},
onSuggestionsConfigChanged: () => {
const result = copilotkit.getSuggestions(resolvedAgentId);
setSuggestions(result.suggestions);
setIsLoading(result.isLoading);
}
});
return () => {
subscription.unsubscribe();
};
}, [copilotkit, resolvedAgentId]);
return {
suggestions,
reloadSuggestions: (0, react.useCallback)(() => {
copilotkit.reloadSuggestions(resolvedAgentId);
}, [copilotkit, resolvedAgentId]),
clearSuggestions: (0, react.useCallback)(() => {
copilotkit.clearSuggestions(resolvedAgentId);
}, [copilotkit, resolvedAgentId]),
isLoading
};
}
//#endregion
//#region src/v2/hooks/use-configure-suggestions.tsx
function useConfigureSuggestions(config, deps) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const chatConfig = useCopilotChatConfiguration();
const extraDeps = deps ?? [];
const resolvedConsumerAgentId = (0, react.useMemo)(() => chatConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID, [chatConfig?.agentId]);
const rawConsumerAgentId = (0, react.useMemo)(() => config ? config.consumerAgentId : void 0, [config]);
const normalizationCacheRef = (0, react.useRef)({
serialized: null,
config: null
});
const { normalizedConfig, serializedConfig } = (0, react.useMemo)(() => {
if (!config) {
normalizationCacheRef.current = {
serialized: null,
config: null
};
return {
normalizedConfig: null,
serializedConfig: null
};
}
if (config.available === "disabled") {
normalizationCacheRef.current = {
serialized: null,
config: null
};
return {
normalizedConfig: null,
serializedConfig: null
};
}
let built;
if (isDynamicConfig(config)) built = { ...config };
else {
const normalizedSuggestions = normalizeStaticSuggestions(config.suggestions);
built = {
...config,
suggestions: normalizedSuggestions
};
}
const serialized = JSON.stringify(built);
const cache = normalizationCacheRef.current;
if (cache.serialized === serialized && cache.config) return {
normalizedConfig: cache.config,
serializedConfig: serialized
};
normalizationCacheRef.current = {
serialized,
config: built
};
return {
normalizedConfig: built,
serializedConfig: serialized
};
}, [
config,
resolvedConsumerAgentId,
...extraDeps
]);
const latestConfigRef = (0, react.useRef)(null);
latestConfigRef.current = normalizedConfig;
const previousSerializedConfigRef = (0, react.useRef)(null);
const targetAgentId = (0, react.useMemo)(() => {
if (!normalizedConfig) return resolvedConsumerAgentId;
const consumer = normalizedConfig.consumerAgentId;
if (!consumer || consumer === "*") return resolvedConsumerAgentId;
return consumer;
}, [normalizedConfig, resolvedConsumerAgentId]);
const isGlobalConfig = rawConsumerAgentId === void 0 || rawConsumerAgentId === "*";
const isDynamicConfigType = (0, react.useMemo)(() => !!normalizedConfig && "instructions" in normalizedConfig, [normalizedConfig]);
const requestReload = (0, react.useCallback)(() => {
if (!normalizedConfig) return;
if (isGlobalConfig) {
const seen = /* @__PURE__ */ new Set();
const agents = Object.values(copilotkit.agents ?? {});
for (const entry of agents) {
const agentId = entry.agentId;
if (!agentId) continue;
seen.add(agentId);
if (!entry.isRunning) copilotkit.reloadSuggestions(agentId);
}
if (targetAgentId && !seen.has(targetAgentId)) copilotkit.reloadSuggestions(targetAgentId);
return;
}
if (!targetAgentId) return;
copilotkit.reloadSuggestions(targetAgentId);
}, [
copilotkit,
isGlobalConfig,
normalizedConfig,
targetAgentId
]);
(0, react.useEffect)(() => {
if (!serializedConfig || !latestConfigRef.current) return;
const id = copilotkit.addSuggestionsConfig(latestConfigRef.current);
requestReload();
return () => {
copilotkit.removeSuggestionsConfig(id);
};
}, [
copilotkit,
serializedConfig,
requestReload
]);
(0, react.useEffect)(() => {
if (!normalizedConfig) {
previousSerializedConfigRef.current = null;
return;
}
if (serializedConfig && previousSerializedConfigRef.current === serializedConfig) return;
if (serializedConfig) previousSerializedConfigRef.current = serializedConfig;
requestReload();
}, [
normalizedConfig,
requestReload,
serializedConfig
]);
(0, react.useEffect)(() => {
if (!normalizedConfig || extraDeps.length === 0) return;
requestReload();
}, [
extraDeps.length,
normalizedConfig,
requestReload,
...extraDeps
]);
(0, react.useEffect)(() => {
if (!normalizedConfig || !isDynamicConfigType) return;
if (!targetAgentId) return;
if (!!copilotkit.getAgent(targetAgentId)) return;
const subscription = copilotkit.subscribe({ onAgentsChanged: () => {
if (copilotkit.getAgent(targetAgentId)) {
requestReload();
subscription.unsubscribe();
}
} });
return () => {
subscription.unsubscribe();
};
}, [
copilotkit,
normalizedConfig,
isDynamicConfigType,
targetAgentId,
requestReload
]);
}
function isDynamicConfig(config) {
return "instructions" in config;
}
function normalizeStaticSuggestions(suggestions) {
return suggestions.map((suggestion) => ({
...suggestion,
isLoading: suggestion.isLoading ?? false
}));
}
//#endregion
//#region src/v2/hooks/use-agent-context.tsx
function useAgentContext(context) {
const { description, value } = context;
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const stringValue = (0, react.useMemo)(() => {
if (typeof value === "string") return value;
return JSON.stringify(value);
}, [value]);
(0, react.useLayoutEffect)(() => {
if (!copilotkit) return;
const id = copilotkit.addContext({
description,
value: stringValue
});
return () => {
copilotkit.removeContext(id);
};
}, [
description,
stringValue,
copilotkit
]);
}
//#endregion
//#region src/v2/hooks/use-threads.tsx
function useThreadStoreSelector(store, selector) {
return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
const subscription = store.select(selector).subscribe(onStoreChange);
return () => subscription.unsubscribe();
}, [store, selector]), () => selector(store.getState()));
}
/**
* React hook for listing and managing Intelligence platform threads.
*
* On mount the hook fetches the thread list for the runtime-authenticated user
* and the given `agentId`. When the Intelligence platform exposes a WebSocket
* URL, it also opens a realtime subscription so the `threads` array stays
* current without polling — thread creates, renames, archives, and deletes
* from any client are reflected immediately.
*
* Mutation methods (`renameThread`, `archiveThread`, `deleteThread`) return
* promises that resolve once the platform confirms the operation and reject
* with an `Error` on failure.
*
* @param input - Agent identifier and optional list controls.
* @returns Thread list state and stable mutation callbacks.
*
* @example
* ```tsx
* import { useThreads } from "@copilotkit/react-core";
*
* function ThreadList() {
* const { threads, isLoading, renameThread, deleteThread } = useThreads({
* agentId: "agent-1",
* });
*
* if (isLoading) return <p>Loading…</p>;
*
* return (
* <ul>
* {threads.map((t) => (
* <li key={t.id}>
* {t.name ?? "Untitled"}
* <button onClick={() => renameThread(t.id, "New name")}>Rename</button>
* <button onClick={() => deleteThread(t.id)}>Delete</button>
* </li>
* ))}
* </ul>
* );
* }
* ```
*/
function useThreads({ agentId, includeArchived, limit }) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
id,
agentId,
name,
archived,
createdAt,
updatedAt,
...lastRunAt !== void 0 ? { lastRunAt } : {}
})), [coreThreads]);
const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
const hasMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
const isFetchingMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
const headersKey = (0, react.useMemo)(() => {
return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
}, [copilotkit.headers]);
const runtimeError = (0, react.useMemo)(() => {
if (copilotkit.runtimeUrl) return null;
return /* @__PURE__ */ new Error("Runtime URL is not configured");
}, [copilotkit.runtimeUrl]);
const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
const error = runtimeError ?? storeError;
(0, react.useEffect)(() => {
store.start();
return () => {
store.stop();
};
}, [store]);
const runtimeStatus = copilotkit.runtimeConnectionStatus;
(0, react.useEffect)(() => {
copilotkit.registerThreadStore(agentId, store);
return () => {
copilotkit.unregisterThreadStore(agentId);
};
}, [
copilotkit,
agentId,
store
]);
(0, react.useEffect)(() => {
if (!copilotkit.runtimeUrl) {
store.setContext(null);
return;
}
if (runtimeStatus !== _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connected) return;
const context = {
runtimeUrl: copilotkit.runtimeUrl,
headers: { ...copilotkit.headers },
wsUrl: copilotkit.intelligence?.wsUrl,
agentId,
includeArchived,
limit
};
store.setContext(context);
setHasDispatchedContext(true);
}, [
store,
copilotkit.runtimeUrl,
runtimeStatus,
headersKey,
copilotkit.intelligence?.wsUrl,
agentId,
includeArchived,
limit
]);
const renameThread = (0, react.useCallback)((threadId, name) => store.renameThread(threadId, name), [store]);
const archiveThread = (0, react.useCallback)((threadId) => store.archiveThread(threadId), [store]);
const deleteThread = (0, react.useCallback)((threadId) => store.deleteThread(threadId), [store]);
return {
threads,
isLoading,
error,
hasMoreThreads,
isFetchingMoreThreads,
fetchMoreThreads: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
renameThread,
archiveThread,
deleteThread
};
}
//#endregion
//#region src/v2/types/defineToolCallRenderer.ts
function defineToolCallRenderer(def) {
const argsSchema = def.name === "*" && !def.args ? zod.z.any() : def.args;
return {
name: def.name,
args: argsSchema,
render: def.render,
...def.agentId ? { agentId: def.agentId } : {}
};
}
//#endregion
//#region src/v2/hooks/use-render-tool.tsx
const EMPTY_DEPS = [];
/**
* Registers a renderer entry in CopilotKit's `renderToolCalls` registry.
*
* Key behavior:
* - deduplicates by `agentId:name` (latest registration wins),
* - keeps renderer entries on cleanup so historical chat tool calls can still render,
* - refreshes registration when `deps` change.
*
* @typeParam S - Schema type describing tool call parameters.
* @param config - Renderer config for wildcard or named tools.
* @param deps - Optional dependencies to refresh registration.
*
* @example
* ```tsx
* useRenderTool(
* {
* name: "searchDocs",
* parameters: z.object({ query: z.string() }),
* render: ({ status, parameters, result }) => {
* if (status === "executing") return <div>Searching {parameters.query}</div>;
* if (status === "complete") return <div>{result}</div>;
* return <div>Preparing...</div>;
* },
* },
* [],
* );
* ```
*
* @example
* ```tsx
* useRenderTool(
* {
* name: "summarize",
* parameters: z.object({ text: z.string() }),
* agentId: "research-agent",
* render: ({ name, status }) => <div>{name}: {status}</div>,
* },
* [selectedAgentId],
* );
* ```
*/
function useRenderTool(config, deps) {
const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
const extraDeps = deps ?? EMPTY_DEPS;
(0, react.useEffect)(() => {
const renderer = config.name === "*" && !config.parameters ? defineToolCallRenderer({
name: "*",
render: (props) => config.render({
...props,
parameters: props.args
}),
...config.agentId ? { agentId: config.agentId } : {}
}) : defineToolCallRenderer({
name: config.name,
args: config.parameters,
render: (props) => config.render({
...props,
parameters: props.args
}),
...config.agentId ? { agentId: config.agentId } : {}
});
copilotkit.addHookRenderToolCall(renderer);
}, [
config.name,
copilotkit,
JSON.stringify(extraDeps)
]);
}
//#endregion
//#region src/v2/hooks/use-capabilities.tsx
/**
* Returns the capabilities declared by the given agent (or the default agent).
* Capabilities are populated from the runtime `/info` response at connection
* time. The hook reads them synchronously from the agent instance — there is
* no separate loading state, but the value will be `undefined` until the
* runtime handshake completes.
*
* @param agentId - Optional agent ID. If omitted, uses the default agent.
* @returns The agent's capabilities, or `undefined` if the agent doesn't
* declare capabilities.
*/
function useCapabilities(agentId) {
const { agent } = useAgent({ agentId });
if (agent && "capabilities" in agent) return agent.capabilities;
}
//#endregion
exports.CopilotChatConfigurationProvider = CopilotChatConfigurationProvider;
exports.CopilotChatDefaultLabels = CopilotChatDefaultLabels;
Object.defineProperty(exports, 'CopilotKitCoreReact', {
enumerable: true,
get: function () {
return _copilotkit_react_core_v2_context.CopilotKitCoreReact;
}
});
exports.defineToolCallRenderer = defineToolCallRenderer;
exports.useAgent = useAgent;
exports.useAgentContext = useAgentContext;
exports.useCapabilities = useCapabilities;
exports.useComponent = useComponent;
exports.useConfigureSuggestions = useConfigureSuggestions;
exports.useCopilotChatConfiguration = useCopilotChatConfiguration;
exports.useFrontendTool = useFrontendTool;
exports.useHumanInTheLoop = useHumanInTheLoop;
exports.useInterrupt = useInterrupt;
exports.useRenderTool = useRenderTool;
exports.useSuggestions = useSuggestions;
exports.useThreads = useThreads;
//# sourceMappingURL=headless.cjs.map