UNPKG

@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,308 lines (1,295 loc) 272 kB
"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?" }; /** * Mobile breakpoint below which the chat modal and the thread-list drawer are * mutually exclusive. At or above this width both surfaces may coexist. This * mirrors the `(max-width: 767px)` / `(min-width: 768px)` split already used by * CopilotChatInput and CopilotSidebarView. */ const MOBILE_MAX_WIDTH_PX = 767; /** * Reports whether the current viewport is in the mobile range (`<768px`), where * the chat modal and drawer must not be open simultaneously. SSR-safe and * defensive against environments without `matchMedia` (treated as desktop, so * no mutual-exclusion constraint is applied). * * @returns `true` when the viewport is mobile-width, `false` otherwise. */ function isMobileViewport() { if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false; return window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH_PX}px)`).matches; } 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 threadIdPropIsAuthoritative = threadId !== void 0 && hasExplicitThreadId !== false; const isThreadIdControlled = threadIdPropIsAuthoritative; const [activeThreadOverride, setActiveThreadOverride] = (0, react.useState)(null); const resolvedThreadId = (0, react.useMemo)(() => { if (threadIdPropIsAuthoritative) return threadId; if (activeThreadOverride) return activeThreadOverride.threadId; if (parentConfig?.threadId) return parentConfig.threadId; if (threadId) return threadId; return (0, _copilotkit_shared.randomUUID)(); }, [ threadIdPropIsAuthoritative, threadId, parentConfig?.threadId, activeThreadOverride ]); const resolvedHasExplicitThreadId = (threadIdPropIsAuthoritative ? true : activeThreadOverride?.explicit ?? hasExplicitThreadId ?? false) || !!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 [ownDrawerOpen, setOwnDrawerOpen] = (0, react.useState)(false); const [ownDrawerCount, setOwnDrawerCount] = (0, react.useState)(0); const modalCloseRef = (0, react.useRef)(() => {}); modalCloseRef.current = resolvedSetModalOpen; const registeredModalClosersRef = (0, react.useRef)([]); const ownRegisterModalCloser = (0, react.useCallback)((closeModal) => { registeredModalClosersRef.current.push(closeModal); return () => { registeredModalClosersRef.current = registeredModalClosersRef.current.filter((entry) => entry !== closeModal); }; }, []); const ownSetDrawerOpen = (0, react.useCallback)((open) => { setOwnDrawerOpen(open); if (open && isMobileViewport()) { const registered = registeredModalClosersRef.current; (registered.length > 0 ? registered[registered.length - 1] : modalCloseRef.current)(false); } }, []); const ownRegisterDrawer = (0, react.useCallback)(() => { setOwnDrawerCount((count) => count + 1); return () => { setOwnDrawerCount((count) => Math.max(0, count - 1)); }; }, []); const resolvedDrawerOpen = parentConfig ? parentConfig.drawerOpen : ownDrawerOpen; const resolvedSetDrawerOpen = parentConfig ? parentConfig.setDrawerOpen : ownSetDrawerOpen; const resolvedDrawerRegistered = parentConfig ? parentConfig.drawerRegistered : ownDrawerCount > 0; const resolvedRegisterDrawer = parentConfig ? parentConfig.registerDrawer : ownRegisterDrawer; const resolvedRegisterModalCloser = parentConfig ? parentConfig.ɵregisterModalCloser : ownRegisterModalCloser; (0, react.useEffect)(() => { if (!hasExplicitDefault) return; return resolvedRegisterModalCloser(resolvedSetModalOpen); }, [ hasExplicitDefault, resolvedRegisterModalCloser, resolvedSetModalOpen ]); const isThreadIdControlledRef = (0, react.useRef)(isThreadIdControlled); isThreadIdControlledRef.current = isThreadIdControlled; const ownSetActiveThreadId = (0, react.useCallback)((id, options) => { setActiveThreadOverride({ threadId: id, explicit: options?.explicit ?? true }); }, []); const ownStartNewThread = (0, react.useCallback)(() => { setActiveThreadOverride({ threadId: (0, _copilotkit_shared.randomUUID)(), explicit: false }); }, []); const parentSetActiveThreadId = parentConfig?.setActiveThreadId; const parentStartNewThread = parentConfig?.startNewThread; const resolvedSetActiveThreadId = (0, react.useCallback)((id, options) => { if (isThreadIdControlledRef.current) { console.warn("[CopilotKit] Ignoring setActiveThreadId(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider."); return; } if (parentSetActiveThreadId) { parentSetActiveThreadId(id, options); return; } ownSetActiveThreadId(id, options); }, [parentSetActiveThreadId, ownSetActiveThreadId]); const resolvedStartNewThread = (0, react.useCallback)(() => { if (isThreadIdControlledRef.current) { console.warn("[CopilotKit] Ignoring startNewThread(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider."); return; } if (parentStartNewThread) { parentStartNewThread(); return; } ownStartNewThread(); }, [parentStartNewThread, ownStartNewThread]); const setModalOpenWithDrawerExclusion = (0, react.useCallback)((open) => { if (open && isMobileViewport()) resolvedSetDrawerOpen(false); resolvedSetModalOpen(open); }, [resolvedSetModalOpen, resolvedSetDrawerOpen]); const configurationValue = (0, react.useMemo)(() => ({ labels: mergedLabels, agentId: resolvedAgentId, threadId: resolvedThreadId, hasExplicitThreadId: resolvedHasExplicitThreadId, isModalOpen: resolvedIsModalOpen, setModalOpen: setModalOpenWithDrawerExclusion, drawerOpen: resolvedDrawerOpen, setDrawerOpen: resolvedSetDrawerOpen, drawerRegistered: resolvedDrawerRegistered, registerDrawer: resolvedRegisterDrawer, ɵregisterModalCloser: resolvedRegisterModalCloser, setActiveThreadId: resolvedSetActiveThreadId, startNewThread: resolvedStartNewThread }), [ mergedLabels, resolvedAgentId, resolvedThreadId, resolvedHasExplicitThreadId, resolvedIsModalOpen, setModalOpenWithDrawerExclusion, resolvedDrawerOpen, resolvedSetDrawerOpen, resolvedDrawerRegistered, resolvedRegisterDrawer, resolvedRegisterModalCloser, resolvedSetActiveThreadId, resolvedStartNewThread ]); 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(); }, onHeadersChanged: () => { forceUpdate(); } }); return () => { subscription.unsubscribe(); }; }, []); return context; }; const LicenseContext = (0, react.createContext)({ status: null, license: null, checkFeature: () => true, getLimit: () => null }); //#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); const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*"); if (!renderConfig) return null; const RenderComponent = renderConfig.render; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRenderer, { toolCall, toolMessage, RenderComponent, isExecuting: executingToolCallIds.has(toolCall.id) }, toolCall.id); }, [ renderToolCalls, executingToolCallIds, agentId ]); } //#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://dashboard.operations.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) { if (shouldFlushImmediately(throttledContentRef.current, content)) { if (timerRef.current !== null) { clearTimeout(timerRef.current); timerRef.current = null; } throttledContentRef.current = content; setThrottledContent(content); } } const flush = (0, react.useCallback)(() => { timerRef.current = null; const latest = latestContentRef.current; throttledContentRef.current = latest; setThrottledContent(latest); }, []); (0, react.useEffect)(() => { if (throttledContentRef.current === content) return; if (timerRef.current === null) timerRef.current = setTimeout(flush, THROTTLE_MS); }, [content, flush]); (0, react.useEffect)(() => { return () => { if (timerRef.current !== null) clearTimeout(timerRef.current); }; }, []); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenGenerativeUIActivityRendererInner, { content: throttledContent }); }; function ensureHead(html) { if (/<head[\s>]/i.test(html)) return html; return `<head></head>${html}`; } function injectCssIntoHtml(html, css) { const headCloseIdx = html.indexOf("</head>"); if (headCloseIdx !== -1) return html.slice(0, headCloseIdx) + `<style>${css}</style>` + html.slice(headCloseIdx); return `<head><style>${css}</style></head>${html}`; } const OpenGenerativeUIActivityRendererInner = react.default.memo(function OpenGenerativeUIActivityRendererInner({ content }) { const initialHeight = content.initialHeight ?? 200; const [autoHeight, setAutoHeight] = (0, react.useState)(null); const sandboxFunctions = useSandboxFunctions(); const localApi = (0, react.useMemo)(() => { const api = {}; for (const fn of sandboxFunctions) api[fn.name] = fn.handler; return api; }, [sandboxFunctions]); const fullHtml = content.htmlComplete && content.html?.length ? content.html.join("") : void 0; const css = content.cssComplete ? content.css : void 0; const cssReady = !!content.cssComplete; const partialHtml = !content.htmlComplete && content.html?.length ? content.html.join("") : void 0; const previewBody = partialHtml ? processPartialHtml(partialHtml) : void 0; const previewStyles = partialHtml ? extractCompleteStyles(partialHtml) : ""; const hasPreview = cssReady && !!previewBody?.trim(); const hasVisibleSandbox = !!fullHtml || hasPreview; const containerRef = (0, react.useRef)(null); const sandboxRef = (0, react.useRef)(null); const previewSandboxRef = (0, react.useRef)(null); const previewReadyRef = (0, react.useRef)(false); const san