@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,370 lines (1,355 loc) • 263 kB
JavaScript
"use client";
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('react'), require('@copilotkit/core'), require('@ag-ui/client'), require('tailwind-merge'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('zod'), require('@lit-labs/react'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('react-dom'), require('react-markdown'), require('@copilotkit/runtime-client-gql')) :
typeof define === 'function' && define.amd ? define(['exports', 'react', '@copilotkit/core', '@ag-ui/client', 'tailwind-merge', '@copilotkit/shared', 'react/jsx-runtime', 'zod', '@lit-labs/react', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', 'react-dom', 'react-markdown', '@copilotkit/runtime-client-gql'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCore = {}), global.React,global.CopilotKitCore,global.AgUIClient,global.tailwind_merge,global.CopilotKitShared,global.ReactJsxRuntime,global.Zod,global._lit_labs_react,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global.ReactDOM,global.ReactMarkdown,global.CopilotKitRuntimeClientGQL));
})(this, function(exports, react, _copilotkit_core, _ag_ui_client, tailwind_merge, _copilotkit_shared, react_jsx_runtime, zod, _lit_labs_react, _copilotkit_a2ui_renderer, zod_to_json_schema, react_dom, react_markdown, _copilotkit_runtime_client_gql) {
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
react = __toESM(react);
react_markdown = __toESM(react_markdown);
//#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/lib/react-core.ts
var CopilotKitCoreReact = class extends _copilotkit_core.CopilotKitCore {
constructor(config) {
super(config);
this._renderToolCalls = [];
this._hookRenderToolCalls = /* @__PURE__ */ new Map();
this._cachedMergedRenderToolCalls = null;
this._renderCustomMessages = [];
this._renderActivityMessages = [];
this._interruptElement = null;
this._renderToolCalls = config.renderToolCalls ?? [];
this._renderCustomMessages = config.renderCustomMessages ?? [];
this._renderActivityMessages = config.renderActivityMessages ?? [];
}
get renderCustomMessages() {
return this._renderCustomMessages;
}
get renderActivityMessages() {
return this._renderActivityMessages;
}
get renderToolCalls() {
if (this._hookRenderToolCalls.size === 0) return this._renderToolCalls;
if (this._cachedMergedRenderToolCalls) return this._cachedMergedRenderToolCalls;
const merged = /* @__PURE__ */ new Map();
for (const rc of this._renderToolCalls) merged.set(`${rc.agentId ?? ""}:${rc.name}`, rc);
for (const [key, rc] of this._hookRenderToolCalls) merged.set(key, rc);
this._cachedMergedRenderToolCalls = Array.from(merged.values());
return this._cachedMergedRenderToolCalls;
}
setRenderActivityMessages(renderers) {
this._renderActivityMessages = renderers;
}
setRenderCustomMessages(renderers) {
this._renderCustomMessages = renderers;
}
setRenderToolCalls(renderToolCalls) {
this._renderToolCalls = renderToolCalls;
this._cachedMergedRenderToolCalls = null;
this._notifyRenderToolCallsChanged();
}
addHookRenderToolCall(entry) {
const key = `${entry.agentId ?? ""}:${entry.name}`;
this._hookRenderToolCalls.set(key, entry);
this._cachedMergedRenderToolCalls = null;
this._notifyRenderToolCallsChanged();
}
removeHookRenderToolCall(name, agentId) {
const key = `${agentId ?? ""}:${name}`;
if (this._hookRenderToolCalls.delete(key)) {
this._cachedMergedRenderToolCalls = null;
this._notifyRenderToolCallsChanged();
}
}
_notifyRenderToolCallsChanged() {
this.notifySubscribers((subscriber) => {
const reactSubscriber = subscriber;
if (reactSubscriber.onRenderToolCallsChanged) reactSubscriber.onRenderToolCallsChanged({
copilotkit: this,
renderToolCalls: this.renderToolCalls
});
}, "Subscriber onRenderToolCallsChanged error:");
}
get interruptElement() {
return this._interruptElement;
}
setInterruptElement(element) {
this._interruptElement = element;
this.notifySubscribers((subscriber) => {
subscriber.onInterruptElementChanged?.({
copilotkit: this,
interruptElement: this._interruptElement
});
}, "Subscriber onInterruptElementChanged error:");
}
subscribe(subscriber) {
return super.subscribe(subscriber);
}
/**
* Wait for pending React state updates before the follow-up agent run.
*
* When a frontend tool handler calls setState(), React 18 batches the update
* and schedules a commit via its internal scheduler (MessageChannel). The
* useAgentContext hook registers context via useLayoutEffect, which runs
* synchronously after React commits that batch.
*
* Awaiting a zero-delay timeout yields to the macrotask queue. React's
* MessageChannel task runs first, committing the pending state and running
* useLayoutEffect (which updates the context store). The follow-up runAgent
* call then reads fresh context.
*/
async waitForPendingFrameworkUpdates() {
await new Promise((resolve) => setTimeout(resolve, 0));
}
};
//#endregion
//#region src/v2/context.ts
const CopilotKitContext = (0, react.createContext)(null);
const useCopilotKit = () => {
const context = (0, react.useContext)(CopilotKitContext);
const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
if (!context) throw new Error("useCopilotKit must be used within CopilotKitProvider");
(0, react.useEffect)(() => {
const subscription = context.copilotkit.subscribe({ onRuntimeConnectionStatusChanged: () => {
forceUpdate();
} });
return () => {
subscription.unsubscribe();
};
}, []);
return context;
};
const LicenseContext = (0, react.createContext)({
status: null,
license: null,
checkFeature: () => true,
getLimit: () => null
});
//#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-default-render-tool.tsx
/**
* Module-level dedup set so an unknown status value only emits a console
* warning the FIRST time we encounter it. Otherwise a stuck/unmapped status
* would log on every re-render (potentially many per second).
*/
const warnedUnknownStatuses = /* @__PURE__ */ new Set();
/**
* Map a {@link ToolCallStatus} enum value to the documented string-union
* status the {@link DefaultRenderProps} contract exposes. Unknown / future
* enum members log a warning (once per distinct value) and fall back to
* `"inProgress"`.
*/
function mapToolCallStatus(status) {
switch (status) {
case _copilotkit_core.ToolCallStatus.Complete: return "complete";
case _copilotkit_core.ToolCallStatus.Executing: return "executing";
case _copilotkit_core.ToolCallStatus.InProgress: return "inProgress";
default: {
const key = String(status);
if (!warnedUnknownStatuses.has(key)) {
warnedUnknownStatuses.add(key);
console.warn(`[CopilotKit] Unknown ToolCallStatus "${key}" in default tool-call renderer; falling back to "inProgress".`);
}
return "inProgress";
}
}
}
/**
* Guarded JSON.stringify used inside the expanded `<pre>` blocks. A circular
* reference would otherwise crash the entire React tree on render.
*/
function safeStringifyForPre(value) {
try {
return JSON.stringify(value, null, 2);
} catch (err) {
console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for default renderer; falling back to String():", err);
try {
return String(value);
} catch (innerErr) {
console.warn("[CopilotKit] safeStringifyForPre: value could not be stringified:", innerErr);
return "[unserializable]";
}
}
}
function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
const [isExpanded, setIsExpanded] = (0, react.useState)(false);
const isActive = status === "inProgress" || status === "executing";
const isComplete = status === "complete";
const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
const dotColor = isActive ? "#f59e0b" : isComplete ? "#10b981" : "#a1a1aa";
const badgeBg = isActive ? "#fef3c7" : isComplete ? "#d1fae5" : "#f4f4f5";
const badgeColor = isActive ? "#92400e" : isComplete ? "#065f46" : "#3f3f46";
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
"data-testid": "copilot-tool-render",
"data-tool-name": name,
"data-tool-call-id": toolCallId,
"data-status": status,
"data-args": safeStringifyForAttr(parameters),
"data-result": safeStringifyForAttr(result),
style: {
marginTop: "8px",
paddingBottom: "8px"
},
children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
style: {
borderRadius: "12px",
border: "1px solid #e4e4e7",
backgroundColor: "#fafafa",
padding: "14px 16px"
},
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
type: "button",
"aria-expanded": isExpanded,
onClick: () => setIsExpanded(!isExpanded),
style: {
display: "flex",
alignItems: "center",
justifyContent: "space-between",
gap: "10px",
cursor: "pointer",
userSelect: "none",
width: "100%",
border: "none",
padding: 0,
margin: 0,
background: "transparent",
textAlign: "left",
font: "inherit",
color: "inherit"
},
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
style: {
display: "flex",
alignItems: "center",
gap: "8px",
minWidth: 0
},
children: [
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
style: {
height: "14px",
width: "14px",
color: "#71717a",
transition: "transform 0.15s",
transform: isExpanded ? "rotate(90deg)" : "rotate(0deg)",
flexShrink: 0
},
fill: "none",
viewBox: "0 0 24 24",
strokeWidth: 2,
stroke: "currentColor",
children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
strokeLinecap: "round",
strokeLinejoin: "round",
d: "M8.25 4.5l7.5 7.5-7.5 7.5"
})
}),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: {
display: "inline-block",
height: "8px",
width: "8px",
borderRadius: "50%",
backgroundColor: dotColor,
flexShrink: 0
} }),
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
"data-testid": "copilot-tool-render-name",
style: {
fontSize: "13px",
fontWeight: 600,
color: "#18181b",
overflow: "hidden",
textOverflow: "ellipsis",
whiteSpace: "nowrap"
},
children: name
})
]
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
"data-testid": "copilot-tool-render-status",
style: {
display: "inline-flex",
alignItems: "center",
borderRadius: "9999px",
padding: "2px 8px",
fontSize: "11px",
fontWeight: 500,
backgroundColor: badgeBg,
color: badgeColor,
flexShrink: 0
},
children: statusLabel
})]
}), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
style: {
marginTop: "12px",
display: "grid",
gap: "12px"
},
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
style: {
fontSize: "10px",
textTransform: "uppercase",
letterSpacing: "0.05em",
color: "#71717a"
},
children: "Arguments"
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
style: {
marginTop: "6px",
maxHeight: "200px",
overflow: "auto",
borderRadius: "6px",
backgroundColor: "#f4f4f5",
padding: "10px",
fontSize: "11px",
lineHeight: 1.6,
color: "#27272a",
whiteSpace: "pre-wrap",
wordBreak: "break-word"
},
children: safeStringifyForPre(parameters ?? {})
})] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
style: {
fontSize: "10px",
textTransform: "uppercase",
letterSpacing: "0.05em",
color: "#71717a"
},
children: "Result"
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
style: {
marginTop: "6px",
maxHeight: "200px",
overflow: "auto",
borderRadius: "6px",
backgroundColor: "#f4f4f5",
padding: "10px",
fontSize: "11px",
lineHeight: 1.6,
color: "#27272a",
whiteSpace: "pre-wrap",
wordBreak: "break-word"
},
children: typeof result === "string" ? result : safeStringifyForPre(result)
})] })]
})]
})
});
}
function safeStringifyForAttr(value) {
if (value === void 0 || value === null) return "";
if (typeof value === "string") return value;
try {
return JSON.stringify(value);
} catch (err) {
console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for data-* attribute; falling back to String():", err);
try {
return String(value);
} catch (innerErr) {
console.warn("[CopilotKit] safeStringifyForAttr: value could not be stringified:", innerErr);
return "";
}
}
}
//#endregion
//#region src/v2/hooks/use-render-tool-call.tsx
/**
* Memoized component that renders a single tool call.
* This prevents unnecessary re-renders when parent components update
* but the tool call data hasn't changed.
*/
const ToolCallRenderer = react.default.memo(function ToolCallRenderer({ toolCall, toolMessage, RenderComponent, isExecuting }) {
const args = (0, react.useMemo)(() => (0, _copilotkit_shared.partialJSONParse)(toolCall.function.arguments), [toolCall.function.arguments]);
const toolName = toolCall.function.name;
if (toolMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
name: toolName,
toolCallId: toolCall.id,
args,
status: _copilotkit_core.ToolCallStatus.Complete,
result: toolMessage.content
});
else if (isExecuting) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
name: toolName,
toolCallId: toolCall.id,
args,
status: _copilotkit_core.ToolCallStatus.Executing,
result: void 0
});
else return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
name: toolName,
toolCallId: toolCall.id,
args,
status: _copilotkit_core.ToolCallStatus.InProgress,
result: void 0
});
}, (prevProps, nextProps) => {
if (prevProps.toolCall.id !== nextProps.toolCall.id) return false;
if (prevProps.toolCall.function.name !== nextProps.toolCall.function.name) return false;
if (prevProps.toolCall.function.arguments !== nextProps.toolCall.function.arguments) return false;
if (prevProps.toolMessage?.content !== nextProps.toolMessage?.content) return false;
if (prevProps.isExecuting !== nextProps.isExecuting) return false;
if (prevProps.RenderComponent !== nextProps.RenderComponent) return false;
return true;
});
/**
* Hook that returns a function to render tool calls based on the render functions
* defined in CopilotKitProvider.
*
* @returns A function that takes a tool call and optional tool message and returns the rendered component
*/
function useRenderToolCall$1() {
const { copilotkit, executingToolCallIds } = useCopilotKit();
const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
const renderToolCalls = (0, react.useSyncExternalStore)((callback) => {
return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
}, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
return (0, react.useCallback)(({ toolCall, toolMessage }) => {
const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRenderer, {
toolCall,
toolMessage,
RenderComponent: (exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*"))?.render ?? defaultToolCallRenderAdapter,
isExecuting: executingToolCallIds.has(toolCall.id)
}, toolCall.id);
}, [
renderToolCalls,
executingToolCallIds,
agentId
]);
}
function defaultToolCallRenderAdapter(props) {
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, {
name: props.name,
toolCallId: props.toolCallId,
parameters: props.args,
status: mapToolCallStatus(props.status),
result: props.result
});
}
//#endregion
//#region src/v2/components/CopilotKitInspector.tsx
const CopilotKitInspector = ({ core, ...rest }) => {
const [InspectorComponent, setInspectorComponent] = react.useState(null);
react.useEffect(() => {
let mounted = true;
import("@copilotkit/web-inspector").then((mod) => {
mod.defineWebInspector?.();
const Component = (0, _lit_labs_react.createComponent)({
tagName: mod.WEB_INSPECTOR_TAG,
elementClass: mod.WebInspectorElement,
react
});
if (mounted) setInspectorComponent(() => Component);
});
return () => {
mounted = false;
};
}, []);
if (!InspectorComponent) return null;
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InspectorComponent, {
...rest,
core: core ?? null
});
};
CopilotKitInspector.displayName = "CopilotKitInspector";
//#endregion
//#region src/v2/components/license-warning-banner.tsx
const LICENSE_BANNER_OFFSET_PX = 52;
const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
const BANNER_STYLES = {
base: {
position: "fixed",
bottom: "8px",
left: "50%",
transform: "translateX(-50%)",
zIndex: 99999,
display: "inline-flex",
alignItems: "center",
gap: "12px",
whiteSpace: "nowrap",
padding: "8px 16px",
fontSize: "13px",
fontFamily: "-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif",
borderRadius: "6px",
boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)"
},
info: {
backgroundColor: "#eff6ff",
border: "1px solid #93c5fd",
color: "#1e40af"
},
warning: {
backgroundColor: "#fffbeb",
border: "1px solid #fbbf24",
color: "#92400e"
},
critical: {
backgroundColor: "#fef2f2",
border: "1px solid #fca5a5",
color: "#991b1b"
}
};
function getSeverityStyle(severity) {
switch (severity) {
case "warning": return BANNER_STYLES.warning;
case "critical": return BANNER_STYLES.critical;
default: return BANNER_STYLES.info;
}
}
function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
(0, react.useEffect)(() => {
if (typeof document === "undefined") return;
const root = document.documentElement;
root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
return () => {
root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
};
}, []);
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
style: {
...BANNER_STYLES.base,
...getSeverityStyle(severity)
},
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: message }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
style: {
display: "flex",
gap: "8px",
alignItems: "center"
},
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", {
href: actionUrl,
target: "_blank",
rel: "noopener noreferrer",
style: {
fontWeight: 600,
textDecoration: "underline",
color: "inherit"
},
children: actionLabel
}), onDismiss && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
onClick: onDismiss,
style: {
background: "none",
border: "none",
cursor: "pointer",
color: "inherit",
fontSize: "16px"
},
children: "×"
})]
})]
});
}
function LicenseWarningBanner({ type, featureName, expiryDate, graceRemaining, onDismiss }) {
switch (type) {
case "no_license": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
severity: "info",
message: "Powered by CopilotKit",
actionLabel: "Get a license",
actionUrl: "https://copilotkit.ai/pricing",
onDismiss
});
case "feature_unlicensed": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
severity: "warning",
message: `⚠ The "${featureName}" feature requires a CopilotKit license.`,
actionLabel: "Get a license",
actionUrl: "https://copilotkit.ai/pricing",
onDismiss
});
case "expiring": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
severity: "warning",
message: `Your CopilotKit license expires in ${graceRemaining} day${graceRemaining !== 1 ? "s" : ""}. Please renew.`,
actionLabel: "Renew",
actionUrl: "https://cloud.copilotkit.ai",
onDismiss
});
case "expired": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
severity: "critical",
message: `Your CopilotKit license expired${expiryDate ? ` on ${expiryDate}` : ""}. Please renew at copilotkit.ai/pricing`,
actionLabel: "Renew now",
actionUrl: "https://copilotkit.ai/pricing",
onDismiss
});
case "invalid": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerShell, {
severity: "critical",
message: "Invalid CopilotKit license token. Please check your configuration.",
actionLabel: "Get a license",
actionUrl: "https://copilotkit.ai/pricing",
onDismiss
});
default: return null;
}
}
//#endregion
//#region src/v2/components/MCPAppsActivityRenderer.tsx
const PROTOCOL_VERSION = "2025-06-18";
function buildSandboxHTML(extraCspDomains) {
const baseScriptSrc = "'self' 'wasm-unsafe-eval' 'unsafe-inline' 'unsafe-eval' blob: data: http://localhost:* https://localhost:*";
const baseFrameSrc = "* blob: data: http://localhost:* https://localhost:*";
const extra = extraCspDomains?.length ? " " + extraCspDomains.join(" ") : "";
return `<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; img-src * data: blob: 'unsafe-inline'; media-src * blob: data:; font-src * blob: data:; script-src ${baseScriptSrc + extra}; style-src * blob: data: 'unsafe-inline'; connect-src *; frame-src ${baseFrameSrc + extra}; base-uri 'self';" />
<style>html,body{margin:0;padding:0;height:100%;width:100%;overflow:hidden}*{box-sizing:border-box}iframe{background-color:transparent;border:none;padding:0;overflow:hidden;width:100%;height:100%}</style>
</head>
<body>
<script>
if(window.self===window.top){throw new Error("This file must be used in an iframe.")}
const inner=document.createElement("iframe");
inner.style="width:100%;height:100%;border:none;";
inner.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms");
document.body.appendChild(inner);
window.addEventListener("message",async(event)=>{
if(event.source===window.parent){
if(event.data&&event.data.method==="ui/notifications/sandbox-resource-ready"){
const{html,sandbox}=event.data.params;
if(typeof sandbox==="string")inner.setAttribute("sandbox",sandbox);
if(typeof html==="string")inner.srcdoc=html;
}else if(inner&&inner.contentWindow){
inner.contentWindow.postMessage(event.data,"*");
}
}else if(event.source===inner.contentWindow){
window.parent.postMessage(event.data,"*");
}
});
window.parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-ready",params:{}},"*");
<\/script>
</body>
</html>`;
}
/**
* Queue for serializing MCP app requests to an agent.
* Ensures requests wait for the agent to stop running and are processed one at a time.
*/
var MCPAppsRequestQueue = class {
constructor() {
this.queues = /* @__PURE__ */ new Map();
this.processing = /* @__PURE__ */ new Map();
}
/**
* Add a request to the queue for a specific agent thread.
* Returns a promise that resolves when the request completes.
*/
async enqueue(agent, request) {
const threadId = agent.threadId || "default";
return new Promise((resolve, reject) => {
let queue = this.queues.get(threadId);
if (!queue) {
queue = [];
this.queues.set(threadId, queue);
}
queue.push({
execute: request,
resolve,
reject
});
this.processQueue(threadId, agent);
});
}
async processQueue(threadId, agent) {
if (this.processing.get(threadId)) return;
this.processing.set(threadId, true);
try {
const queue = this.queues.get(threadId);
if (!queue) return;
while (queue.length > 0) {
const item = queue[0];
try {
await this.waitForAgentIdle(agent);
const result = await item.execute();
item.resolve(result);
} catch (error) {
item.reject(error instanceof Error ? error : new Error(String(error)));
}
queue.shift();
}
} finally {
this.processing.set(threadId, false);
}
}
waitForAgentIdle(agent) {
return new Promise((resolve) => {
if (!agent.isRunning) {
resolve();
return;
}
let done = false;
const finish = () => {
if (done) return;
done = true;
clearInterval(checkInterval);
sub.unsubscribe();
resolve();
};
const sub = agent.subscribe({
onRunFinalized: finish,
onRunFailed: finish
});
const checkInterval = setInterval(() => {
if (!agent.isRunning) finish();
}, 500);
});
}
};
const mcpAppsRequestQueue = new MCPAppsRequestQueue();
/**
* Activity type for MCP Apps events - must match the middleware's MCPAppsActivityType
*/
const MCPAppsActivityType = "mcp-apps";
const MCPAppsActivityContentSchema = zod.z.object({
result: zod.z.object({
content: zod.z.array(zod.z.any()).optional(),
structuredContent: zod.z.any().optional(),
isError: zod.z.boolean().optional()
}),
resourceUri: zod.z.string(),
serverHash: zod.z.string(),
serverId: zod.z.string().optional(),
toolInput: zod.z.record(zod.z.string(), zod.z.unknown()).optional()
});
function isRequest(msg) {
return "id" in msg && "method" in msg;
}
function isNotification(msg) {
return !("id" in msg) && "method" in msg;
}
/**
* MCP Apps Extension Activity Renderer
*
* Renders MCP Apps UI in a sandboxed iframe with full protocol support.
* Fetches resource content on-demand via proxied MCP requests.
*/
const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, agent }) {
const { copilotkit } = useCopilotKit();
const containerRef = (0, react.useRef)(null);
const iframeRef = (0, react.useRef)(null);
const [iframeReady, setIframeReady] = (0, react.useState)(false);
const [error, setError] = (0, react.useState)(null);
const [isLoading, setIsLoading] = (0, react.useState)(true);
const [iframeSize, setIframeSize] = (0, react.useState)({});
const [fetchedResource, setFetchedResource] = (0, react.useState)(null);
const contentRef = (0, react.useRef)(content);
contentRef.current = content;
const agentRef = (0, react.useRef)(agent);
agentRef.current = agent;
const fetchStateRef = (0, react.useRef)({
inProgress: false,
promise: null,
resourceUri: null
});
const sendToIframe = (0, react.useCallback)((msg) => {
if (iframeRef.current?.contentWindow) {
console.log("[MCPAppsRenderer] Sending to iframe:", msg);
iframeRef.current.contentWindow.postMessage(msg, "*");
}
}, []);
const sendResponse = (0, react.useCallback)((id, result) => {
sendToIframe({
jsonrpc: "2.0",
id,
result
});
}, [sendToIframe]);
const sendErrorResponse = (0, react.useCallback)((id, code, message) => {
sendToIframe({
jsonrpc: "2.0",
id,
error: {
code,
message
}
});
}, [sendToIframe]);
const sendNotification = (0, react.useCallback)((method, params) => {
sendToIframe({
jsonrpc: "2.0",
method,
params: params || {}
});
}, [sendToIframe]);
(0, react.useEffect)(() => {
const { resourceUri, serverHash, serverId } = content;
if (fetchStateRef.current.inProgress && fetchStateRef.current.resourceUri === resourceUri) {
fetchStateRef.current.promise?.then((resource) => {
if (resource) {
setFetchedResource(resource);
setIsLoading(false);
}
}).catch((err) => {
setError(err instanceof Error ? err : new Error(String(err)));
setIsLoading(false);
});
return;
}
if (!agent) {
setError(/* @__PURE__ */ new Error("No agent available to fetch resource"));
setIsLoading(false);
return;
}
fetchStateRef.current.inProgress = true;
fetchStateRef.current.resourceUri = resourceUri;
const fetchPromise = (async () => {
try {
const resource = (await mcpAppsRequestQueue.enqueue(agent, () => agent.runAgent({ forwardedProps: { __proxiedMCPRequest: {
serverHash,
serverId,
method: "resources/read",
params: { uri: resourceUri }
} } }))).result?.contents?.[0];
if (!resource) throw new Error("No resource content in response");
return resource;
} catch (err) {
console.error("[MCPAppsRenderer] Failed to fetch resource:", err);
throw err;
} finally {
fetchStateRef.current.inProgress = false;
}
})();
fetchStateRef.current.promise = fetchPromise;
fetchPromise.then((resource) => {
if (resource) {
setFetchedResource(resource);
setIsLoading(false);
}
}).catch((err) => {
setError(err instanceof Error ? err : new Error(String(err)));
setIsLoading(false);
});
}, [agent, content]);
(0, react.useEffect)(() => {
if (isLoading || !fetchedResource) return;
const container = containerRef.current;
if (!container) return;
let mounted = true;
let messageHandler = null;
let initialListener = null;
let createdIframe = null;
const setup = async () => {
try {
const iframe = document.createElement("iframe");
createdIframe = iframe;
iframe.style.width = "100%";
iframe.style.height = "100px";
iframe.style.border = "none";
iframe.style.backgroundColor = "transparent";
iframe.style.display = "block";
iframe.setAttribute("sandbox", "allow-scripts allow-same-origin allow-forms");
const sandboxReady = new Promise((resolve) => {
initialListener = (event) => {
if (event.source === iframe.contentWindow) {
if (event.data?.method === "ui/notifications/sandbox-proxy-ready") {
if (initialListener) {
window.removeEventListener("message", initialListener);
initialListener = null;
}
resolve();
}
}
};
window.addEventListener("message", initialListener);
});
if (!mounted) {
if (initialListener) {
window.removeEventListener("message", initialListener);
initialListener = null;
}
return;
}
const cspDomains = fetchedResource._meta?.ui?.csp?.resourceDomains;
iframe.srcdoc = buildSandboxHTML(cspDomains);
iframeRef.current = iframe;
container.appendChild(iframe);
await sandboxReady;
if (!mounted) return;
console.log("[MCPAppsRenderer] Sandbox proxy ready");
messageHandler = async (event) => {
if (event.source !== iframe.contentWindow) return;
const msg = event.data;
if (!msg || typeof msg !== "object" || msg.jsonrpc !== "2.0") return;
console.log("[MCPAppsRenderer] Received from iframe:", msg);
if (isRequest(msg)) switch (msg.method) {
case "ui/initialize":
sendResponse(msg.id, {
protocolVersion: PROTOCOL_VERSION,
hostInfo: {
name: "CopilotKit MCP Apps Host",
version: "1.0.0"
},
hostCapabilities: {
openLinks: {},
logging: {}
},
hostContext: {
theme: "light",
platform: "web"
}
});
break;
case "ui/message": {
const currentAgent = agentRef.current;
if (!currentAgent) {
console.warn("[MCPAppsRenderer] ui/message: No agent available");
sendResponse(msg.id, { isError: false });
break;
}
try {
const params = msg.params;
const role = params.role || "user";
const textContent = params.content?.filter((c) => c.type === "text" && c.text).map((c) => c.text).join("\n") || "";
if (textContent) currentAgent.addMessage({
id: crypto.randomUUID(),
role,
content: textContent
});
sendResponse(msg.id, { isError: false });
if ((params.followUp ?? role === "user") && textContent) mcpAppsRequestQueue.enqueue(currentAgent, () => copilotkit.runAgent({ agent: currentAgent })).catch((err) => console.error("[MCPAppsRenderer] ui/message agent run failed:", err));
} catch (err) {
console.error("[MCPAppsRenderer] ui/message error:", err);
sendResponse(msg.id, { isError: true });
}
break;
}
case "ui/open-link": {
const url = msg.params?.url;
if (url) {
window.open(url, "_blank", "noopener,noreferrer");
sendResponse(msg.id, { isError: false });
} else sendErrorResponse(msg.id, -32602, "Missing url parameter");
break;
}
case "tools/call": {
const { serverHash, serverId } = contentRef.current;
const currentAgent = agentRef.current;
if (!serverHash) {
sendErrorResponse(msg.id, -32603, "No server hash available for proxying");
break;
}
if (!currentAgent) {
sendErrorResponse(msg.id, -32603, "No agent available for proxying");
break;
}
try {
const runResult = await mcpAppsRequestQueue.enqueue(currentAgent, () => currentAgent.runAgent({ forwardedProps: { __proxiedMCPRequest: {
serverHash,
serverId,
method: "tools/call",
params: msg.params
} } }));
sendResponse(msg.id, runResult.result || {});
} catch (err) {
console.error("[MCPAppsRenderer] tools/call error:", err);
sendErrorResponse(msg.id, -32603, String(err));
}
break;
}
default: sendErrorResponse(msg.id, -32601, `Method not found: ${msg.method}`);
}
if (isNotification(msg)) switch (msg.method) {
case "ui/notifications/initialized":
console.log("[MCPAppsRenderer] Inner iframe initialized");
if (mounted) setIframeReady(true);
break;
case "ui/notifications/size-changed": {
const { width, height } = msg.params || {};
console.log("[MCPAppsRenderer] Size change:", {
width,
height
});
if (mounted) setIframeSize({
width: typeof width === "number" ? width : void 0,
height: typeof height === "number" ? height : void 0
});
break;
}
case "notifications/message":
console.log("[MCPAppsRenderer] App log:", msg.params);
break;
}
};
window.addEventListener("message", messageHandler);
let html;
if (fetchedResource.text) html = fetchedResource.text;
else if (fetchedResource.blob) html = atob(fetchedResource.blob);
else throw new Error("Resource has no text or blob content");
sendNotification("ui/notifications/sandbox-resource-ready", { html });
} catch (err) {
console.error("[MCPAppsRenderer] Setup error:", err);
if (mounted) setError(err instanceof Error ? err : new Error(String(err)));
}
};
setup();
return () => {
mounted = false;
if (initialListener) {
window.removeEventListener("message", initialListener);
initialListener = null;
}
if (messageHandler) window.removeEventListener("message", messageHandler);
if (createdIframe) {
createdIframe.remove();
createdIframe = null;
}
iframeRef.current = null;
};
}, [
isLoading,
fetchedResource,
sendNotification,
sendResponse,
sendErrorResponse
]);
(0, react.useEffect)(() => {
if (iframeRef.current) {
if (iframeSize.width !== void 0) {
iframeRef.current.style.minWidth = `min(${iframeSize.width}px, 100%)`;
iframeRef.current.style.width = "100%";
}
if (iframeSize.height !== void 0) iframeRef.current.style.height = `${iframeSize.height}px`;
}
}, [iframeSize]);
(0, react.useEffect)(() => {
if (iframeReady && content.toolInput) {
console.log("[MCPAppsRenderer] Sending tool input:", content.toolInput);
sendNotification("ui/notifications/tool-input", { arguments: content.toolInput });
}
}, [
iframeReady,
content.toolInput,
sendNotification
]);
(0, react.useEffect)(() => {
if (iframeReady && content.result) {
console.log("[MCPAppsRenderer] Sending tool result:", content.result);
sendNotification("ui/notifications/tool-result", content.result);
}
}, [
iframeReady,
content.result,
sendNotification
]);
const borderStyle = fetchedResource?._meta?.ui?.prefersBorder === true ? {
borderRadius: "8px",
backgroundColor: "#f9f9f9",
border: "1px solid #e0e0e0"
} : {};
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
ref: containerRef,
style: {
width: "100%",
height: iframeSize.height ? `${iframeSize.height}px` : "auto",
minHeight: "100px",
overflow: "hidden",
position: "relative",
...borderStyle
},
children: [isLoading && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
style: {
padding: "1rem",
color: "#666"
},
children: "Loading..."
}), error && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
style: {
color: "red",
padding: "1rem"
},
children: ["Error: ", error.message]
})]
});
};
//#endregion
//#region src/v2/providers/SandboxFunctionsContext.ts
const SandboxFunctionsContext = (0, react.createContext)([]);
function useSandboxFunctions() {
return (0, react.useContext)(SandboxFunctionsContext);
}
//#endregion
//#region src/v2/lib/processPartialHtml.ts
/**
* Extracts all complete `<style>` blocks from the raw HTML.
* Returns the concatenated style tags, suitable for injection into `<head>`.
*/
function extractCompleteStyles(html) {
const matches = html.match(/<style\b[^>]*>[\s\S]*?<\/style>/gi);
return matches ? matches.join("") : "";
}
/**
* Processes raw accumulated HTML for safe preview via innerHTML injection.
* Pure function, no DOM dependencies.
*
* Pipeline (order matters):
* 1. Strip incomplete tag at end
* 2. Strip complete <style>, <script>, and <head> blocks
* 3. Strip incomplete <style>/<script>/<head> blocks
* 4. Strip incomplete HTML entities
* 5. Extract body content (or use full string if no <body>)
*/
function processPartialHtml(html) {
let result = html;
result = result.replace(/<[^>]*$/, "");
result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*$/gi, "");
result = result.replace(/&[a-zA-Z0-9#]*$/, "");
const bodyMatch = result.match(/<body[^>]*>([\s\S]*)/i);
if (bodyMatch) {
result = bodyMatch[1];
result = result.replace(/<\/body>[\s\S]*/i, "");
}
return result;
}
//#endregion
//#region src/v2/components/OpenGenerativeUIRenderer.tsx
const OpenGenerativeUIActivityType = "open-generative-ui";
const OpenGenerativeUIContentSchema = zod.z.object({
initialHeight: zod.z.number().optional(),
generating: zod.z.boolean().optional(),
css: zod.z.string().optional(),
cssComplete: zod.z.boolean().optional(),
html: zod.z.array(zod.z.string()).optional(),
htmlComplete: zod.z.boolean().optional(),
jsFunctions: zod.z.string().optional(),
jsFunctionsComplete: zod.z.boolean().optional(),
jsExpressions: zod.z.array(zod.z.string()).optional(),
jsExpressionsComplete: zod.z.boolean().optional()
});
/**
* Schema for the generateSandboxedUi tool call arguments.
* Used by the frontend tool renderer to display placeholder messages.
*/
const GenerateSandboxedUiArgsSchema = zod.z.object({
initialHeight: zod.z.number().optional(),
placeholderMessages: zod.z.array(zod.z.string()).optional(),
css: zod.z.string().optional(),
html: zod.z.string().optional(),
jsFunctions: zod.z.string().optional(),
jsExpressions: zod.z.array(zod.z.string()).optional()
});
const THROTTLE_MS = 1e3;
/**
* Returns true when the inner component should re-render immediately
* (no throttle delay).
*/
function shouldFlushImmediately(prev, next) {
if (next.cssComplete && (!prev || !prev.cssComplete)) return true;
if (next.htmlComplete) return true;
if (next.generating === false) return true;
if (next.jsFunctions && (!prev || !prev.jsFunctions)) return true;
if ((next.jsExpressions?.length ?? 0) > (prev?.jsExpressions?.length ?? 0)) return true;
if (next.html?.length && (!prev || !prev.html?.length)) return true;
return false;
}
/**
* Outer wrapper — absorbs every parent re-render but only forwards
* throttled content snapshots to the memoized inner component.
*/
const OpenGenerativeUIActivityRenderer = function OpenGenerativeUIActivityRenderer({ content }) {
const latestContentRef = (0, react.useRef)(content);
latestContentRef.current = content;
const [throttledContent, setThrottledContent] = (0, react.useState)(content);
const throttledContentRef = (0, react.useRef)(throttledContent);
const timerRef = (0, react.useRef)(null);
if (throttledContentRef.current !== content) {