@copilotkit/react-core 1.61.2 → 1.62.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/dist/{copilotkit-Be-o2UaU.mjs → copilotkit-BtRkFsNR.mjs} +941 -404
  2. package/dist/copilotkit-BtRkFsNR.mjs.map +1 -0
  3. package/dist/{copilotkit-CTCjVxkH.cjs → copilotkit-CdpDZi3i.cjs} +943 -400
  4. package/dist/copilotkit-CdpDZi3i.cjs.map +1 -0
  5. package/dist/{copilotkit-ClqbUuGX.d.cts → copilotkit-Cga6AHO0.d.cts} +429 -149
  6. package/dist/copilotkit-Cga6AHO0.d.cts.map +1 -0
  7. package/dist/{copilotkit-Bpt1c_-q.d.mts → copilotkit-DnLpJ2Cl.d.mts} +429 -149
  8. package/dist/copilotkit-DnLpJ2Cl.d.mts.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.mts +1 -1
  12. package/dist/index.mjs +1 -1
  13. package/dist/index.umd.js +193 -176
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/v2/headless.cjs +156 -11
  16. package/dist/v2/headless.cjs.map +1 -1
  17. package/dist/v2/headless.d.cts +111 -1
  18. package/dist/v2/headless.d.cts.map +1 -1
  19. package/dist/v2/headless.d.mts +111 -1
  20. package/dist/v2/headless.d.mts.map +1 -1
  21. package/dist/v2/headless.mjs +157 -12
  22. package/dist/v2/headless.mjs.map +1 -1
  23. package/dist/v2/index.cjs +2 -1
  24. package/dist/v2/index.css +1 -1
  25. package/dist/v2/index.d.cts +2 -2
  26. package/dist/v2/index.d.mts +2 -2
  27. package/dist/v2/index.mjs +2 -2
  28. package/dist/v2/index.umd.js +949 -412
  29. package/dist/v2/index.umd.js.map +1 -1
  30. package/package.json +8 -6
  31. package/dist/copilotkit-Be-o2UaU.mjs.map +0 -1
  32. package/dist/copilotkit-Bpt1c_-q.d.mts.map +0 -1
  33. package/dist/copilotkit-CTCjVxkH.cjs.map +0 -1
  34. package/dist/copilotkit-ClqbUuGX.d.cts.map +0 -1
@@ -48,6 +48,7 @@ let zod_to_json_schema = require("zod-to-json-schema");
48
48
  let react_dom = require("react-dom");
49
49
  let _tanstack_react_virtual = require("@tanstack/react-virtual");
50
50
  let use_stick_to_bottom = require("use-stick-to-bottom");
51
+ let _copilotkit_web_components_threads_drawer = require("@copilotkit/web-components/threads-drawer");
51
52
  let react_markdown = require("react-markdown");
52
53
  react_markdown = __toESM(react_markdown);
53
54
 
@@ -174,6 +175,25 @@ const CopilotChatDefaultLabels = {
174
175
  modalHeaderTitle: "CopilotKit Chat",
175
176
  welcomeMessageText: "How can I help you today?"
176
177
  };
178
+ /**
179
+ * Mobile breakpoint below which the chat modal and the thread-list drawer are
180
+ * mutually exclusive. At or above this width both surfaces may coexist. This
181
+ * mirrors the `(max-width: 767px)` / `(min-width: 768px)` split already used by
182
+ * CopilotChatInput and CopilotSidebarView.
183
+ */
184
+ const MOBILE_MAX_WIDTH_PX = 767;
185
+ /**
186
+ * Reports whether the current viewport is in the mobile range (`<768px`), where
187
+ * the chat modal and drawer must not be open simultaneously. SSR-safe and
188
+ * defensive against environments without `matchMedia` (treated as desktop, so
189
+ * no mutual-exclusion constraint is applied).
190
+ *
191
+ * @returns `true` when the viewport is mobile-width, `false` otherwise.
192
+ */
193
+ function isMobileViewport() {
194
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
195
+ return window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH_PX}px)`).matches;
196
+ }
177
197
  const CopilotChatConfiguration = (0, react.createContext)(null);
178
198
  const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
179
199
  const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
@@ -184,12 +204,22 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
184
204
  ...stableLabels
185
205
  }), [stableLabels, parentConfig?.labels]);
186
206
  const resolvedAgentId = agentId ?? parentConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
207
+ const threadIdPropIsAuthoritative = threadId !== void 0 && hasExplicitThreadId !== false;
208
+ const isThreadIdControlled = threadIdPropIsAuthoritative;
209
+ const [activeThreadOverride, setActiveThreadOverride] = (0, react.useState)(null);
187
210
  const resolvedThreadId = (0, react.useMemo)(() => {
188
- if (threadId) return threadId;
211
+ if (threadIdPropIsAuthoritative) return threadId;
212
+ if (activeThreadOverride) return activeThreadOverride.threadId;
189
213
  if (parentConfig?.threadId) return parentConfig.threadId;
214
+ if (threadId) return threadId;
190
215
  return (0, _copilotkit_shared.randomUUID)();
191
- }, [threadId, parentConfig?.threadId]);
192
- const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId;
216
+ }, [
217
+ threadIdPropIsAuthoritative,
218
+ threadId,
219
+ parentConfig?.threadId,
220
+ activeThreadOverride
221
+ ]);
222
+ const resolvedHasExplicitThreadId = (threadIdPropIsAuthoritative ? true : activeThreadOverride?.explicit ?? hasExplicitThreadId ?? false) || !!parentConfig?.hasExplicitThreadId;
193
223
  const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen ?? true);
194
224
  const hasExplicitDefault = isModalDefaultOpen !== void 0;
195
225
  const setAndSync = (0, react.useCallback)((open) => {
@@ -208,20 +238,113 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
208
238
  }, [parentConfig?.isModalOpen, hasExplicitDefault]);
209
239
  const resolvedIsModalOpen = hasExplicitDefault ? internalModalOpen : parentConfig?.isModalOpen ?? internalModalOpen;
210
240
  const resolvedSetModalOpen = hasExplicitDefault ? setAndSync : parentConfig?.setModalOpen ?? setInternalModalOpen;
241
+ const [ownDrawerOpen, setOwnDrawerOpen] = (0, react.useState)(false);
242
+ const [ownDrawerCount, setOwnDrawerCount] = (0, react.useState)(0);
243
+ const modalCloseRef = (0, react.useRef)(() => {});
244
+ modalCloseRef.current = resolvedSetModalOpen;
245
+ const registeredModalClosersRef = (0, react.useRef)([]);
246
+ const ownRegisterModalCloser = (0, react.useCallback)((closeModal) => {
247
+ registeredModalClosersRef.current.push(closeModal);
248
+ return () => {
249
+ registeredModalClosersRef.current = registeredModalClosersRef.current.filter((entry) => entry !== closeModal);
250
+ };
251
+ }, []);
252
+ const ownSetDrawerOpen = (0, react.useCallback)((open) => {
253
+ setOwnDrawerOpen(open);
254
+ if (open && isMobileViewport()) {
255
+ const registered = registeredModalClosersRef.current;
256
+ (registered.length > 0 ? registered[registered.length - 1] : modalCloseRef.current)(false);
257
+ }
258
+ }, []);
259
+ const ownRegisterDrawer = (0, react.useCallback)(() => {
260
+ setOwnDrawerCount((count) => count + 1);
261
+ return () => {
262
+ setOwnDrawerCount((count) => Math.max(0, count - 1));
263
+ };
264
+ }, []);
265
+ const resolvedDrawerOpen = parentConfig ? parentConfig.drawerOpen : ownDrawerOpen;
266
+ const resolvedSetDrawerOpen = parentConfig ? parentConfig.setDrawerOpen : ownSetDrawerOpen;
267
+ const resolvedDrawerRegistered = parentConfig ? parentConfig.drawerRegistered : ownDrawerCount > 0;
268
+ const resolvedRegisterDrawer = parentConfig ? parentConfig.registerDrawer : ownRegisterDrawer;
269
+ const resolvedRegisterModalCloser = parentConfig ? parentConfig.ɵregisterModalCloser : ownRegisterModalCloser;
270
+ (0, react.useEffect)(() => {
271
+ if (!hasExplicitDefault) return;
272
+ return resolvedRegisterModalCloser(resolvedSetModalOpen);
273
+ }, [
274
+ hasExplicitDefault,
275
+ resolvedRegisterModalCloser,
276
+ resolvedSetModalOpen
277
+ ]);
278
+ const isThreadIdControlledRef = (0, react.useRef)(isThreadIdControlled);
279
+ isThreadIdControlledRef.current = isThreadIdControlled;
280
+ const ownSetActiveThreadId = (0, react.useCallback)((id, options) => {
281
+ setActiveThreadOverride({
282
+ threadId: id,
283
+ explicit: options?.explicit ?? true
284
+ });
285
+ }, []);
286
+ const ownStartNewThread = (0, react.useCallback)(() => {
287
+ setActiveThreadOverride({
288
+ threadId: (0, _copilotkit_shared.randomUUID)(),
289
+ explicit: false
290
+ });
291
+ }, []);
292
+ const parentSetActiveThreadId = parentConfig?.setActiveThreadId;
293
+ const parentStartNewThread = parentConfig?.startNewThread;
294
+ const resolvedSetActiveThreadId = (0, react.useCallback)((id, options) => {
295
+ if (isThreadIdControlledRef.current) {
296
+ console.warn("[CopilotKit] Ignoring setActiveThreadId(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider.");
297
+ return;
298
+ }
299
+ if (parentSetActiveThreadId) {
300
+ parentSetActiveThreadId(id, options);
301
+ return;
302
+ }
303
+ ownSetActiveThreadId(id, options);
304
+ }, [parentSetActiveThreadId, ownSetActiveThreadId]);
305
+ const resolvedStartNewThread = (0, react.useCallback)(() => {
306
+ if (isThreadIdControlledRef.current) {
307
+ console.warn("[CopilotKit] Ignoring startNewThread(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider.");
308
+ return;
309
+ }
310
+ if (parentStartNewThread) {
311
+ parentStartNewThread();
312
+ return;
313
+ }
314
+ ownStartNewThread();
315
+ }, [parentStartNewThread, ownStartNewThread]);
316
+ const setModalOpenWithDrawerExclusion = (0, react.useCallback)((open) => {
317
+ if (open && isMobileViewport()) resolvedSetDrawerOpen(false);
318
+ resolvedSetModalOpen(open);
319
+ }, [resolvedSetModalOpen, resolvedSetDrawerOpen]);
211
320
  const configurationValue = (0, react.useMemo)(() => ({
212
321
  labels: mergedLabels,
213
322
  agentId: resolvedAgentId,
214
323
  threadId: resolvedThreadId,
215
324
  hasExplicitThreadId: resolvedHasExplicitThreadId,
216
325
  isModalOpen: resolvedIsModalOpen,
217
- setModalOpen: resolvedSetModalOpen
326
+ setModalOpen: setModalOpenWithDrawerExclusion,
327
+ drawerOpen: resolvedDrawerOpen,
328
+ setDrawerOpen: resolvedSetDrawerOpen,
329
+ drawerRegistered: resolvedDrawerRegistered,
330
+ registerDrawer: resolvedRegisterDrawer,
331
+ ɵregisterModalCloser: resolvedRegisterModalCloser,
332
+ setActiveThreadId: resolvedSetActiveThreadId,
333
+ startNewThread: resolvedStartNewThread
218
334
  }), [
219
335
  mergedLabels,
220
336
  resolvedAgentId,
221
337
  resolvedThreadId,
222
338
  resolvedHasExplicitThreadId,
223
339
  resolvedIsModalOpen,
224
- resolvedSetModalOpen
340
+ setModalOpenWithDrawerExclusion,
341
+ resolvedDrawerOpen,
342
+ resolvedSetDrawerOpen,
343
+ resolvedDrawerRegistered,
344
+ resolvedRegisterDrawer,
345
+ resolvedRegisterModalCloser,
346
+ resolvedSetActiveThreadId,
347
+ resolvedStartNewThread
225
348
  ]);
226
349
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfiguration.Provider, {
227
350
  value: configurationValue,
@@ -1142,7 +1265,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1142
1265
  },
1143
1266
  ...props,
1144
1267
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1145
- className: "cpk:max-w-3xl cpk:mx-auto cpk:py-0 cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-4 cpk:pointer-events-auto",
1268
+ className: "cpk:max-w-3xl cpk:mx-auto cpk:py-0 cpk:px-4 cpk:@3xl:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-4 cpk:pointer-events-auto",
1146
1269
  children: inputPill
1147
1270
  }), shouldShowDisclaimer && BoundDisclaimer]
1148
1271
  });
@@ -1428,309 +1551,27 @@ const useCopilotKit = () => {
1428
1551
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
1429
1552
  if (!context) throw new Error("useCopilotKit must be used within CopilotKitProvider");
1430
1553
  (0, react.useEffect)(() => {
1431
- const subscription = context.copilotkit.subscribe({
1432
- onRuntimeConnectionStatusChanged: () => {
1433
- forceUpdate();
1434
- },
1435
- onHeadersChanged: () => {
1436
- forceUpdate();
1437
- }
1438
- });
1439
- return () => {
1440
- subscription.unsubscribe();
1441
- };
1442
- }, []);
1443
- return context;
1444
- };
1445
- const LicenseContext = (0, react.createContext)({
1446
- status: null,
1447
- license: null,
1448
- checkFeature: () => true,
1449
- getLimit: () => null
1450
- });
1451
- const useLicenseContext = () => (0, react.useContext)(LicenseContext);
1452
-
1453
- //#endregion
1454
- //#region src/v2/types/defineToolCallRenderer.ts
1455
- function defineToolCallRenderer(def) {
1456
- const argsSchema = def.name === "*" && !def.args ? zod.z.any() : def.args;
1457
- return {
1458
- name: def.name,
1459
- args: argsSchema,
1460
- render: def.render,
1461
- ...def.agentId ? { agentId: def.agentId } : {}
1462
- };
1463
- }
1464
-
1465
- //#endregion
1466
- //#region src/v2/hooks/use-render-tool.tsx
1467
- const EMPTY_DEPS$1 = [];
1468
- /**
1469
- * Registers a renderer entry in CopilotKit's `renderToolCalls` registry.
1470
- *
1471
- * Key behavior:
1472
- * - deduplicates by `agentId:name` (latest registration wins),
1473
- * - keeps renderer entries on cleanup so historical chat tool calls can still render,
1474
- * - refreshes registration when `deps` change.
1475
- *
1476
- * @typeParam S - Schema type describing tool call parameters.
1477
- * @param config - Renderer config for wildcard or named tools.
1478
- * @param deps - Optional dependencies to refresh registration.
1479
- *
1480
- * @example
1481
- * ```tsx
1482
- * useRenderTool(
1483
- * {
1484
- * name: "searchDocs",
1485
- * parameters: z.object({ query: z.string() }),
1486
- * render: ({ status, parameters, result }) => {
1487
- * if (status === "executing") return <div>Searching {parameters.query}</div>;
1488
- * if (status === "complete") return <div>{result}</div>;
1489
- * return <div>Preparing...</div>;
1490
- * },
1491
- * },
1492
- * [],
1493
- * );
1494
- * ```
1495
- *
1496
- * @example
1497
- * ```tsx
1498
- * useRenderTool(
1499
- * {
1500
- * name: "summarize",
1501
- * parameters: z.object({ text: z.string() }),
1502
- * agentId: "research-agent",
1503
- * render: ({ name, status }) => <div>{name}: {status}</div>,
1504
- * },
1505
- * [selectedAgentId],
1506
- * );
1507
- * ```
1508
- */
1509
- function useRenderTool(config, deps) {
1510
- const { copilotkit } = useCopilotKit();
1511
- const extraDeps = deps ?? EMPTY_DEPS$1;
1512
- (0, react.useEffect)(() => {
1513
- const renderer = config.name === "*" && !config.parameters ? defineToolCallRenderer({
1514
- name: "*",
1515
- render: (props) => config.render({
1516
- ...props,
1517
- parameters: props.args
1518
- }),
1519
- ...config.agentId ? { agentId: config.agentId } : {}
1520
- }) : defineToolCallRenderer({
1521
- name: config.name,
1522
- args: config.parameters,
1523
- render: (props) => {
1524
- if (props.status === _copilotkit_core.ToolCallStatus.InProgress) return config.render({
1525
- ...props,
1526
- parameters: props.args
1527
- });
1528
- if (props.status === _copilotkit_core.ToolCallStatus.Executing) return config.render({
1529
- ...props,
1530
- parameters: props.args
1531
- });
1532
- return config.render({
1533
- ...props,
1534
- parameters: props.args
1535
- });
1536
- },
1537
- ...config.agentId ? { agentId: config.agentId } : {}
1538
- });
1539
- copilotkit.addHookRenderToolCall(renderer);
1540
- }, [
1541
- config.name,
1542
- copilotkit,
1543
- JSON.stringify(extraDeps)
1544
- ]);
1545
- }
1546
-
1547
- //#endregion
1548
- //#region src/v2/hooks/use-default-render-tool.tsx
1549
- /**
1550
- * Module-level dedup set so an unknown status value only emits a console
1551
- * warning the FIRST time we encounter it. Otherwise a stuck/unmapped status
1552
- * would log on every re-render (potentially many per second).
1553
- */
1554
- const warnedUnknownStatuses = /* @__PURE__ */ new Set();
1555
- /**
1556
- * Map a {@link ToolCallStatus} enum value to the documented string-union
1557
- * status the {@link DefaultRenderProps} contract exposes. Unknown / future
1558
- * enum members log a warning (once per distinct value) and fall back to
1559
- * `"inProgress"`.
1560
- */
1561
- function mapToolCallStatus(status) {
1562
- switch (status) {
1563
- case _copilotkit_core.ToolCallStatus.Complete: return "complete";
1564
- case _copilotkit_core.ToolCallStatus.Executing: return "executing";
1565
- case _copilotkit_core.ToolCallStatus.InProgress: return "inProgress";
1566
- default: {
1567
- const key = String(status);
1568
- if (!warnedUnknownStatuses.has(key)) {
1569
- warnedUnknownStatuses.add(key);
1570
- console.warn(`[CopilotKit] Unknown ToolCallStatus "${key}" in default tool-call renderer; falling back to "inProgress".`);
1571
- }
1572
- return "inProgress";
1573
- }
1574
- }
1575
- }
1576
- /**
1577
- * Convert the framework-internal renderer props (`args`, enum status) into
1578
- * the documented {@link DefaultRenderProps} shape (`parameters`, string-union
1579
- * status) so a user `config.render` always sees the documented contract.
1580
- */
1581
- function adaptRendererProps(props) {
1582
- return {
1583
- name: props.name,
1584
- toolCallId: props.toolCallId,
1585
- parameters: props.args,
1586
- status: mapToolCallStatus(props.status),
1587
- result: props.result
1588
- };
1589
- }
1590
- /**
1591
- * Registers a wildcard (`"*"`) tool-call renderer via `useRenderTool`.
1592
- *
1593
- * - Call with no config to use CopilotKit's built-in default tool-call card.
1594
- * - Pass `config.render` to replace the default UI with your own fallback renderer.
1595
- *
1596
- * This is useful when you want a generic renderer for tools that do not have a
1597
- * dedicated `useRenderTool({ name: "..." })` registration.
1598
- *
1599
- * @param config - Optional custom wildcard render function.
1600
- * @param deps - Optional dependencies to refresh registration.
1601
- *
1602
- * @example
1603
- * ```tsx
1604
- * useDefaultRenderTool();
1605
- * ```
1606
- *
1607
- * @example
1608
- * ```tsx
1609
- * useDefaultRenderTool({
1610
- * render: ({ name, status }) => <div>{name}: {status}</div>,
1611
- * });
1612
- * ```
1613
- *
1614
- * @example
1615
- * ```tsx
1616
- * useDefaultRenderTool(
1617
- * {
1618
- * render: ({ name, result }) => (
1619
- * <ToolEventRow title={name} payload={result} compact={compactMode} />
1620
- * ),
1621
- * },
1622
- * [compactMode],
1623
- * );
1624
- * ```
1625
- */
1626
- function useDefaultRenderTool(config, deps) {
1627
- const userRender = config?.render;
1628
- useRenderTool({
1629
- name: "*",
1630
- render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) })
1631
- }, deps);
1632
- }
1633
- /**
1634
- * Guarded JSON.stringify used inside the expanded `<pre>` blocks. A circular
1635
- * reference would otherwise crash the entire React tree on render.
1636
- */
1637
- function safeStringifyForPre(value) {
1638
- try {
1639
- return JSON.stringify(value, null, 2);
1640
- } catch (err) {
1641
- console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for default renderer; falling back to String():", err);
1642
- try {
1643
- return String(value);
1644
- } catch (innerErr) {
1645
- console.warn("[CopilotKit] safeStringifyForPre: value could not be stringified:", innerErr);
1646
- return "[unserializable]";
1647
- }
1648
- }
1649
- }
1650
- function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
1651
- const [isExpanded, setIsExpanded] = (0, react.useState)(false);
1652
- const isActive = status === "inProgress" || status === "executing";
1653
- const isComplete = status === "complete";
1654
- const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
1655
- const dotClassName = isActive ? "cpk:bg-amber-500" : isComplete ? "cpk:bg-emerald-500" : "cpk:bg-zinc-400";
1656
- const badgeClassName = isActive ? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400" : isComplete ? "cpk:bg-emerald-100 cpk:text-emerald-800 cpk:dark:bg-emerald-500/15 cpk:dark:text-emerald-400" : "cpk:bg-zinc-100 cpk:text-zinc-800 cpk:dark:bg-zinc-700/40 cpk:dark:text-zinc-300";
1657
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1658
- "data-testid": "copilot-tool-render",
1659
- "data-tool-name": name,
1660
- "data-tool-call-id": toolCallId,
1661
- "data-status": status,
1662
- "data-args": safeStringifyForAttr(parameters),
1663
- "data-result": safeStringifyForAttr(result),
1664
- className: "cpk:mt-2 cpk:pb-2",
1665
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1666
- className: "cpk:rounded-xl cpk:border cpk:border-zinc-200/60 cpk:bg-white/70 cpk:p-4 cpk:shadow-sm cpk:backdrop-blur cpk:dark:border-zinc-800/60 cpk:dark:bg-zinc-900/50",
1667
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1668
- type: "button",
1669
- "aria-expanded": isExpanded,
1670
- onClick: () => setIsExpanded(!isExpanded),
1671
- className: "cpk:flex cpk:w-full cpk:cursor-pointer cpk:select-none cpk:items-center cpk:justify-between cpk:gap-2.5 cpk:border-none cpk:bg-transparent cpk:p-0 cpk:m-0 cpk:text-left cpk:text-inherit",
1672
- style: { font: "inherit" },
1673
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1674
- className: "cpk:flex cpk:min-w-0 cpk:items-center cpk:gap-2",
1675
- children: [
1676
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
1677
- className: `cpk:h-3.5 cpk:w-3.5 cpk:flex-shrink-0 cpk:text-zinc-500 cpk:transition-transform cpk:dark:text-zinc-400 ${isExpanded ? "cpk:rotate-90" : ""}`,
1678
- fill: "none",
1679
- viewBox: "0 0 24 24",
1680
- strokeWidth: 2,
1681
- stroke: "currentColor",
1682
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
1683
- strokeLinecap: "round",
1684
- strokeLinejoin: "round",
1685
- d: "M8.25 4.5l7.5 7.5-7.5 7.5"
1686
- })
1687
- }),
1688
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `cpk:inline-block cpk:h-2 cpk:w-2 cpk:flex-shrink-0 cpk:rounded-full ${dotClassName}` }),
1689
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1690
- "data-testid": "copilot-tool-render-name",
1691
- className: "cpk:truncate cpk:text-[13px] cpk:font-semibold cpk:text-zinc-900 cpk:dark:text-zinc-100",
1692
- children: name
1693
- })
1694
- ]
1695
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1696
- "data-testid": "copilot-tool-render-status",
1697
- className: `cpk:inline-flex cpk:flex-shrink-0 cpk:items-center cpk:rounded-full cpk:px-2 cpk:py-0.5 cpk:text-[11px] cpk:font-medium ${badgeClassName}`,
1698
- children: statusLabel
1699
- })]
1700
- }), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1701
- className: "cpk:mt-3 cpk:grid cpk:gap-3",
1702
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1703
- className: "cpk:text-[10px] cpk:uppercase cpk:text-zinc-500 cpk:dark:text-zinc-400",
1704
- children: "Arguments"
1705
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1706
- className: "cpk:mt-1.5 cpk:max-h-[200px] cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-100 cpk:p-2.5 cpk:text-[11px] cpk:leading-relaxed cpk:text-zinc-800 cpk:whitespace-pre-wrap cpk:break-words cpk:dark:bg-zinc-800/60 cpk:dark:text-zinc-200",
1707
- children: safeStringifyForPre(parameters ?? {})
1708
- })] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1709
- className: "cpk:text-[10px] cpk:uppercase cpk:text-zinc-500 cpk:dark:text-zinc-400",
1710
- children: "Result"
1711
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
1712
- className: "cpk:mt-1.5 cpk:max-h-[200px] cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-100 cpk:p-2.5 cpk:text-[11px] cpk:leading-relaxed cpk:text-zinc-800 cpk:whitespace-pre-wrap cpk:break-words cpk:dark:bg-zinc-800/60 cpk:dark:text-zinc-200",
1713
- children: typeof result === "string" ? result : safeStringifyForPre(result)
1714
- })] })]
1715
- })]
1716
- })
1717
- });
1718
- }
1719
- function safeStringifyForAttr(value) {
1720
- if (value === void 0 || value === null) return "";
1721
- if (typeof value === "string") return value;
1722
- try {
1723
- return JSON.stringify(value);
1724
- } catch (err) {
1725
- console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for data-* attribute; falling back to String():", err);
1726
- try {
1727
- return String(value);
1728
- } catch (innerErr) {
1729
- console.warn("[CopilotKit] safeStringifyForAttr: value could not be stringified:", innerErr);
1730
- return "";
1731
- }
1732
- }
1733
- }
1554
+ const subscription = context.copilotkit.subscribe({
1555
+ onRuntimeConnectionStatusChanged: () => {
1556
+ forceUpdate();
1557
+ },
1558
+ onHeadersChanged: () => {
1559
+ forceUpdate();
1560
+ }
1561
+ });
1562
+ return () => {
1563
+ subscription.unsubscribe();
1564
+ };
1565
+ }, []);
1566
+ return context;
1567
+ };
1568
+ const LicenseContext = (0, react.createContext)({
1569
+ status: null,
1570
+ license: null,
1571
+ checkFeature: () => true,
1572
+ getLimit: () => null
1573
+ });
1574
+ const useLicenseContext = () => (0, react.useContext)(LicenseContext);
1734
1575
 
1735
1576
  //#endregion
1736
1577
  //#region src/v2/hooks/use-render-tool-call.tsx
@@ -1786,10 +1627,13 @@ function useRenderToolCall() {
1786
1627
  }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
1787
1628
  return (0, react.useCallback)(({ toolCall, toolMessage }) => {
1788
1629
  const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
1630
+ const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
1631
+ if (!renderConfig) return null;
1632
+ const RenderComponent = renderConfig.render;
1789
1633
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRenderer, {
1790
1634
  toolCall,
1791
1635
  toolMessage,
1792
- RenderComponent: (exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*"))?.render ?? defaultToolCallRenderAdapter,
1636
+ RenderComponent,
1793
1637
  isExecuting: executingToolCallIds.has(toolCall.id)
1794
1638
  }, toolCall.id);
1795
1639
  }, [
@@ -1798,15 +1642,6 @@ function useRenderToolCall() {
1798
1642
  agentId
1799
1643
  ]);
1800
1644
  }
1801
- function defaultToolCallRenderAdapter(props) {
1802
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, {
1803
- name: props.name,
1804
- toolCallId: props.toolCallId,
1805
- parameters: props.args,
1806
- status: mapToolCallStatus(props.status),
1807
- result: props.result
1808
- });
1809
- }
1810
1645
 
1811
1646
  //#endregion
1812
1647
  //#region src/v2/components/CopilotKitInspector.tsx
@@ -3288,7 +3123,7 @@ const A2UISurfaceContentSchema = zod.z.object({
3288
3123
  ...A2UILifecycleFields
3289
3124
  }).passthrough();
3290
3125
  function createA2UIMessageRenderer(options) {
3291
- const { theme, catalog, loadingComponent, recovery } = options;
3126
+ const { theme, catalog, loadingComponent, recovery, onAction } = options;
3292
3127
  const showAfterMs = recovery?.showAfterMs ?? 2e3;
3293
3128
  const showAfterAttempts = recovery?.showAfterAttempts ?? 2;
3294
3129
  const optionDebugExposure = recovery?.debugExposure ?? "collapsed";
@@ -3364,6 +3199,7 @@ function createA2UIMessageRenderer(options) {
3364
3199
  agent,
3365
3200
  copilotkit,
3366
3201
  catalog,
3202
+ onAction,
3367
3203
  onReady: markSurfaceReady
3368
3204
  }, surfaceId))
3369
3205
  });
@@ -3384,29 +3220,60 @@ function createA2UIMessageRenderer(options) {
3384
3220
  };
3385
3221
  }
3386
3222
  /**
3223
+ * Orchestrates a single A2UI user action: runs the optional `onAction`
3224
+ * interceptor first, then forwards to the agent unless the interceptor
3225
+ * suppressed it (returned `null`). Exported for unit testing; the wiring lives
3226
+ * in {@link ReactSurfaceHost}.
3227
+ */
3228
+ async function runA2UIAction({ message, agent, copilotkit, onAction }) {
3229
+ if (!agent) return;
3230
+ const action = message.userAction;
3231
+ const forward = async (forwardAction) => {
3232
+ const a2uiAction = forwardAction !== void 0 ? {
3233
+ ...message,
3234
+ userAction: forwardAction
3235
+ } : message;
3236
+ try {
3237
+ copilotkit.setProperties({
3238
+ ...copilotkit.properties,
3239
+ a2uiAction
3240
+ });
3241
+ await copilotkit.runAgent({ agent });
3242
+ } finally {
3243
+ if (copilotkit.properties) {
3244
+ const { a2uiAction: _omit, ...rest } = copilotkit.properties;
3245
+ copilotkit.setProperties(rest);
3246
+ }
3247
+ }
3248
+ };
3249
+ if (onAction && action) {
3250
+ const result = await onAction(action, forward);
3251
+ if (result === null) return;
3252
+ if (result) {
3253
+ await forward(result);
3254
+ return;
3255
+ }
3256
+ }
3257
+ await forward();
3258
+ }
3259
+ /**
3387
3260
  * Renders a single A2UI surface using the React renderer.
3388
3261
  * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
3389
3262
  */
3390
- function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog, onReady }) {
3263
+ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog, onAction, onReady }) {
3391
3264
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
3392
3265
  className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
3393
3266
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
3394
- onAction: (0, react.useCallback)(async (message) => {
3395
- if (!agent) return;
3396
- message.userAction;
3397
- try {
3398
- copilotkit.setProperties({
3399
- ...copilotkit.properties,
3400
- a2uiAction: message
3401
- });
3402
- await copilotkit.runAgent({ agent });
3403
- } finally {
3404
- if (copilotkit.properties) {
3405
- const { a2uiAction, ...rest } = copilotkit.properties;
3406
- copilotkit.setProperties(rest);
3407
- }
3408
- }
3409
- }, [agent, copilotkit]),
3267
+ onAction: (0, react.useCallback)((message) => runA2UIAction({
3268
+ message,
3269
+ agent,
3270
+ copilotkit,
3271
+ onAction
3272
+ }), [
3273
+ agent,
3274
+ copilotkit,
3275
+ onAction
3276
+ ]),
3410
3277
  theme,
3411
3278
  catalog,
3412
3279
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
@@ -3477,6 +3344,18 @@ function getOperationSurfaceId(operation) {
3477
3344
  return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
3478
3345
  }
3479
3346
 
3347
+ //#endregion
3348
+ //#region src/v2/types/defineToolCallRenderer.ts
3349
+ function defineToolCallRenderer(def) {
3350
+ const argsSchema = def.name === "*" && !def.args ? zod.z.any() : def.args;
3351
+ return {
3352
+ name: def.name,
3353
+ args: argsSchema,
3354
+ render: def.render,
3355
+ ...def.agentId ? { agentId: def.agentId } : {}
3356
+ };
3357
+ }
3358
+
3480
3359
  //#endregion
3481
3360
  //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
3482
3361
  /**
@@ -4030,10 +3909,10 @@ function useRenderActivityMessage() {
4030
3909
 
4031
3910
  //#endregion
4032
3911
  //#region src/v2/hooks/use-frontend-tool.tsx
4033
- const EMPTY_DEPS = [];
3912
+ const EMPTY_DEPS$1 = [];
4034
3913
  function useFrontendTool(tool, deps) {
4035
3914
  const { copilotkit } = useCopilotKit();
4036
- const extraDeps = deps ?? EMPTY_DEPS;
3915
+ const extraDeps = deps ?? EMPTY_DEPS$1;
4037
3916
  (0, react.useEffect)(() => {
4038
3917
  const name = tool.name;
4039
3918
  if (copilotkit.getTool({
@@ -4064,70 +3943,340 @@ function useFrontendTool(tool, deps) {
4064
3943
  //#endregion
4065
3944
  //#region src/v2/hooks/use-component.tsx
4066
3945
  /**
4067
- * Registers a React component as a frontend tool renderer in chat.
4068
- *
4069
- * This hook is a convenience wrapper around `useFrontendTool` that:
4070
- * - builds a model-facing tool description,
4071
- * - forwards optional schema parameters (any Standard Schema V1 compatible library),
4072
- * - renders your component with tool call parameters.
3946
+ * Registers a React component as a frontend tool renderer in chat.
3947
+ *
3948
+ * This hook is a convenience wrapper around `useFrontendTool` that:
3949
+ * - builds a model-facing tool description,
3950
+ * - forwards optional schema parameters (any Standard Schema V1 compatible library),
3951
+ * - renders your component with tool call parameters.
3952
+ *
3953
+ * Use this when you want to display a typed visual component for a tool call
3954
+ * without manually wiring a full frontend tool object.
3955
+ *
3956
+ * When `parameters` is provided, render props are inferred from the schema.
3957
+ * When omitted, the render component may accept any props.
3958
+ *
3959
+ * @typeParam TSchema - Schema describing tool parameters, or `undefined` when no schema is given.
3960
+ * @param config - Tool registration config.
3961
+ * @param deps - Optional dependencies to refresh registration (same semantics as `useEffect`).
3962
+ *
3963
+ * @example
3964
+ * ```tsx
3965
+ * // Without parameters — render accepts any props
3966
+ * useComponent({
3967
+ * name: "showGreeting",
3968
+ * render: ({ message }: { message: string }) => <div>{message}</div>,
3969
+ * });
3970
+ * ```
3971
+ *
3972
+ * @example
3973
+ * ```tsx
3974
+ * // With parameters — render props inferred from schema
3975
+ * useComponent({
3976
+ * name: "showWeatherCard",
3977
+ * parameters: z.object({ city: z.string() }),
3978
+ * render: ({ city }) => <div>{city}</div>,
3979
+ * });
3980
+ * ```
3981
+ *
3982
+ * @example
3983
+ * ```tsx
3984
+ * useComponent(
3985
+ * {
3986
+ * name: "renderProfile",
3987
+ * parameters: z.object({ userId: z.string() }),
3988
+ * render: ProfileCard,
3989
+ * agentId: "support-agent",
3990
+ * },
3991
+ * [selectedAgentId],
3992
+ * );
3993
+ * ```
3994
+ */
3995
+ function useComponent(config, deps) {
3996
+ const prefix = `Use this tool to display the "${config.name}" component in the chat. This tool renders a visual UI component for the user.`;
3997
+ const fullDescription = config.description ? `${prefix}\n\n${config.description}` : prefix;
3998
+ useFrontendTool({
3999
+ name: config.name,
4000
+ description: fullDescription,
4001
+ parameters: config.parameters,
4002
+ render: ({ args }) => {
4003
+ const Component = config.render;
4004
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...args });
4005
+ },
4006
+ agentId: config.agentId,
4007
+ followUp: config.followUp
4008
+ }, deps);
4009
+ }
4010
+
4011
+ //#endregion
4012
+ //#region src/v2/hooks/use-render-tool.tsx
4013
+ const EMPTY_DEPS = [];
4014
+ /**
4015
+ * Registers a renderer entry in CopilotKit's `renderToolCalls` registry.
4016
+ *
4017
+ * Key behavior:
4018
+ * - deduplicates by `agentId:name` (latest registration wins),
4019
+ * - keeps renderer entries on cleanup so historical chat tool calls can still render,
4020
+ * - refreshes registration when `deps` change.
4021
+ *
4022
+ * @typeParam S - Schema type describing tool call parameters.
4023
+ * @param config - Renderer config for wildcard or named tools.
4024
+ * @param deps - Optional dependencies to refresh registration.
4025
+ *
4026
+ * @example
4027
+ * ```tsx
4028
+ * useRenderTool(
4029
+ * {
4030
+ * name: "searchDocs",
4031
+ * parameters: z.object({ query: z.string() }),
4032
+ * render: ({ status, parameters, result }) => {
4033
+ * if (status === "executing") return <div>Searching {parameters.query}</div>;
4034
+ * if (status === "complete") return <div>{result}</div>;
4035
+ * return <div>Preparing...</div>;
4036
+ * },
4037
+ * },
4038
+ * [],
4039
+ * );
4040
+ * ```
4041
+ *
4042
+ * @example
4043
+ * ```tsx
4044
+ * useRenderTool(
4045
+ * {
4046
+ * name: "summarize",
4047
+ * parameters: z.object({ text: z.string() }),
4048
+ * agentId: "research-agent",
4049
+ * render: ({ name, status }) => <div>{name}: {status}</div>,
4050
+ * },
4051
+ * [selectedAgentId],
4052
+ * );
4053
+ * ```
4054
+ */
4055
+ function useRenderTool(config, deps) {
4056
+ const { copilotkit } = useCopilotKit();
4057
+ const extraDeps = deps ?? EMPTY_DEPS;
4058
+ (0, react.useEffect)(() => {
4059
+ const renderer = config.name === "*" && !config.parameters ? defineToolCallRenderer({
4060
+ name: "*",
4061
+ render: (props) => config.render({
4062
+ ...props,
4063
+ parameters: props.args
4064
+ }),
4065
+ ...config.agentId ? { agentId: config.agentId } : {}
4066
+ }) : defineToolCallRenderer({
4067
+ name: config.name,
4068
+ args: config.parameters,
4069
+ render: (props) => {
4070
+ if (props.status === _copilotkit_core.ToolCallStatus.InProgress) return config.render({
4071
+ ...props,
4072
+ parameters: props.args
4073
+ });
4074
+ if (props.status === _copilotkit_core.ToolCallStatus.Executing) return config.render({
4075
+ ...props,
4076
+ parameters: props.args
4077
+ });
4078
+ return config.render({
4079
+ ...props,
4080
+ parameters: props.args
4081
+ });
4082
+ },
4083
+ ...config.agentId ? { agentId: config.agentId } : {}
4084
+ });
4085
+ copilotkit.addHookRenderToolCall(renderer);
4086
+ }, [
4087
+ config.name,
4088
+ copilotkit,
4089
+ JSON.stringify(extraDeps)
4090
+ ]);
4091
+ }
4092
+
4093
+ //#endregion
4094
+ //#region src/v2/hooks/use-default-render-tool.tsx
4095
+ /**
4096
+ * Module-level dedup set so an unknown status value only emits a console
4097
+ * warning the FIRST time we encounter it. Otherwise a stuck/unmapped status
4098
+ * would log on every re-render (potentially many per second).
4099
+ */
4100
+ const warnedUnknownStatuses = /* @__PURE__ */ new Set();
4101
+ /**
4102
+ * Map a {@link ToolCallStatus} enum value to the documented string-union
4103
+ * status the {@link DefaultRenderProps} contract exposes. Unknown / future
4104
+ * enum members log a warning (once per distinct value) and fall back to
4105
+ * `"inProgress"`.
4106
+ */
4107
+ function mapToolCallStatus(status) {
4108
+ switch (status) {
4109
+ case _copilotkit_core.ToolCallStatus.Complete: return "complete";
4110
+ case _copilotkit_core.ToolCallStatus.Executing: return "executing";
4111
+ case _copilotkit_core.ToolCallStatus.InProgress: return "inProgress";
4112
+ default: {
4113
+ const key = String(status);
4114
+ if (!warnedUnknownStatuses.has(key)) {
4115
+ warnedUnknownStatuses.add(key);
4116
+ console.warn(`[CopilotKit] Unknown ToolCallStatus "${key}" in default tool-call renderer; falling back to "inProgress".`);
4117
+ }
4118
+ return "inProgress";
4119
+ }
4120
+ }
4121
+ }
4122
+ /**
4123
+ * Convert the framework-internal renderer props (`args`, enum status) into
4124
+ * the documented {@link DefaultRenderProps} shape (`parameters`, string-union
4125
+ * status) so a user `config.render` always sees the documented contract.
4126
+ */
4127
+ function adaptRendererProps(props) {
4128
+ return {
4129
+ name: props.name,
4130
+ toolCallId: props.toolCallId,
4131
+ parameters: props.args,
4132
+ status: mapToolCallStatus(props.status),
4133
+ result: props.result
4134
+ };
4135
+ }
4136
+ /**
4137
+ * Registers a wildcard (`"*"`) tool-call renderer via `useRenderTool`.
4073
4138
  *
4074
- * Use this when you want to display a typed visual component for a tool call
4075
- * without manually wiring a full frontend tool object.
4139
+ * - Call with no config to use CopilotKit's built-in default tool-call card.
4140
+ * - Pass `config.render` to replace the default UI with your own fallback renderer.
4076
4141
  *
4077
- * When `parameters` is provided, render props are inferred from the schema.
4078
- * When omitted, the render component may accept any props.
4142
+ * This is useful when you want a generic renderer for tools that do not have a
4143
+ * dedicated `useRenderTool({ name: "..." })` registration.
4079
4144
  *
4080
- * @typeParam TSchema - Schema describing tool parameters, or `undefined` when no schema is given.
4081
- * @param config - Tool registration config.
4082
- * @param deps - Optional dependencies to refresh registration (same semantics as `useEffect`).
4145
+ * @param config - Optional custom wildcard render function.
4146
+ * @param deps - Optional dependencies to refresh registration.
4083
4147
  *
4084
4148
  * @example
4085
4149
  * ```tsx
4086
- * // Without parameters — render accepts any props
4087
- * useComponent({
4088
- * name: "showGreeting",
4089
- * render: ({ message }: { message: string }) => <div>{message}</div>,
4090
- * });
4150
+ * useDefaultRenderTool();
4091
4151
  * ```
4092
4152
  *
4093
4153
  * @example
4094
4154
  * ```tsx
4095
- * // With parameters — render props inferred from schema
4096
- * useComponent({
4097
- * name: "showWeatherCard",
4098
- * parameters: z.object({ city: z.string() }),
4099
- * render: ({ city }) => <div>{city}</div>,
4155
+ * useDefaultRenderTool({
4156
+ * render: ({ name, status }) => <div>{name}: {status}</div>,
4100
4157
  * });
4101
4158
  * ```
4102
4159
  *
4103
4160
  * @example
4104
4161
  * ```tsx
4105
- * useComponent(
4162
+ * useDefaultRenderTool(
4106
4163
  * {
4107
- * name: "renderProfile",
4108
- * parameters: z.object({ userId: z.string() }),
4109
- * render: ProfileCard,
4110
- * agentId: "support-agent",
4164
+ * render: ({ name, result }) => (
4165
+ * <ToolEventRow title={name} payload={result} compact={compactMode} />
4166
+ * ),
4111
4167
  * },
4112
- * [selectedAgentId],
4168
+ * [compactMode],
4113
4169
  * );
4114
4170
  * ```
4115
4171
  */
4116
- function useComponent(config, deps) {
4117
- const prefix = `Use this tool to display the "${config.name}" component in the chat. This tool renders a visual UI component for the user.`;
4118
- const fullDescription = config.description ? `${prefix}\n\n${config.description}` : prefix;
4119
- useFrontendTool({
4120
- name: config.name,
4121
- description: fullDescription,
4122
- parameters: config.parameters,
4123
- render: ({ args }) => {
4124
- const Component = config.render;
4125
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, { ...args });
4126
- },
4127
- agentId: config.agentId,
4128
- followUp: config.followUp
4172
+ function useDefaultRenderTool(config, deps) {
4173
+ const userRender = config?.render;
4174
+ useRenderTool({
4175
+ name: "*",
4176
+ render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) })
4129
4177
  }, deps);
4130
4178
  }
4179
+ /**
4180
+ * Guarded JSON.stringify used inside the expanded `<pre>` blocks. A circular
4181
+ * reference would otherwise crash the entire React tree on render.
4182
+ */
4183
+ function safeStringifyForPre(value) {
4184
+ try {
4185
+ return JSON.stringify(value, null, 2);
4186
+ } catch (err) {
4187
+ console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for default renderer; falling back to String():", err);
4188
+ try {
4189
+ return String(value);
4190
+ } catch (innerErr) {
4191
+ console.warn("[CopilotKit] safeStringifyForPre: value could not be stringified:", innerErr);
4192
+ return "[unserializable]";
4193
+ }
4194
+ }
4195
+ }
4196
+ function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
4197
+ const [isExpanded, setIsExpanded] = (0, react.useState)(false);
4198
+ const isActive = status === "inProgress" || status === "executing";
4199
+ const isComplete = status === "complete";
4200
+ const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
4201
+ const dotClassName = isActive ? "cpk:bg-amber-500" : isComplete ? "cpk:bg-emerald-500" : "cpk:bg-zinc-400";
4202
+ const badgeClassName = isActive ? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400" : isComplete ? "cpk:bg-emerald-100 cpk:text-emerald-800 cpk:dark:bg-emerald-500/15 cpk:dark:text-emerald-400" : "cpk:bg-zinc-100 cpk:text-zinc-800 cpk:dark:bg-zinc-700/40 cpk:dark:text-zinc-300";
4203
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4204
+ "data-testid": "copilot-tool-render",
4205
+ "data-tool-name": name,
4206
+ "data-tool-call-id": toolCallId,
4207
+ "data-status": status,
4208
+ "data-args": safeStringifyForAttr(parameters),
4209
+ "data-result": safeStringifyForAttr(result),
4210
+ className: "cpk:mt-2 cpk:pb-2",
4211
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4212
+ className: "cpk:rounded-xl cpk:border cpk:border-zinc-200/60 cpk:bg-white/70 cpk:p-4 cpk:shadow-sm cpk:backdrop-blur cpk:dark:border-zinc-800/60 cpk:dark:bg-zinc-900/50",
4213
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4214
+ type: "button",
4215
+ "aria-expanded": isExpanded,
4216
+ onClick: () => setIsExpanded(!isExpanded),
4217
+ className: "cpk:flex cpk:w-full cpk:cursor-pointer cpk:select-none cpk:items-center cpk:justify-between cpk:gap-2.5 cpk:border-none cpk:bg-transparent cpk:p-0 cpk:m-0 cpk:text-left cpk:text-inherit",
4218
+ style: { font: "inherit" },
4219
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4220
+ className: "cpk:flex cpk:min-w-0 cpk:items-center cpk:gap-2",
4221
+ children: [
4222
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
4223
+ className: `cpk:h-3.5 cpk:w-3.5 cpk:flex-shrink-0 cpk:text-zinc-500 cpk:transition-transform cpk:dark:text-zinc-400 ${isExpanded ? "cpk:rotate-90" : ""}`,
4224
+ fill: "none",
4225
+ viewBox: "0 0 24 24",
4226
+ strokeWidth: 2,
4227
+ stroke: "currentColor",
4228
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
4229
+ strokeLinecap: "round",
4230
+ strokeLinejoin: "round",
4231
+ d: "M8.25 4.5l7.5 7.5-7.5 7.5"
4232
+ })
4233
+ }),
4234
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: `cpk:inline-block cpk:h-2 cpk:w-2 cpk:flex-shrink-0 cpk:rounded-full ${dotClassName}` }),
4235
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4236
+ "data-testid": "copilot-tool-render-name",
4237
+ className: "cpk:truncate cpk:text-[13px] cpk:font-semibold cpk:text-zinc-900 cpk:dark:text-zinc-100",
4238
+ children: name
4239
+ })
4240
+ ]
4241
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4242
+ "data-testid": "copilot-tool-render-status",
4243
+ className: `cpk:inline-flex cpk:flex-shrink-0 cpk:items-center cpk:rounded-full cpk:px-2 cpk:py-0.5 cpk:text-[11px] cpk:font-medium ${badgeClassName}`,
4244
+ children: statusLabel
4245
+ })]
4246
+ }), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4247
+ className: "cpk:mt-3 cpk:grid cpk:gap-3",
4248
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4249
+ className: "cpk:text-[10px] cpk:uppercase cpk:text-zinc-500 cpk:dark:text-zinc-400",
4250
+ children: "Arguments"
4251
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
4252
+ className: "cpk:mt-1.5 cpk:max-h-[200px] cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-100 cpk:p-2.5 cpk:text-[11px] cpk:leading-relaxed cpk:text-zinc-800 cpk:whitespace-pre-wrap cpk:break-words cpk:dark:bg-zinc-800/60 cpk:dark:text-zinc-200",
4253
+ children: safeStringifyForPre(parameters ?? {})
4254
+ })] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4255
+ className: "cpk:text-[10px] cpk:uppercase cpk:text-zinc-500 cpk:dark:text-zinc-400",
4256
+ children: "Result"
4257
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
4258
+ className: "cpk:mt-1.5 cpk:max-h-[200px] cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-100 cpk:p-2.5 cpk:text-[11px] cpk:leading-relaxed cpk:text-zinc-800 cpk:whitespace-pre-wrap cpk:break-words cpk:dark:bg-zinc-800/60 cpk:dark:text-zinc-200",
4259
+ children: typeof result === "string" ? result : safeStringifyForPre(result)
4260
+ })] })]
4261
+ })]
4262
+ })
4263
+ });
4264
+ }
4265
+ function safeStringifyForAttr(value) {
4266
+ if (value === void 0 || value === null) return "";
4267
+ if (typeof value === "string") return value;
4268
+ try {
4269
+ return JSON.stringify(value);
4270
+ } catch (err) {
4271
+ console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for data-* attribute; falling back to String():", err);
4272
+ try {
4273
+ return String(value);
4274
+ } catch (innerErr) {
4275
+ console.warn("[CopilotKit] safeStringifyForAttr: value could not be stringified:", innerErr);
4276
+ return "";
4277
+ }
4278
+ }
4279
+ }
4131
4280
 
4132
4281
  //#endregion
4133
4282
  //#region src/v2/hooks/use-human-in-the-loop.tsx
@@ -4865,7 +5014,7 @@ function useThreadStoreSelector(store, selector) {
4865
5014
  return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
4866
5015
  const subscription = store.select(selector).subscribe(onStoreChange);
4867
5016
  return () => subscription.unsubscribe();
4868
- }, [store, selector]), () => selector(store.getState()));
5017
+ }, [store, selector]), () => selector(store.getState()), () => selector(store.getServerState()));
4869
5018
  }
4870
5019
  /**
4871
5020
  * React hook for listing and managing Intelligence platform threads.
@@ -4908,7 +5057,7 @@ function useThreadStoreSelector(store, selector) {
4908
5057
  * }
4909
5058
  * ```
4910
5059
  */
4911
- function useThreads$1({ agentId, includeArchived, limit }) {
5060
+ function useThreads$1({ agentId, includeArchived, limit, enabled = true }) {
4912
5061
  const { copilotkit } = useCopilotKit();
4913
5062
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
4914
5063
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
@@ -4925,6 +5074,7 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4925
5074
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
4926
5075
  const hasMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
4927
5076
  const isFetchingMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
5077
+ const isMutating = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsMutating);
4928
5078
  const headersKey = (0, react.useMemo)(() => {
4929
5079
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
4930
5080
  }, [copilotkit.headers]);
@@ -4945,9 +5095,16 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4945
5095
  return /* @__PURE__ */ new Error("Thread mutations are not available on this CopilotKit runtime");
4946
5096
  }, [threadMutationsSupported]);
4947
5097
  const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
4948
- const preConnectLoading = !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
4949
- const isLoading = runtimeError || threadEndpointsError ? false : preConnectLoading || storeIsLoading;
4950
- const error = runtimeError ?? threadEndpointsError ?? storeError;
5098
+ const preConnectLoading = enabled && !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
5099
+ const [configErrorDismissed, setConfigErrorDismissed] = (0, react.useState)(false);
5100
+ (0, react.useEffect)(() => {
5101
+ setConfigErrorDismissed(false);
5102
+ }, [runtimeError, threadEndpointsError]);
5103
+ const activeRuntimeError = configErrorDismissed ? null : runtimeError;
5104
+ const activeThreadEndpointsError = configErrorDismissed ? null : threadEndpointsError;
5105
+ const isLoading = activeRuntimeError || activeThreadEndpointsError ? false : preConnectLoading || storeIsLoading;
5106
+ const error = activeRuntimeError ?? activeThreadEndpointsError ?? storeError;
5107
+ const listError = storeError;
4951
5108
  (0, react.useEffect)(() => {
4952
5109
  store.start();
4953
5110
  return () => {
@@ -4955,6 +5112,7 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4955
5112
  };
4956
5113
  }, [store]);
4957
5114
  (0, react.useEffect)(() => {
5115
+ if (!enabled) return;
4958
5116
  copilotkit.registerThreadStore(agentId, store);
4959
5117
  return () => {
4960
5118
  copilotkit.unregisterThreadStore(agentId);
@@ -4962,9 +5120,15 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4962
5120
  }, [
4963
5121
  copilotkit,
4964
5122
  agentId,
4965
- store
5123
+ store,
5124
+ enabled
4966
5125
  ]);
4967
5126
  (0, react.useEffect)(() => {
5127
+ if (!enabled) {
5128
+ store.setContext(null);
5129
+ setHasDispatchedContext(false);
5130
+ return;
5131
+ }
4968
5132
  if (!copilotkit.runtimeUrl) {
4969
5133
  store.setContext(null);
4970
5134
  setHasDispatchedContext(false);
@@ -4988,6 +5152,7 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4988
5152
  setHasDispatchedContext(true);
4989
5153
  }, [
4990
5154
  store,
5155
+ enabled,
4991
5156
  copilotkit.runtimeUrl,
4992
5157
  runtimeStatus,
4993
5158
  headersKey,
@@ -5011,9 +5176,16 @@ function useThreads$1({ agentId, includeArchived, limit }) {
5011
5176
  threads,
5012
5177
  isLoading,
5013
5178
  error,
5179
+ listError,
5014
5180
  hasMoreThreads,
5015
5181
  isFetchingMoreThreads,
5182
+ isMutating,
5016
5183
  fetchMoreThreads: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
5184
+ refetchThreads: (0, react.useCallback)(() => store.refetchThreads(), [store]),
5185
+ startNewThread: (0, react.useCallback)(() => {
5186
+ setConfigErrorDismissed(true);
5187
+ store.startNewThread();
5188
+ }, [store]),
5017
5189
  renameThread,
5018
5190
  archiveThread,
5019
5191
  unarchiveThread,
@@ -6200,7 +6372,7 @@ const DefaultContainer = react.default.forwardRef(function DefaultContainer({ cl
6200
6372
  ref,
6201
6373
  "data-copilotkit": true,
6202
6374
  "data-testid": "copilot-suggestions",
6203
- className: cn("cpk:flex cpk:flex-wrap cpk:items-center cpk:gap-1.5 cpk:sm:gap-2 cpk:pl-0 cpk:pr-4 cpk:sm:px-0 cpk:pointer-events-none", className),
6375
+ className: cn("cpk:flex cpk:flex-wrap cpk:items-center cpk:gap-1.5 cpk:sm:gap-2 cpk:pl-0 cpk:pr-4 cpk:@3xl:px-0 cpk:pointer-events-none", className),
6204
6376
  ...props
6205
6377
  });
6206
6378
  });
@@ -7305,7 +7477,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7305
7477
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7306
7478
  className: "cpk:max-w-3xl cpk:mx-auto",
7307
7479
  children: [BoundMessageView, hasSuggestions ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7308
- className: "cpk:pl-0 cpk:pr-4 cpk:sm:px-0 cpk:mt-4",
7480
+ className: "cpk:pl-0 cpk:pr-4 cpk:@3xl:px-0 cpk:mt-4",
7309
7481
  children: BoundSuggestionView
7310
7482
  }) : null]
7311
7483
  })
@@ -7348,7 +7520,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7348
7520
  onDragOver,
7349
7521
  onDragLeave,
7350
7522
  onDrop,
7351
- className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
7523
+ className: cn("copilotKitChat cpk:@container cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
7352
7524
  ...props,
7353
7525
  children: [dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}), BoundWelcomeScreen]
7354
7526
  });
@@ -7370,7 +7542,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7370
7542
  onDragOver,
7371
7543
  onDragLeave,
7372
7544
  onDrop,
7373
- className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
7545
+ className: cn("copilotKitChat cpk:@container cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
7374
7546
  ...props,
7375
7547
  children: [
7376
7548
  dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}),
@@ -7409,7 +7581,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7409
7581
  minHeight: 0
7410
7582
  },
7411
7583
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7412
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7584
+ className: "cpk:px-4 cpk:@3xl:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7413
7585
  children
7414
7586
  })
7415
7587
  }),
@@ -7442,7 +7614,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7442
7614
  ...props,
7443
7615
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7444
7616
  ref: contentRef,
7445
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7617
+ className: "cpk:px-4 cpk:@3xl:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7446
7618
  children
7447
7619
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7448
7620
  ref: spacerRef,
@@ -7504,7 +7676,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7504
7676
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7505
7677
  className: "cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
7506
7678
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7507
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7679
+ className: "cpk:px-4 cpk:@3xl:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7508
7680
  children
7509
7681
  })
7510
7682
  });
@@ -7519,7 +7691,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
7519
7691
  children: [
7520
7692
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7521
7693
  ref: contentRef,
7522
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7694
+ className: "cpk:px-4 cpk:@3xl:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
7523
7695
  children
7524
7696
  }),
7525
7697
  BoundFeather,
@@ -7783,9 +7955,17 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
7783
7955
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
7784
7956
  const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
7785
7957
  const isConnecting = hasExplicitThreadId && lastConnectedThreadId !== resolvedThreadId;
7958
+ const previousThreadIdRef = (0, react.useRef)(null);
7959
+ const hasExplicitThreadIdRef = (0, react.useRef)(hasExplicitThreadId);
7960
+ hasExplicitThreadIdRef.current = hasExplicitThreadId;
7786
7961
  (0, react.useEffect)(() => {
7962
+ const threadChanged = previousThreadIdRef.current !== resolvedThreadId;
7963
+ previousThreadIdRef.current = resolvedThreadId;
7787
7964
  agent.threadId = resolvedThreadId;
7788
- if (!hasExplicitThreadId) return;
7965
+ if (!hasExplicitThreadId) {
7966
+ if (threadChanged && agent.messages.length > 0) agent.setMessages([]);
7967
+ return;
7968
+ }
7789
7969
  let detached = false;
7790
7970
  const connectAbortController = new AbortController();
7791
7971
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -7799,6 +7979,7 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
7799
7979
  if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
7800
7980
  if (!detached) setLastConnectedThreadId(resolvedThreadId);
7801
7981
  });
7982
+ else if (!hasExplicitThreadIdRef.current) agentToConnect.setMessages([]);
7802
7983
  }
7803
7984
  };
7804
7985
  connect(agent);
@@ -8149,18 +8330,46 @@ CopilotChatToggleButton.displayName = "CopilotChatToggleButton";
8149
8330
 
8150
8331
  //#endregion
8151
8332
  //#region src/v2/components/chat/CopilotModalHeader.tsx
8152
- function CopilotModalHeader({ title, titleContent, closeButton, children, className, ...rest }) {
8333
+ /**
8334
+ * Reactively tracks whether the viewport is in the mobile range (≤767px) — the
8335
+ * same breakpoint the drawer + chat coordination use. SSR-safe: starts `false`
8336
+ * (desktop) so the server render and first client render agree, then syncs on
8337
+ * mount and on resize.
8338
+ */
8339
+ function useIsMobileViewport() {
8340
+ const [isMobile, setIsMobile] = (0, react.useState)(false);
8341
+ (0, react.useEffect)(() => {
8342
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
8343
+ const mql = window.matchMedia("(max-width: 767px)");
8344
+ const update = () => setIsMobile(mql.matches);
8345
+ update();
8346
+ mql.addEventListener("change", update);
8347
+ return () => mql.removeEventListener("change", update);
8348
+ }, []);
8349
+ return isMobile;
8350
+ }
8351
+ function CopilotModalHeader({ title, titleContent, closeButton, drawerLauncher, children, className, ...rest }) {
8153
8352
  const configuration = useCopilotChatConfiguration();
8154
8353
  const fallbackTitle = configuration?.labels.modalHeaderTitle ?? CopilotChatDefaultLabels.modalHeaderTitle;
8155
8354
  const resolvedTitle = title ?? fallbackTitle;
8355
+ const isMobile = useIsMobileViewport();
8356
+ const drawerRegistered = (configuration?.drawerRegistered ?? false) && isMobile;
8156
8357
  const handleClose = (0, react.useCallback)(() => {
8157
8358
  configuration?.setModalOpen?.(false);
8158
8359
  }, [configuration]);
8360
+ const handleToggleDrawer = (0, react.useCallback)(() => {
8361
+ configuration?.setDrawerOpen?.(!configuration.drawerOpen);
8362
+ }, [configuration]);
8159
8363
  const BoundTitle = renderSlot(titleContent, CopilotModalHeader.Title, { children: resolvedTitle });
8160
8364
  const BoundCloseButton = renderSlot(closeButton, CopilotModalHeader.CloseButton, { onClick: handleClose });
8365
+ const BoundDrawerLauncher = drawerRegistered ? renderSlot(drawerLauncher, CopilotModalHeader.DrawerLauncher, {
8366
+ onClick: handleToggleDrawer,
8367
+ "aria-expanded": configuration?.drawerOpen ?? false
8368
+ }) : null;
8161
8369
  if (children) return children({
8162
8370
  titleContent: BoundTitle,
8163
8371
  closeButton: BoundCloseButton,
8372
+ drawerLauncher: BoundDrawerLauncher,
8164
8373
  title: resolvedTitle,
8165
8374
  ...rest
8166
8375
  });
@@ -8173,8 +8382,8 @@ function CopilotModalHeader({ title, titleContent, closeButton, children, classN
8173
8382
  className: "cpk:flex cpk:w-full cpk:items-center cpk:gap-2",
8174
8383
  children: [
8175
8384
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8176
- className: "cpk:flex-1",
8177
- "aria-hidden": "true"
8385
+ className: "cpk:flex cpk:flex-1 cpk:justify-start",
8386
+ children: BoundDrawerLauncher ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": "true" })
8178
8387
  }),
8179
8388
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8180
8389
  className: "cpk:flex cpk:flex-1 cpk:justify-center cpk:text-center",
@@ -8207,9 +8416,21 @@ CopilotModalHeader.displayName = "CopilotModalHeader";
8207
8416
  "aria-hidden": "true"
8208
8417
  })
8209
8418
  });
8419
+ _CopilotModalHeader.DrawerLauncher = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8420
+ type: "button",
8421
+ "data-testid": "copilot-threads-drawer-launcher",
8422
+ className: cn("cpk:inline-flex cpk:size-8 cpk:items-center cpk:justify-center cpk:rounded-full cpk:text-muted-foreground cpk:transition cpk:cursor-pointer", "cpk:hover:bg-muted cpk:hover:text-foreground cpk:focus-visible:outline-none cpk:focus-visible:ring-2 cpk:focus-visible:ring-ring", className),
8423
+ "aria-label": "Open threads",
8424
+ ...props,
8425
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PanelLeftOpen, {
8426
+ className: "cpk:h-4 cpk:w-4",
8427
+ "aria-hidden": "true"
8428
+ })
8429
+ });
8210
8430
  })(CopilotModalHeader || (CopilotModalHeader = {}));
8211
8431
  CopilotModalHeader.Title.displayName = "CopilotModalHeader.Title";
8212
8432
  CopilotModalHeader.CloseButton.displayName = "CopilotModalHeader.CloseButton";
8433
+ CopilotModalHeader.DrawerLauncher.displayName = "CopilotModalHeader.DrawerLauncher";
8213
8434
 
8214
8435
  //#endregion
8215
8436
  //#region src/v2/components/chat/CopilotSidebarView.tsx
@@ -8567,6 +8788,322 @@ function CopilotPopup({ header, toggleButton, defaultOpen, width, height, clickO
8567
8788
  }
8568
8789
  CopilotPopup.displayName = "CopilotPopup";
8569
8790
 
8791
+ //#endregion
8792
+ //#region src/v2/components/chat/CopilotThreadsDrawer.tsx
8793
+ /**
8794
+ * Maps a {@link Thread} from {@link useThreads} to the element's
8795
+ * {@link DrawerThread} view shape. The shapes are structurally compatible; this
8796
+ * narrows to exactly the fields the element renders so the element never sees
8797
+ * platform-internal fields.
8798
+ */
8799
+ function toDrawerThread(thread) {
8800
+ return {
8801
+ id: thread.id,
8802
+ name: thread.name,
8803
+ archived: thread.archived,
8804
+ createdAt: thread.createdAt,
8805
+ updatedAt: thread.updatedAt,
8806
+ ...thread.lastRunAt !== void 0 ? { lastRunAt: thread.lastRunAt } : {}
8807
+ };
8808
+ }
8809
+ /** The chat input textarea's documented `data-testid`. */
8810
+ const CHAT_INPUT_TESTID = "copilot-chat-textarea";
8811
+ /** The chat view container's documented `data-testid`. */
8812
+ const CHAT_CONTAINER_TESTID = "copilot-chat";
8813
+ /**
8814
+ * Returns the chat input element for focus-return after a thread is selected.
8815
+ *
8816
+ * Best-effort and SCOPED: walks up from the drawer element looking for an
8817
+ * ancestor that contains a chat-view container (`data-testid="copilot-chat"`),
8818
+ * then returns the chat input within that subtree. This avoids focusing the
8819
+ * wrong composer on a page hosting more than one chat (multi-chat dashboards),
8820
+ * where a document-global lookup would grab whichever input appears first in
8821
+ * DOM order rather than the one this drawer drives.
8822
+ *
8823
+ * Falls back to a document-global lookup when no scoping ancestor is found
8824
+ * (e.g. the drawer and chat share no common container, or headless usage),
8825
+ * and returns `null` when there is no chat input at all.
8826
+ *
8827
+ * @param origin - The drawer element to scope the search from.
8828
+ */
8829
+ function findChatInput(origin) {
8830
+ if (typeof document === "undefined") return null;
8831
+ const container = origin?.closest?.(`[data-testid="${CHAT_CONTAINER_TESTID}"]`);
8832
+ if (container) {
8833
+ const scoped = container.querySelector(`[data-testid="${CHAT_INPUT_TESTID}"]`);
8834
+ if (scoped) return scoped;
8835
+ }
8836
+ return document.querySelector(`[data-testid="${CHAT_INPUT_TESTID}"]`);
8837
+ }
8838
+ /**
8839
+ * React wrapper for the shadow-DOM `<copilotkit-threads-drawer>` threads drawer.
8840
+ *
8841
+ * Responsibilities:
8842
+ * - Registers the custom element on the client (SSR-safe; nothing renders
8843
+ * during prerender to avoid hydration mismatch).
8844
+ * - Feeds the element domain data: `threads`, `loading`, `error`,
8845
+ * `activeThreadId`, `licensed`, fetch-more state.
8846
+ * - Routes the element's nine outbound events to core thread operations
8847
+ * ({@link useThreads}) and chat-configuration changes.
8848
+ * - Registers with the surrounding chat configuration so the header
8849
+ * thread-list launcher appears, and binds the element `open` state to the
8850
+ * configuration's `drawerOpen`.
8851
+ *
8852
+ * License gating is two-pronged: the locked view shows when no license is configured
8853
+ * (the runtime reported no license status) OR the `threads` feature is
8854
+ * explicitly unlicensed. While unlicensed, the thread fetch is skipped entirely
8855
+ * so an unlicensed drawer issues no network requests.
8856
+ *
8857
+ * Thread switching needs no host wiring: when `onThreadSelect`/`onNewThread`
8858
+ * are omitted, the wrapper drives the surrounding chat configuration directly
8859
+ * ({@link CopilotChatConfigurationValue.setActiveThreadId} /
8860
+ * {@link CopilotChatConfigurationValue.startNewThread}), so a bare drawer
8861
+ * connects to the picked thread and shows the welcome screen on "+ New". Pass
8862
+ * the callbacks only to take control yourself.
8863
+ *
8864
+ * @example
8865
+ * ```tsx
8866
+ * // Callback-free: the drawer drives the chat configuration itself.
8867
+ * <CopilotKitProvider runtimeUrl="/api/copilotkit" publicLicenseKey="ck_pub_...">
8868
+ * <CopilotChat />
8869
+ * <CopilotThreadsDrawer />
8870
+ * </CopilotKitProvider>
8871
+ * ```
8872
+ */
8873
+ function CopilotThreadsDrawer({ agentId, onThreadSelect, onNewThread, onLicensed, licenseUrl, renderRow, label, limit, "data-testid": dataTestId = "copilot-threads-drawer" }) {
8874
+ const configuration = useCopilotChatConfiguration();
8875
+ const { status, checkFeature } = useLicenseContext();
8876
+ const licensePresent = status === "valid" || status === "expiring";
8877
+ const featureLicensed = checkFeature("threads");
8878
+ const licensed = licensePresent && featureLicensed;
8879
+ const licensePending = status === null;
8880
+ const resolvedAgentId = agentId ?? configuration?.agentId ?? "default";
8881
+ const activeThreadId = configuration?.threadId ?? null;
8882
+ const { threads, isLoading, listError, hasMoreThreads, isFetchingMoreThreads, archiveThread, unarchiveThread, deleteThread, fetchMoreThreads, refetchThreads, startNewThread } = useThreads$1({
8883
+ agentId: resolvedAgentId,
8884
+ includeArchived: true,
8885
+ enabled: licensed,
8886
+ ...limit !== void 0 ? { limit } : {}
8887
+ });
8888
+ const drawerThreads = (0, react.useMemo)(() => threads.map(toDrawerThread), [threads]);
8889
+ const elementRef = (0, react.useRef)(null);
8890
+ const [mounted, setMounted] = (0, react.useState)(false);
8891
+ (0, react.useEffect)(() => {
8892
+ (0, _copilotkit_web_components_threads_drawer.defineCopilotKitThreadsDrawer)();
8893
+ setMounted(true);
8894
+ }, []);
8895
+ const registerDrawer = configuration?.registerDrawer;
8896
+ (0, react.useEffect)(() => {
8897
+ if (!registerDrawer) return;
8898
+ return registerDrawer();
8899
+ }, [registerDrawer]);
8900
+ const [localDrawerOpen, setLocalDrawerOpen] = (0, react.useState)(false);
8901
+ const drawerOpen = configuration ? configuration.drawerOpen : localDrawerOpen;
8902
+ const setDrawerOpen = configuration ? configuration.setDrawerOpen : setLocalDrawerOpen;
8903
+ const setActiveThreadId = configuration?.setActiveThreadId;
8904
+ const startNewThreadConfig = configuration?.startNewThread;
8905
+ const handleThreadSelected = (0, react.useCallback)((threadId) => {
8906
+ if (onThreadSelect) onThreadSelect(threadId);
8907
+ else setActiveThreadId?.(threadId, { explicit: true });
8908
+ findChatInput(elementRef.current)?.focus();
8909
+ }, [onThreadSelect, setActiveThreadId]);
8910
+ const handleNewThread = (0, react.useCallback)(() => {
8911
+ startNewThread();
8912
+ if (onNewThread) onNewThread();
8913
+ else startNewThreadConfig?.();
8914
+ }, [
8915
+ startNewThread,
8916
+ onNewThread,
8917
+ startNewThreadConfig
8918
+ ]);
8919
+ const handleArchive = (0, react.useCallback)((threadId) => {
8920
+ archiveThread(threadId).catch((err) => {
8921
+ console.error("CopilotThreadsDrawer: archiveThread failed", err);
8922
+ });
8923
+ }, [archiveThread]);
8924
+ const handleUnarchive = (0, react.useCallback)((threadId) => {
8925
+ unarchiveThread(threadId).catch((err) => {
8926
+ console.error("CopilotThreadsDrawer: unarchiveThread failed", err);
8927
+ });
8928
+ }, [unarchiveThread]);
8929
+ const handleDelete = (0, react.useCallback)((threadId) => {
8930
+ const isActive = threadId === activeThreadId;
8931
+ deleteThread(threadId).then(() => {
8932
+ if (isActive) {
8933
+ startNewThread();
8934
+ if (onNewThread) onNewThread();
8935
+ else startNewThreadConfig?.();
8936
+ }
8937
+ }).catch((err) => {
8938
+ console.error("CopilotThreadsDrawer: deleteThread failed", err);
8939
+ });
8940
+ }, [
8941
+ deleteThread,
8942
+ activeThreadId,
8943
+ startNewThread,
8944
+ onNewThread,
8945
+ startNewThreadConfig
8946
+ ]);
8947
+ const handleFilterChange = (0, react.useCallback)(() => {
8948
+ refetchThreads();
8949
+ }, [refetchThreads]);
8950
+ const handleRetry = (0, react.useCallback)((scope) => {
8951
+ if (scope === "fetch-more") fetchMoreThreads();
8952
+ else refetchThreads();
8953
+ }, [fetchMoreThreads, refetchThreads]);
8954
+ const handleOpenChange = (0, react.useCallback)((open) => {
8955
+ setDrawerOpen(open);
8956
+ }, [setDrawerOpen]);
8957
+ const handleLicensed = (0, react.useCallback)(() => {
8958
+ onLicensed?.();
8959
+ }, [onLicensed]);
8960
+ const handleLoadMore = (0, react.useCallback)(() => {
8961
+ fetchMoreThreads();
8962
+ }, [fetchMoreThreads]);
8963
+ const handlersRef = (0, react.useRef)({
8964
+ handleThreadSelected,
8965
+ handleNewThread,
8966
+ handleArchive,
8967
+ handleUnarchive,
8968
+ handleDelete,
8969
+ handleFilterChange,
8970
+ handleRetry,
8971
+ handleOpenChange,
8972
+ handleLicensed,
8973
+ handleLoadMore
8974
+ });
8975
+ handlersRef.current = {
8976
+ handleThreadSelected,
8977
+ handleNewThread,
8978
+ handleArchive,
8979
+ handleUnarchive,
8980
+ handleDelete,
8981
+ handleFilterChange,
8982
+ handleRetry,
8983
+ handleOpenChange,
8984
+ handleLicensed,
8985
+ handleLoadMore
8986
+ };
8987
+ (0, react.useEffect)(() => {
8988
+ const el = elementRef.current;
8989
+ if (!el) return;
8990
+ const onThreadSelected = (event) => {
8991
+ const detail = event.detail;
8992
+ handlersRef.current.handleThreadSelected(detail.threadId);
8993
+ };
8994
+ const onNewThreadEvent = () => handlersRef.current.handleNewThread();
8995
+ const onArchive = (event) => {
8996
+ const detail = event.detail;
8997
+ handlersRef.current.handleArchive(detail.threadId);
8998
+ };
8999
+ const onUnarchive = (event) => {
9000
+ const detail = event.detail;
9001
+ handlersRef.current.handleUnarchive(detail.threadId);
9002
+ };
9003
+ const onDelete = (event) => {
9004
+ const detail = event.detail;
9005
+ handlersRef.current.handleDelete(detail.threadId);
9006
+ };
9007
+ const onFilterChange = (_event) => {
9008
+ handlersRef.current.handleFilterChange();
9009
+ };
9010
+ const onOpenChangeEvent = (event) => {
9011
+ const detail = event.detail;
9012
+ handlersRef.current.handleOpenChange(detail.open);
9013
+ };
9014
+ const onRetry = (event) => {
9015
+ const detail = event.detail;
9016
+ handlersRef.current.handleRetry(detail.scope);
9017
+ };
9018
+ const onLicensedEvent = () => handlersRef.current.handleLicensed();
9019
+ const onLoadMore = () => handlersRef.current.handleLoadMore();
9020
+ el.addEventListener("thread-selected", onThreadSelected);
9021
+ el.addEventListener("new-thread", onNewThreadEvent);
9022
+ el.addEventListener("archive", onArchive);
9023
+ el.addEventListener("unarchive", onUnarchive);
9024
+ el.addEventListener("delete", onDelete);
9025
+ el.addEventListener("filter-change", onFilterChange);
9026
+ el.addEventListener("open-change", onOpenChangeEvent);
9027
+ el.addEventListener("retry", onRetry);
9028
+ el.addEventListener("licensed", onLicensedEvent);
9029
+ el.addEventListener("load-more", onLoadMore);
9030
+ return () => {
9031
+ el.removeEventListener("thread-selected", onThreadSelected);
9032
+ el.removeEventListener("new-thread", onNewThreadEvent);
9033
+ el.removeEventListener("archive", onArchive);
9034
+ el.removeEventListener("unarchive", onUnarchive);
9035
+ el.removeEventListener("delete", onDelete);
9036
+ el.removeEventListener("filter-change", onFilterChange);
9037
+ el.removeEventListener("open-change", onOpenChangeEvent);
9038
+ el.removeEventListener("retry", onRetry);
9039
+ el.removeEventListener("licensed", onLicensedEvent);
9040
+ el.removeEventListener("load-more", onLoadMore);
9041
+ };
9042
+ }, [mounted]);
9043
+ (0, react.useEffect)(() => {
9044
+ const el = elementRef.current;
9045
+ if (!el) return;
9046
+ el.threads = drawerThreads;
9047
+ }, [drawerThreads, mounted]);
9048
+ (0, react.useEffect)(() => {
9049
+ const el = elementRef.current;
9050
+ if (!el) return;
9051
+ el.loading = isLoading || licensePending;
9052
+ el.error = listError ? listError.message : null;
9053
+ el.activeThreadId = activeThreadId;
9054
+ el.licensed = licensed || licensePending;
9055
+ el.hasMore = hasMoreThreads;
9056
+ el.fetchingMore = isFetchingMoreThreads;
9057
+ }, [
9058
+ isLoading,
9059
+ listError,
9060
+ activeThreadId,
9061
+ licensed,
9062
+ licensePending,
9063
+ hasMoreThreads,
9064
+ isFetchingMoreThreads,
9065
+ mounted
9066
+ ]);
9067
+ (0, react.useEffect)(() => {
9068
+ const el = elementRef.current;
9069
+ if (!el) return;
9070
+ el.open = drawerOpen;
9071
+ }, [drawerOpen, mounted]);
9072
+ (0, react.useEffect)(() => {
9073
+ const el = elementRef.current;
9074
+ if (!el) return;
9075
+ if (label !== void 0) el.label = label;
9076
+ }, [label, mounted]);
9077
+ (0, react.useEffect)(() => {
9078
+ const el = elementRef.current;
9079
+ if (!el) return;
9080
+ if (licenseUrl !== void 0) el.licenseUrl = licenseUrl;
9081
+ }, [licenseUrl, mounted]);
9082
+ const rowChildren = (0, react.useMemo)(() => {
9083
+ if (!renderRow) return null;
9084
+ return drawerThreads.map((drawerThread) => {
9085
+ const fullThread = threads.find((t) => t.id === drawerThread.id);
9086
+ if (!fullThread) return null;
9087
+ const content = renderRow(fullThread);
9088
+ if (content === null || content === void 0) return null;
9089
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9090
+ slot: `row:${drawerThread.id}`,
9091
+ children: content
9092
+ }, drawerThread.id);
9093
+ });
9094
+ }, [
9095
+ renderRow,
9096
+ drawerThreads,
9097
+ threads
9098
+ ]);
9099
+ if (!mounted) return null;
9100
+ return react.default.createElement(_copilotkit_web_components_threads_drawer.COPILOTKIT_THREADS_DRAWER_TAG, {
9101
+ ref: elementRef,
9102
+ "data-testid": dataTestId
9103
+ }, rowChildren);
9104
+ }
9105
+ CopilotThreadsDrawer.displayName = "CopilotThreadsDrawer";
9106
+
8570
9107
  //#endregion
8571
9108
  //#region src/v2/components/WildcardToolCallRender.tsx
8572
9109
  const WildcardToolCallRender = defineToolCallRenderer({
@@ -10929,6 +11466,12 @@ Object.defineProperty(exports, 'CopilotSidebarView', {
10929
11466
  return CopilotSidebarView;
10930
11467
  }
10931
11468
  });
11469
+ Object.defineProperty(exports, 'CopilotThreadsDrawer', {
11470
+ enumerable: true,
11471
+ get: function () {
11472
+ return CopilotThreadsDrawer;
11473
+ }
11474
+ });
10932
11475
  Object.defineProperty(exports, 'DefaultCloseIcon', {
10933
11476
  enumerable: true,
10934
11477
  get: function () {
@@ -11217,4 +11760,4 @@ Object.defineProperty(exports, 'useToast', {
11217
11760
  return useToast;
11218
11761
  }
11219
11762
  });
11220
- //# sourceMappingURL=copilotkit-CTCjVxkH.cjs.map
11763
+ //# sourceMappingURL=copilotkit-CdpDZi3i.cjs.map