@copilotkit/react-core 1.59.1 → 1.59.2

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.
@@ -1532,6 +1532,47 @@ function useRenderTool(config, deps) {
1532
1532
  //#endregion
1533
1533
  //#region src/v2/hooks/use-default-render-tool.tsx
1534
1534
  /**
1535
+ * Module-level dedup set so an unknown status value only emits a console
1536
+ * warning the FIRST time we encounter it. Otherwise a stuck/unmapped status
1537
+ * would log on every re-render (potentially many per second).
1538
+ */
1539
+ const warnedUnknownStatuses = /* @__PURE__ */ new Set();
1540
+ /**
1541
+ * Map a {@link ToolCallStatus} enum value to the documented string-union
1542
+ * status the {@link DefaultRenderProps} contract exposes. Unknown / future
1543
+ * enum members log a warning (once per distinct value) and fall back to
1544
+ * `"inProgress"`.
1545
+ */
1546
+ function mapToolCallStatus(status) {
1547
+ switch (status) {
1548
+ case _copilotkit_core.ToolCallStatus.Complete: return "complete";
1549
+ case _copilotkit_core.ToolCallStatus.Executing: return "executing";
1550
+ case _copilotkit_core.ToolCallStatus.InProgress: return "inProgress";
1551
+ default: {
1552
+ const key = String(status);
1553
+ if (!warnedUnknownStatuses.has(key)) {
1554
+ warnedUnknownStatuses.add(key);
1555
+ console.warn(`[CopilotKit] Unknown ToolCallStatus "${key}" in default tool-call renderer; falling back to "inProgress".`);
1556
+ }
1557
+ return "inProgress";
1558
+ }
1559
+ }
1560
+ }
1561
+ /**
1562
+ * Convert the framework-internal renderer props (`args`, enum status) into
1563
+ * the documented {@link DefaultRenderProps} shape (`parameters`, string-union
1564
+ * status) so a user `config.render` always sees the documented contract.
1565
+ */
1566
+ function adaptRendererProps(props) {
1567
+ return {
1568
+ name: props.name,
1569
+ toolCallId: props.toolCallId,
1570
+ parameters: props.args,
1571
+ status: mapToolCallStatus(props.status),
1572
+ result: props.result
1573
+ };
1574
+ }
1575
+ /**
1535
1576
  * Registers a wildcard (`"*"`) tool-call renderer via `useRenderTool`.
1536
1577
  *
1537
1578
  * - Call with no config to use CopilotKit's built-in default tool-call card.
@@ -1568,16 +1609,33 @@ function useRenderTool(config, deps) {
1568
1609
  * ```
1569
1610
  */
1570
1611
  function useDefaultRenderTool(config, deps) {
1612
+ const userRender = config?.render;
1571
1613
  useRenderTool({
1572
1614
  name: "*",
1573
- render: config?.render ?? DefaultToolCallRenderer
1615
+ render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) })
1574
1616
  }, deps);
1575
1617
  }
1576
- function DefaultToolCallRenderer({ name, parameters, status, result }) {
1618
+ /**
1619
+ * Guarded JSON.stringify used inside the expanded `<pre>` blocks. A circular
1620
+ * reference would otherwise crash the entire React tree on render.
1621
+ */
1622
+ function safeStringifyForPre(value) {
1623
+ try {
1624
+ return JSON.stringify(value, null, 2);
1625
+ } catch (err) {
1626
+ console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for default renderer; falling back to String():", err);
1627
+ try {
1628
+ return String(value);
1629
+ } catch (innerErr) {
1630
+ console.warn("[CopilotKit] safeStringifyForPre: value could not be stringified:", innerErr);
1631
+ return "[unserializable]";
1632
+ }
1633
+ }
1634
+ }
1635
+ function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
1577
1636
  const [isExpanded, setIsExpanded] = (0, react.useState)(false);
1578
- const statusString = String(status);
1579
- const isActive = statusString === "inProgress" || statusString === "executing";
1580
- const isComplete = statusString === "complete";
1637
+ const isActive = status === "inProgress" || status === "executing";
1638
+ const isComplete = status === "complete";
1581
1639
  const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
1582
1640
  const dotColor = isActive ? "#f59e0b" : isComplete ? "#10b981" : "#a1a1aa";
1583
1641
  const badgeBg = isActive ? "#fef3c7" : isComplete ? "#d1fae5" : "#f4f4f5";
@@ -1585,7 +1643,8 @@ function DefaultToolCallRenderer({ name, parameters, status, result }) {
1585
1643
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1586
1644
  "data-testid": "copilot-tool-render",
1587
1645
  "data-tool-name": name,
1588
- "data-status": statusString,
1646
+ "data-tool-call-id": toolCallId,
1647
+ "data-status": status,
1589
1648
  "data-args": safeStringifyForAttr(parameters),
1590
1649
  "data-result": safeStringifyForAttr(result),
1591
1650
  style: {
@@ -1599,7 +1658,9 @@ function DefaultToolCallRenderer({ name, parameters, status, result }) {
1599
1658
  backgroundColor: "#fafafa",
1600
1659
  padding: "14px 16px"
1601
1660
  },
1602
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1661
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
1662
+ type: "button",
1663
+ "aria-expanded": isExpanded,
1603
1664
  onClick: () => setIsExpanded(!isExpanded),
1604
1665
  style: {
1605
1666
  display: "flex",
@@ -1607,7 +1668,15 @@ function DefaultToolCallRenderer({ name, parameters, status, result }) {
1607
1668
  justifyContent: "space-between",
1608
1669
  gap: "10px",
1609
1670
  cursor: "pointer",
1610
- userSelect: "none"
1671
+ userSelect: "none",
1672
+ width: "100%",
1673
+ border: "none",
1674
+ padding: 0,
1675
+ margin: 0,
1676
+ background: "transparent",
1677
+ textAlign: "left",
1678
+ font: "inherit",
1679
+ color: "inherit"
1611
1680
  },
1612
1681
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1613
1682
  style: {
@@ -1700,7 +1769,7 @@ function DefaultToolCallRenderer({ name, parameters, status, result }) {
1700
1769
  whiteSpace: "pre-wrap",
1701
1770
  wordBreak: "break-word"
1702
1771
  },
1703
- children: JSON.stringify(parameters ?? {}, null, 2)
1772
+ children: safeStringifyForPre(parameters ?? {})
1704
1773
  })] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1705
1774
  style: {
1706
1775
  fontSize: "10px",
@@ -1723,7 +1792,7 @@ function DefaultToolCallRenderer({ name, parameters, status, result }) {
1723
1792
  whiteSpace: "pre-wrap",
1724
1793
  wordBreak: "break-word"
1725
1794
  },
1726
- children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
1795
+ children: typeof result === "string" ? result : safeStringifyForPre(result)
1727
1796
  })] })]
1728
1797
  })]
1729
1798
  })
@@ -1734,8 +1803,14 @@ function safeStringifyForAttr(value) {
1734
1803
  if (typeof value === "string") return value;
1735
1804
  try {
1736
1805
  return JSON.stringify(value);
1737
- } catch {
1738
- return String(value);
1806
+ } catch (err) {
1807
+ console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload for data-* attribute; falling back to String():", err);
1808
+ try {
1809
+ return String(value);
1810
+ } catch (innerErr) {
1811
+ console.warn("[CopilotKit] safeStringifyForAttr: value could not be stringified:", innerErr);
1812
+ return "";
1813
+ }
1739
1814
  }
1740
1815
  }
1741
1816
 
@@ -1806,11 +1881,11 @@ function useRenderToolCall() {
1806
1881
  ]);
1807
1882
  }
1808
1883
  function defaultToolCallRenderAdapter(props) {
1809
- const status = props.status === _copilotkit_core.ToolCallStatus.Complete ? "complete" : props.status === _copilotkit_core.ToolCallStatus.Executing ? "executing" : "inProgress";
1810
1884
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, {
1811
1885
  name: props.name,
1886
+ toolCallId: props.toolCallId,
1812
1887
  parameters: props.args,
1813
- status,
1888
+ status: mapToolCallStatus(props.status),
1814
1889
  result: props.result
1815
1890
  });
1816
1891
  }
@@ -4505,7 +4580,6 @@ function useInterrupt(config) {
4505
4580
  return () => subscription.unsubscribe();
4506
4581
  }, [agent]);
4507
4582
  const resolve = (0, react.useCallback)((response) => {
4508
- setPendingEvent(null);
4509
4583
  copilotkit.runAgent({
4510
4584
  agent,
4511
4585
  forwardedProps: { command: {
@@ -4514,44 +4588,67 @@ function useInterrupt(config) {
4514
4588
  } }
4515
4589
  });
4516
4590
  }, [agent, copilotkit]);
4591
+ const renderRef = (0, react.useRef)(config.render);
4592
+ renderRef.current = config.render;
4593
+ const enabledRef = (0, react.useRef)(config.enabled);
4594
+ enabledRef.current = config.enabled;
4595
+ const handlerRef = (0, react.useRef)(config.handler);
4596
+ handlerRef.current = config.handler;
4597
+ const resolveRef = (0, react.useRef)(resolve);
4598
+ resolveRef.current = resolve;
4599
+ const isEnabled = (event) => {
4600
+ const predicate = enabledRef.current;
4601
+ if (!predicate) return true;
4602
+ try {
4603
+ return predicate(event);
4604
+ } catch (err) {
4605
+ console.error("[CopilotKit] useInterrupt enabled predicate threw; treating interrupt as disabled:", err);
4606
+ return false;
4607
+ }
4608
+ };
4517
4609
  (0, react.useEffect)(() => {
4518
4610
  if (!pendingEvent) {
4519
4611
  setHandlerResult(null);
4520
4612
  return;
4521
4613
  }
4522
- if (config.enabled && !config.enabled(pendingEvent)) {
4614
+ if (!isEnabled(pendingEvent)) {
4523
4615
  setHandlerResult(null);
4524
4616
  return;
4525
4617
  }
4526
- const handler = config.handler;
4618
+ const handler = handlerRef.current;
4527
4619
  if (!handler) {
4528
4620
  setHandlerResult(null);
4529
4621
  return;
4530
4622
  }
4531
4623
  let cancelled = false;
4532
- const maybePromise = handler({
4533
- event: pendingEvent,
4534
- resolve
4535
- });
4624
+ let maybePromise;
4625
+ try {
4626
+ maybePromise = handler({
4627
+ event: pendingEvent,
4628
+ resolve: resolveRef.current
4629
+ });
4630
+ } catch (err) {
4631
+ console.error("[CopilotKit] useInterrupt handler threw; result will be null:", err);
4632
+ if (!cancelled) setHandlerResult(null);
4633
+ return () => {
4634
+ cancelled = true;
4635
+ };
4636
+ }
4536
4637
  if (isPromiseLike(maybePromise)) Promise.resolve(maybePromise).then((resolved) => {
4537
4638
  if (!cancelled) setHandlerResult(resolved);
4538
- }).catch(() => {
4639
+ }).catch((err) => {
4640
+ console.error("[CopilotKit] useInterrupt handler rejected; result will be null:", err);
4539
4641
  if (!cancelled) setHandlerResult(null);
4540
4642
  });
4541
4643
  else setHandlerResult(maybePromise);
4542
4644
  return () => {
4543
4645
  cancelled = true;
4544
4646
  };
4545
- }, [
4546
- pendingEvent,
4547
- config.enabled,
4548
- config.handler,
4549
- resolve
4550
- ]);
4647
+ }, [pendingEvent]);
4551
4648
  const element = (0, react.useMemo)(() => {
4552
4649
  if (!pendingEvent) return null;
4553
- if (config.enabled && !config.enabled(pendingEvent)) return null;
4554
- return config.render({
4650
+ if (!isEnabled(pendingEvent)) return null;
4651
+ return renderRef.current({
4555
4652
  event: pendingEvent,
4556
4653
  result: handlerResult,
4557
4654
  resolve
@@ -4559,19 +4656,22 @@ function useInterrupt(config) {
4559
4656
  }, [
4560
4657
  pendingEvent,
4561
4658
  handlerResult,
4562
- config.enabled,
4563
- config.render,
4564
4659
  resolve
4565
4660
  ]);
4566
4661
  (0, react.useEffect)(() => {
4567
4662
  if (config.renderInChat === false) return;
4568
4663
  copilotkit.setInterruptElement(element);
4569
- return () => copilotkit.setInterruptElement(null);
4570
4664
  }, [
4571
4665
  element,
4572
4666
  config.renderInChat,
4573
4667
  copilotkit
4574
4668
  ]);
4669
+ (0, react.useEffect)(() => {
4670
+ if (config.renderInChat === false) return;
4671
+ return () => {
4672
+ copilotkit.setInterruptElement(null);
4673
+ };
4674
+ }, []);
4575
4675
  if (config.renderInChat === false) return element;
4576
4676
  }
4577
4677
 
@@ -8220,6 +8320,7 @@ function BannerErrorDisplay({ bannerError, onDismiss }) {
8220
8320
  const details = bannerError.details;
8221
8321
  const link = extractUrl(bannerError.message);
8222
8322
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8323
+ "data-testid": "copilot-error-banner",
8223
8324
  style: {
8224
8325
  position: "fixed",
8225
8326
  bottom: "20px",
@@ -8420,7 +8521,7 @@ function ToastProvider({ enabled, children }) {
8420
8521
  }, []);
8421
8522
  const addToast = (0, react.useCallback)((toast) => {
8422
8523
  if (!enabled) return;
8423
- const id = toast.id ?? Math.random().toString(36).substring(2, 9);
8524
+ const id = toast.id ?? Math.random().toString(36).slice(2, 9);
8424
8525
  setToasts((currentToasts) => {
8425
8526
  if (currentToasts.find((toast) => toast.id === id)) return currentToasts;
8426
8527
  return [...currentToasts, {
@@ -8777,6 +8878,7 @@ function UsageBanner({ severity = _copilotkit_shared.Severity.CRITICAL, message
8777
8878
  }
8778
8879
  ` }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8779
8880
  className: "usage-banner",
8881
+ "data-testid": "copilot-error-banner",
8780
8882
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8781
8883
  className: "banner-content",
8782
8884
  children: [
@@ -10440,4 +10542,4 @@ Object.defineProperty(exports, 'useToast', {
10440
10542
  return useToast;
10441
10543
  }
10442
10544
  });
10443
- //# sourceMappingURL=copilotkit-DKPrBplZ.cjs.map
10545
+ //# sourceMappingURL=copilotkit-IQO0VDZb.cjs.map