@copilotkit/react-core 1.79.2-canary.1792172552 → 1.80.0

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 (42) hide show
  1. package/dist/{copilotkit-DcENKLnJ.mjs → copilotkit-BNLtgOyD.mjs} +1783 -238
  2. package/dist/copilotkit-BNLtgOyD.mjs.map +1 -0
  3. package/dist/{copilotkit-kcYN96tM.d.mts → copilotkit-CWOSXhRO.d.mts} +278 -41
  4. package/dist/copilotkit-CWOSXhRO.d.mts.map +1 -0
  5. package/dist/{copilotkit-ConxFSB2.cjs → copilotkit-DKICi3nC.cjs} +1764 -206
  6. package/dist/copilotkit-DKICi3nC.cjs.map +1 -0
  7. package/dist/{copilotkit-BA0vyDds.d.cts → copilotkit-Dfw9HzO9.d.cts} +278 -41
  8. package/dist/copilotkit-Dfw9HzO9.d.cts.map +1 -0
  9. package/dist/index.cjs +15 -3
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.mts +1 -1
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +15 -3
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/index.umd.js +48 -11
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/v2/context.d.cts +7 -0
  20. package/dist/v2/context.d.cts.map +1 -1
  21. package/dist/v2/context.d.mts +7 -0
  22. package/dist/v2/context.d.mts.map +1 -1
  23. package/dist/v2/headless.cjs +23 -6
  24. package/dist/v2/headless.cjs.map +1 -1
  25. package/dist/v2/headless.d.cts +20 -2
  26. package/dist/v2/headless.d.cts.map +1 -1
  27. package/dist/v2/headless.d.mts +20 -2
  28. package/dist/v2/headless.d.mts.map +1 -1
  29. package/dist/v2/headless.mjs +23 -6
  30. package/dist/v2/headless.mjs.map +1 -1
  31. package/dist/v2/index.cjs +3 -7
  32. package/dist/v2/index.css +1 -1
  33. package/dist/v2/index.d.cts +2 -2
  34. package/dist/v2/index.d.mts +2 -2
  35. package/dist/v2/index.mjs +2 -2
  36. package/dist/v2/index.umd.js +1756 -215
  37. package/dist/v2/index.umd.js.map +1 -1
  38. package/package.json +14 -11
  39. package/dist/copilotkit-BA0vyDds.d.cts.map +0 -1
  40. package/dist/copilotkit-ConxFSB2.cjs.map +0 -1
  41. package/dist/copilotkit-DcENKLnJ.mjs.map +0 -1
  42. package/dist/copilotkit-kcYN96tM.d.mts.map +0 -1
@@ -1,10 +1,10 @@
1
1
  "use client";
2
2
 
3
3
  (function(global, factory) {
4
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./index.css'), require('@copilotkit/core'), require('@ag-ui/client'), require('react'), require('tailwind-merge'), require('lucide-react'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@radix-ui/react-slot'), require('class-variance-authority'), require('clsx'), require('@radix-ui/react-tooltip'), require('@radix-ui/react-dropdown-menu'), require('streamdown'), require('@ag-ui/core'), require('zod'), require('@copilotkit/mcp-apps-renderer/activity'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('react-dom'), require('@tanstack/react-virtual'), require('use-stick-to-bottom'), require('@copilotkit/web-components/threads-drawer')) :
5
- typeof define === 'function' && define.amd ? define(['exports', './index.css', '@copilotkit/core', '@ag-ui/client', 'react', 'tailwind-merge', 'lucide-react', '@copilotkit/shared', 'react/jsx-runtime', '@radix-ui/react-slot', 'class-variance-authority', 'clsx', '@radix-ui/react-tooltip', '@radix-ui/react-dropdown-menu', 'streamdown', '@ag-ui/core', 'zod', '@copilotkit/mcp-apps-renderer/activity', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', 'react-dom', '@tanstack/react-virtual', 'use-stick-to-bottom', '@copilotkit/web-components/threads-drawer'], factory) :
6
- (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCoreV2 = {}), global.src_v2_index_css,global.CopilotKitCore,global.AgUIClient,global.React,global.tailwindMerge,global.lucideReact,global.CopilotKitShared,global.ReactJsxRuntime,global.RadixReactSlot,global.classVarianceAuthority,global.clsx,global.RadixReactTooltip,global.RadixReactDropdownMenu,global.streamdown,global.AgUICore,global.Zod,global.CopilotKitMcpAppsRendererActivity,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global.ReactDOM,global._tanstack_react_virtual,global.useStickToBottom,global._copilotkit_web_components_threads_drawer));
7
- })(this, function(exports, src_v2_index_css, _copilotkit_core, _ag_ui_client, react, tailwind_merge, lucide_react, _copilotkit_shared, react_jsx_runtime, _radix_ui_react_slot, class_variance_authority, clsx, _radix_ui_react_tooltip, _radix_ui_react_dropdown_menu, streamdown, _ag_ui_core, zod, _copilotkit_mcp_apps_renderer_activity, _copilotkit_a2ui_renderer, zod_to_json_schema, react_dom, _tanstack_react_virtual, use_stick_to_bottom, _copilotkit_web_components_threads_drawer) {
4
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('./index.css'), require('@copilotkit/core'), require('@ag-ui/client'), require('react'), require('tailwind-merge'), require('lucide-react'), require('@copilotkit/shared'), require('react/jsx-runtime'), require('@radix-ui/react-slot'), require('class-variance-authority'), require('clsx'), require('@radix-ui/react-tooltip'), require('@radix-ui/react-dropdown-menu'), require('streamdown'), require('@ag-ui/core'), require('zod'), require('@copilotkit/mcp-apps-renderer/activity'), require('@copilotkit/a2ui-renderer'), require('zod-to-json-schema'), require('@radix-ui/react-popover'), require('react-dom'), require('@tanstack/react-virtual'), require('use-stick-to-bottom'), require('@copilotkit/web-components/threads-drawer')) :
5
+ typeof define === 'function' && define.amd ? define(['exports', './index.css', '@copilotkit/core', '@ag-ui/client', 'react', 'tailwind-merge', 'lucide-react', '@copilotkit/shared', 'react/jsx-runtime', '@radix-ui/react-slot', 'class-variance-authority', 'clsx', '@radix-ui/react-tooltip', '@radix-ui/react-dropdown-menu', 'streamdown', '@ag-ui/core', 'zod', '@copilotkit/mcp-apps-renderer/activity', '@copilotkit/a2ui-renderer', 'zod-to-json-schema', '@radix-ui/react-popover', 'react-dom', '@tanstack/react-virtual', 'use-stick-to-bottom', '@copilotkit/web-components/threads-drawer'], factory) :
6
+ (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory((global.CopilotKitReactCoreV2 = {}), global.src_v2_index_css,global.CopilotKitCore,global.AgUIClient,global.React,global.tailwindMerge,global.lucideReact,global.CopilotKitShared,global.ReactJsxRuntime,global.RadixReactSlot,global.classVarianceAuthority,global.clsx,global.RadixReactTooltip,global.RadixReactDropdownMenu,global.streamdown,global.AgUICore,global.Zod,global.CopilotKitMcpAppsRendererActivity,global.CopilotKitA2UIRenderer,global.zod_to_json_schema,global._radix_ui_react_popover,global.ReactDOM,global._tanstack_react_virtual,global.useStickToBottom,global._copilotkit_web_components_threads_drawer));
7
+ })(this, function(exports, src_v2_index_css, _copilotkit_core, _ag_ui_client, react, tailwind_merge, lucide_react, _copilotkit_shared, react_jsx_runtime, _radix_ui_react_slot, class_variance_authority, clsx, _radix_ui_react_tooltip, _radix_ui_react_dropdown_menu, streamdown, _ag_ui_core, zod, _copilotkit_mcp_apps_renderer_activity, _copilotkit_a2ui_renderer, zod_to_json_schema, _radix_ui_react_popover, react_dom, _tanstack_react_virtual, use_stick_to_bottom, _copilotkit_web_components_threads_drawer) {
8
8
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
9
9
  //#region \0rolldown/runtime.js
10
10
  var __create = Object.create;
@@ -36,6 +36,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
36
36
  react = __toESM(react);
37
37
  _radix_ui_react_tooltip = __toESM(_radix_ui_react_tooltip);
38
38
  _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
39
+ _radix_ui_react_popover = __toESM(_radix_ui_react_popover);
39
40
 
40
41
  //#region src/v2/lib/react-core.ts
41
42
  var CopilotKitCoreReact = class extends _copilotkit_core.CopilotKitCore {
@@ -215,6 +216,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
215
216
  chatInputToolbarFinishTranscribeButtonLabel: "Finish",
216
217
  chatInputToolbarAddButtonLabel: "Add attachments",
217
218
  chatInputToolbarToolsButtonLabel: "Tools",
219
+ chatInputToolbarSendButtonLabel: "Send message",
220
+ chatInputToolbarStopButtonLabel: "Stop",
218
221
  assistantMessageToolbarCopyCodeLabel: "Copy",
219
222
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
220
223
  assistantMessageToolbarCopyMessageLabel: "Copy",
@@ -227,13 +230,18 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
227
230
  assistantMessageToolbarInspectorHideDescription: "Until you reload this page",
228
231
  assistantMessageToolbarThumbsUpLabel: "Good response",
229
232
  assistantMessageToolbarThumbsDownLabel: "Bad response",
233
+ assistantMessageFeedbackCommentPlaceholder: "Add a comment (optional)",
234
+ assistantMessageFeedbackCommentSubmitLabel: "Send comment",
230
235
  assistantMessageToolbarReadAloudLabel: "Read aloud",
231
236
  assistantMessageToolbarRegenerateLabel: "Regenerate",
232
237
  userMessageToolbarCopyMessageLabel: "Copy",
233
238
  userMessageToolbarEditMessageLabel: "Edit",
239
+ userMessageToolbarPreviousBranchLabel: "Previous branch",
240
+ userMessageToolbarNextBranchLabel: "Next branch",
234
241
  chatDisclaimerText: "AI can make mistakes. Please verify important information.",
235
242
  chatToggleOpenLabel: "Open chat",
236
243
  chatToggleCloseLabel: "Close chat",
244
+ chatScrollToBottomLabel: "Scroll to bottom",
237
245
  modalHeaderTitle: "CopilotKit Chat",
238
246
  welcomeMessageText: "How can I help you today?"
239
247
  };
@@ -495,9 +503,9 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
495
503
 
496
504
  //#endregion
497
505
  //#region src/v2/lib/utils.ts
498
- const twMerge$10 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
506
+ const twMerge$12 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
499
507
  function cn(...inputs) {
500
- return twMerge$10((0, clsx.clsx)(inputs));
508
+ return twMerge$12((0, clsx.clsx)(inputs));
501
509
  }
502
510
 
503
511
  //#endregion
@@ -945,6 +953,11 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
945
953
  //#region src/v2/components/chat/CopilotChatInput.tsx
946
954
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
947
955
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
956
+ /**
957
+ * The chat composer: a text area with the add-menu, voice and send/stop
958
+ * buttons, and a slash-command menu that lists `toolsMenu` items once the text
959
+ * starts with "/".
960
+ */
948
961
  function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, bottomAnchored = false, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
949
962
  const isControlled = value !== void 0;
950
963
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
@@ -1164,10 +1177,12 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1164
1177
  send();
1165
1178
  };
1166
1179
  const BoundAudioRecorder = renderSlot(audioRecorder, CopilotChatAudioRecorder, { ref: audioRecorderRef });
1180
+ const showsStop = isProcessing && canStop;
1167
1181
  const BoundSendButton = renderSlot(sendButton, CopilotChatInput.SendButton, {
1168
1182
  onClick: handleSendButtonClick,
1169
1183
  disabled: isProcessing ? !canStop : !canSend,
1170
- children: isProcessing && canStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Square, { className: "cpk:size-[18px] cpk:fill-current" }) : void 0
1184
+ "aria-label": showsStop ? labels.chatInputToolbarStopButtonLabel : labels.chatInputToolbarSendButtonLabel,
1185
+ children: showsStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Square, { className: "cpk:size-[18px] cpk:fill-current" }) : void 0
1171
1186
  });
1172
1187
  const BoundStartTranscribeButton = renderSlot(startTranscribeButton, CopilotChatInput.StartTranscribeButton, { onClick: onStartTranscribe });
1173
1188
  const BoundCancelTranscribeButton = renderSlot(cancelTranscribeButton, CopilotChatInput.CancelTranscribeButton, { onClick: onCancelTranscribe });
@@ -1479,18 +1494,22 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1479
1494
  });
1480
1495
  }
1481
1496
  (function(_CopilotChatInput) {
1482
- _CopilotChatInput.SendButton = ({ className, children, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1483
- className: "cpk:mr-[10px]",
1484
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1485
- type: "button",
1486
- "data-testid": "copilot-send-button",
1487
- variant: "chatInputToolbarPrimary",
1488
- size: "chatInputToolbarIcon",
1489
- className,
1490
- ...props,
1491
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
1492
- })
1493
- });
1497
+ _CopilotChatInput.SendButton = ({ className, children, ...props }) => {
1498
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1499
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1500
+ className: "cpk:mr-[10px]",
1501
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1502
+ type: "button",
1503
+ "data-testid": "copilot-send-button",
1504
+ variant: "chatInputToolbarPrimary",
1505
+ size: "chatInputToolbarIcon",
1506
+ className,
1507
+ "aria-label": labels.chatInputToolbarSendButtonLabel,
1508
+ ...props,
1509
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
1510
+ })
1511
+ });
1512
+ };
1494
1513
  const ToolbarButton = _CopilotChatInput.ToolbarButton = ({ icon, labelKey, defaultClassName, className, ...props }) => {
1495
1514
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1496
1515
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
@@ -1500,6 +1519,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1500
1519
  variant: "chatInputToolbarSecondary",
1501
1520
  size: "chatInputToolbarIcon",
1502
1521
  className: (0, tailwind_merge.twMerge)(defaultClassName, className),
1522
+ "aria-label": labels[labelKey],
1503
1523
  ...props,
1504
1524
  children: icon
1505
1525
  })
@@ -1570,6 +1590,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1570
1590
  size: "chatInputToolbarIcon",
1571
1591
  className: (0, tailwind_merge.twMerge)("cpk:ml-1", className),
1572
1592
  disabled: isDisabled,
1593
+ "aria-label": labels.chatInputToolbarAddButtonLabel,
1573
1594
  ...props,
1574
1595
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "cpk:size-[20px]" })
1575
1596
  });
@@ -1719,6 +1740,32 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1719
1740
  }
1720
1741
  }
1721
1742
  /**
1743
+ * The tool-call renderers registered on the provider, kept current.
1744
+ * @internal
1745
+ */
1746
+ function useToolCallRenderers() {
1747
+ const { copilotkit } = useCopilotKit();
1748
+ return (0, react.useSyncExternalStore)((callback) => copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
1749
+ }
1750
+ /**
1751
+ * Which tool names have a renderer of their own, whether a wildcard (`"*"`)
1752
+ * renderer is registered, and which of them opted into grouping.
1753
+ * @internal
1754
+ */
1755
+ function useToolCallRendererNames() {
1756
+ const renderers = useToolCallRenderers();
1757
+ return (0, react.useMemo)(() => {
1758
+ const named = renderers.filter((rc) => rc.name !== "*");
1759
+ const wildcards = renderers.filter((rc) => rc.name === "*");
1760
+ return {
1761
+ named: new Set(named.map((rc) => rc.name)),
1762
+ groupedNames: new Set(named.filter((rc) => rc.group).map((rc) => rc.name)),
1763
+ wildcard: wildcards.length > 0,
1764
+ wildcardGroups: wildcards.some((rc) => rc.group)
1765
+ };
1766
+ }, [renderers]);
1767
+ }
1768
+ /**
1722
1769
  * Hook that returns a function to render tool calls based on the render functions
1723
1770
  * defined in CopilotKitProvider.
1724
1771
  *
@@ -1729,9 +1776,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1729
1776
  const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
1730
1777
  const unrenderedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
1731
1778
  const warnedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
1732
- const renderToolCalls = (0, react.useSyncExternalStore)((callback) => {
1733
- return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
1734
- }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
1779
+ const renderToolCalls = useToolCallRenderers();
1735
1780
  const renderToolCall = (0, react.useCallback)(({ toolCall, toolMessage }) => {
1736
1781
  const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
1737
1782
  const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
@@ -1766,7 +1811,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
1766
1811
 
1767
1812
  //#endregion
1768
1813
  //#region package.json
1769
- var version = "1.79.2-canary.1792172552";
1814
+ var version = "1.80.0";
1770
1815
 
1771
1816
  //#endregion
1772
1817
  //#region src/v2/components/CopilotKitInspector.tsx
@@ -2002,7 +2047,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
2002
2047
  * Fetches resource content on-demand via proxied MCP requests. The React shell
2003
2048
  * owns the iframe; `bindMcpApp` owns the protocol.
2004
2049
  */
2005
- const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, message, agent }) {
2050
+ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, message, agent, allowSameOriginFallback }) {
2006
2051
  const { copilotkit } = useCopilotKit();
2007
2052
  const messageId = message?.id;
2008
2053
  const containerRef = (0, react.useRef)(null);
@@ -2052,6 +2097,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
2052
2097
  getAgent: () => agentRef.current,
2053
2098
  host: copilotkit,
2054
2099
  messageId,
2100
+ options: { allowSameOriginFallback },
2055
2101
  hooks: {
2056
2102
  onResource: (resource) => {
2057
2103
  if (!mounted) return;
@@ -2093,6 +2139,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
2093
2139
  agent,
2094
2140
  copilotkit,
2095
2141
  messageId,
2142
+ allowSameOriginFallback,
2096
2143
  content.resourceUri,
2097
2144
  content.serverHash,
2098
2145
  content.serverId
@@ -3307,7 +3354,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
3307
3354
  name: def.name,
3308
3355
  args: argsSchema,
3309
3356
  render: def.render,
3310
- ...def.agentId ? { agentId: def.agentId } : {}
3357
+ ...def.agentId ? { agentId: def.agentId } : {},
3358
+ ...def.group ? { group: true } : {}
3311
3359
  };
3312
3360
  }
3313
3361
 
@@ -3427,6 +3475,11 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
3427
3475
  - Use CSS Grid or Flexbox for layout. Ensure the UI looks good at any width.
3428
3476
  - Minimal transitions (150ms) for hover/focus states only. No decorative animations.
3429
3477
  - Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
3478
+ /** The MCP Apps renderer with `mcpApps.allowSameOriginFallback` applied. */
3479
+ const SameOriginMCPAppsActivityRenderer = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MCPAppsActivityRenderer, {
3480
+ ...props,
3481
+ allowSameOriginFallback: true
3482
+ });
3430
3483
  const DEFAULT_LEARNING = Object.freeze({});
3431
3484
  function toLearningConfig(learning) {
3432
3485
  if (learning === true) return DEFAULT_LEARNING;
@@ -3443,7 +3496,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
3443
3496
  }, [value, warningMessage]);
3444
3497
  return value;
3445
3498
  }
3446
- const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, messageFilter, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, agentId, showIntelligenceIndicator = true, useSingleEndpoint, onError, a2ui, defaultThrottleMs, debug, learning }) => {
3499
+ /** Provide a shared core instance and synchronize runtime, tool, and UI configuration. */
3500
+ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY_HEADERS, credentials, messageFilter, publicApiKey, publicLicenseKey, licenseToken, properties = EMPTY_PROPERTIES, agents__unsafe_dev_only: agents = EMPTY_AGENTS, selfManagedAgents = EMPTY_AGENTS, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, enableInspector, agentId, showIntelligenceIndicator = true, useSingleEndpoint, onError, a2ui, mcpApps, defaultThrottleMs, debug, learning }) => {
3447
3501
  const learningConfig = (0, react.useMemo)(() => toLearningConfig(learning), [learning]);
3448
3502
  const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
3449
3503
  const [inspectorVisible, setInspectorVisible] = (0, react.useState)(false);
@@ -3500,11 +3554,12 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
3500
3554
  const core = copilotkitRef.current;
3501
3555
  return (0, _copilotkit_a2ui_renderer.filterCatalog)(rawCatalog, (name) => core ? core.isCatalogComponentEnabled(name) : true);
3502
3556
  }, [rawCatalog, catalogToggleVersion]);
3557
+ const mcpAppsAllowSameOrigin = mcpApps?.allowSameOriginFallback === true;
3503
3558
  const builtInActivityRenderers = (0, react.useMemo)(() => {
3504
3559
  const renderers = [{
3505
3560
  activityType: _copilotkit_mcp_apps_renderer_activity.MCPAppsActivityType,
3506
3561
  content: _copilotkit_mcp_apps_renderer_activity.MCPAppsActivityContentSchema,
3507
- render: MCPAppsActivityRenderer
3562
+ render: mcpAppsAllowSameOrigin ? SameOriginMCPAppsActivityRenderer : MCPAppsActivityRenderer
3508
3563
  }];
3509
3564
  if (openGenUIActive) renderers.push({
3510
3565
  activityType: OpenGenerativeUIActivityType,
@@ -3522,7 +3577,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
3522
3577
  a2uiActive,
3523
3578
  openGenUIActive,
3524
3579
  a2ui,
3525
- filteredCatalog
3580
+ filteredCatalog,
3581
+ mcpAppsAllowSameOrigin
3526
3582
  ]);
3527
3583
  const allActivityRenderers = (0, react.useMemo)(() => {
3528
3584
  return [...renderActivityMessagesList, ...builtInActivityRenderers];
@@ -3630,6 +3686,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
3630
3686
  description: _copilotkit_shared.GENERATE_SANDBOXED_UI_DESCRIPTION,
3631
3687
  parameters: GenerateSandboxedUiArgsSchema,
3632
3688
  handler: async () => "UI generated",
3689
+ reconnectBehavior: "resume-pending",
3633
3690
  followUp: true,
3634
3691
  render: OpenGenerativeUIToolRenderer
3635
3692
  }];
@@ -4361,7 +4418,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
4361
4418
  ...props,
4362
4419
  parameters: props.args
4363
4420
  }),
4364
- ...config.agentId ? { agentId: config.agentId } : {}
4421
+ ...config.agentId ? { agentId: config.agentId } : {},
4422
+ group: config.group
4365
4423
  }) : defineToolCallRenderer({
4366
4424
  name: config.name,
4367
4425
  args: config.parameters,
@@ -4379,16 +4437,395 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
4379
4437
  parameters: props.args
4380
4438
  });
4381
4439
  },
4382
- ...config.agentId ? { agentId: config.agentId } : {}
4440
+ ...config.agentId ? { agentId: config.agentId } : {},
4441
+ group: config.group
4383
4442
  });
4384
4443
  copilotkit.addHookRenderToolCall(renderer);
4385
4444
  }, [
4386
4445
  config.name,
4446
+ config.group,
4387
4447
  copilotkit,
4388
4448
  depsVersion
4389
4449
  ]);
4390
4450
  }
4391
4451
 
4452
+ //#endregion
4453
+ //#region src/v2/components/chat/CopilotChatToolCallGroup.tsx
4454
+ /**
4455
+ * Set by a group around the calls it shows, so CopilotKit's built-in tool card
4456
+ * draws itself as a compact row that fits the group instead of a card.
4457
+ * @internal
4458
+ */
4459
+ const ToolCallGroupContext = (0, react.createContext)(null);
4460
+ /**
4461
+ * A call without a result is running only while the group is active: once
4462
+ * the run has moved on or ended, its result is never coming.
4463
+ */
4464
+ const entryStatus = ({ toolMessage }, active) => toolMessage ? toolMessage.error ? "failed" : "done" : active ? "running" : "stopped";
4465
+ /** @internal */
4466
+ const statusLabels = {
4467
+ running: "Running",
4468
+ done: "Done",
4469
+ failed: "Failed",
4470
+ stopped: "Stopped"
4471
+ };
4472
+ /** The first non-empty string argument: usually a command, path or query. */
4473
+ function argumentPreview(parameters) {
4474
+ if (!parameters || typeof parameters !== "object") return void 0;
4475
+ return Object.values(parameters).find((v) => typeof v === "string" && v.trim().length > 0)?.replace(/\s+/g, " ");
4476
+ }
4477
+ const entryPreview = (entry) => argumentPreview((0, _copilotkit_shared.partialJSONParse)(entry.toolCall.function.arguments));
4478
+ /** "run_command ×2, read_file" */
4479
+ function toolNames(calls) {
4480
+ const counts = /* @__PURE__ */ new Map();
4481
+ for (const { toolCall } of calls) {
4482
+ const name = toolCall.function.name;
4483
+ counts.set(name, (counts.get(name) ?? 0) + 1);
4484
+ }
4485
+ return [...counts].map(([name, count]) => count > 1 ? `${name} ×${count}` : name).join(", ");
4486
+ }
4487
+ const pluralize = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
4488
+ /**
4489
+ * Height transition between closed and its content's height. Closed content
4490
+ * stays mounted (so its state survives) but is inert.
4491
+ * @internal
4492
+ */
4493
+ function Collapsible({ open, id, className, children }) {
4494
+ const ref = (0, react.useRef)(null);
4495
+ (0, react.useLayoutEffect)(() => {
4496
+ ref.current?.toggleAttribute("inert", !open);
4497
+ }, [open]);
4498
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4499
+ ref,
4500
+ id,
4501
+ className: (0, tailwind_merge.twMerge)("cpk:grid cpk:transition-[grid-template-rows] cpk:duration-300 cpk:ease-[cubic-bezier(0.16,1,0.3,1)] cpk:motion-reduce:transition-none", className),
4502
+ style: { gridTemplateRows: open ? "1fr" : "0fr" },
4503
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4504
+ className: "cpk:min-h-0 cpk:overflow-hidden",
4505
+ children
4506
+ })
4507
+ });
4508
+ }
4509
+ function StatusIcon({ status, className }) {
4510
+ const Icon = {
4511
+ running: lucide_react.Loader2,
4512
+ done: lucide_react.Check,
4513
+ failed: lucide_react.X,
4514
+ stopped: lucide_react.Circle
4515
+ }[status];
4516
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4517
+ "aria-hidden": "true",
4518
+ className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:text-muted-foreground", status === "running" && "cpk:animate-spin", status === "failed" && "cpk:text-destructive", className)
4519
+ });
4520
+ }
4521
+ function Chevron({ open, className }) {
4522
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
4523
+ "aria-hidden": "true",
4524
+ className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", open && "cpk:rotate-90", className)
4525
+ });
4526
+ }
4527
+ function FailedCount({ calls }) {
4528
+ const failed = calls.filter((call) => call.toolMessage?.error).length;
4529
+ if (!failed) return null;
4530
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4531
+ className: "cpk:shrink-0 cpk:text-destructive",
4532
+ children: [failed, " failed"]
4533
+ });
4534
+ }
4535
+ /**
4536
+ * Guarded JSON.stringify for the payload blocks. A circular reference would
4537
+ * otherwise crash the entire React tree on render.
4538
+ */
4539
+ function stringifyPayload(value) {
4540
+ if (typeof value === "string") return value;
4541
+ try {
4542
+ return JSON.stringify(value, null, 2);
4543
+ } catch (err) {
4544
+ console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload; falling back to String():", err);
4545
+ try {
4546
+ return String(value);
4547
+ } catch {
4548
+ return "[unserializable]";
4549
+ }
4550
+ }
4551
+ }
4552
+ function Payload({ label, value }) {
4553
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4554
+ className: "cpk:text-[11px] cpk:font-medium cpk:uppercase cpk:tracking-wide cpk:text-muted-foreground",
4555
+ children: label
4556
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
4557
+ className: "cpk:m-0 cpk:mt-1.5 cpk:max-h-[200px] cpk:overflow-auto cpk:rounded-lg cpk:bg-muted cpk:p-2.5 cpk:font-mono cpk:text-xs cpk:leading-relaxed cpk:text-foreground cpk:whitespace-pre-wrap cpk:break-words",
4558
+ children: stringifyPayload(value)
4559
+ })] });
4560
+ }
4561
+ /**
4562
+ * A tool call's arguments and, once it has one, its result.
4563
+ * @internal
4564
+ */
4565
+ function ToolCallPayloads({ parameters, result, className }) {
4566
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4567
+ className: (0, tailwind_merge.twMerge)("cpk:grid cpk:gap-3", className),
4568
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Payload, {
4569
+ label: "Arguments",
4570
+ value: parameters ?? {}
4571
+ }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Payload, {
4572
+ label: "Result",
4573
+ value: result
4574
+ })]
4575
+ });
4576
+ }
4577
+ /**
4578
+ * The built-in tool card drawn as a row inside a group: status, name and main
4579
+ * argument, opening to the payloads.
4580
+ * @internal
4581
+ */
4582
+ function ToolCallRow({ name, parameters, result, status, appearance, className, ...props }) {
4583
+ const [open, setOpen] = (0, react.useState)(false);
4584
+ const detailsId = (0, react.useId)();
4585
+ const preview = argumentPreview(parameters);
4586
+ const line = appearance === "line";
4587
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4588
+ "data-copilotkit": true,
4589
+ className: (0, tailwind_merge.twMerge)(!line && "cpk:border-t cpk:border-border", className),
4590
+ ...props,
4591
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4592
+ type: "button",
4593
+ "aria-expanded": open,
4594
+ "aria-controls": detailsId,
4595
+ onClick: () => setOpen((value) => !value),
4596
+ className: (0, tailwind_merge.twMerge)("cpk:group/row cpk:m-0 cpk:flex cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:gap-2 cpk:border-none cpk:bg-transparent cpk:text-left cpk:text-[13px] cpk:text-inherit cpk:transition-colors cpk:hover:bg-accent/60 cpk:focus-visible:outline-2 cpk:focus-visible:-outline-offset-2 cpk:focus-visible:outline-ring", line ? "cpk:-mx-1.5 cpk:w-[calc(100%+0.75rem)] cpk:rounded-md cpk:px-1.5 cpk:py-1" : "cpk:w-full cpk:px-3.5 cpk:py-2"),
4597
+ children: [
4598
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status }),
4599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4600
+ "data-testid": "copilot-tool-render-name",
4601
+ className: (0, tailwind_merge.twMerge)("cpk:shrink-0 cpk:font-mono cpk:text-[13px] cpk:font-medium cpk:text-foreground", status === "running" && "cpk-shimmer"),
4602
+ children: name
4603
+ }),
4604
+ preview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4605
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-[13px] cpk:text-muted-foreground",
4606
+ children: preview
4607
+ }),
4608
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4609
+ "data-testid": "copilot-tool-render-status",
4610
+ className: "cpk:sr-only",
4611
+ children: statusLabels[status]
4612
+ }),
4613
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
4614
+ open,
4615
+ className: (0, tailwind_merge.twMerge)("cpk:ml-auto cpk:text-muted-foreground", !open && "cpk:opacity-0 cpk:group-hover/row:opacity-100 cpk:group-focus-visible/row:opacity-100")
4616
+ })
4617
+ ]
4618
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
4619
+ open,
4620
+ id: detailsId,
4621
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallPayloads, {
4622
+ parameters,
4623
+ result,
4624
+ className: line ? "cpk:mt-1 cpk:mb-2 cpk:ml-5.5" : "cpk:px-3.5 cpk:pt-1 cpk:pb-3"
4625
+ })
4626
+ })]
4627
+ });
4628
+ }
4629
+ /**
4630
+ * Tool calls made in a row, collapsed into one summary. While a call runs the
4631
+ * summary names it ("Running read_file src/app.ts"); once they're all done it
4632
+ * reads "Used 3 tools" and opens to the calls.
4633
+ *
4634
+ * Each call is drawn by its registered renderer. CopilotKit's built-in card
4635
+ * (`useDefaultRenderTool`) draws a compact row inside the card and line
4636
+ * appearances; a custom wildcard renderer draws whatever it draws.
4637
+ */
4638
+ function CopilotChatToolCallGroup({ calls, renderCall, appearance = "card", isActive = false, className, ...props }) {
4639
+ const [open, setOpen] = (0, react.useState)(false);
4640
+ const bodyId = (0, react.useId)();
4641
+ const latest = calls[calls.length - 1];
4642
+ if (!latest) return null;
4643
+ const current = isActive ? calls.find((call) => !call.toolMessage) ?? latest : void 0;
4644
+ const preview = current ? entryPreview(current) : void 0;
4645
+ const done = calls.filter((call) => call.toolMessage).length;
4646
+ const statuses = calls.map((call) => entryStatus(call, isActive));
4647
+ const failedIds = new Set(calls.filter((_, index) => statuses[index] === "failed").map((call) => call.toolCall.id));
4648
+ const status = current ? "running" : failedIds.size > 0 ? "failed" : statuses.includes("stopped") ? "stopped" : "done";
4649
+ const rows = (rowAppearance) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
4650
+ value: {
4651
+ appearance: rowAppearance,
4652
+ failedIds,
4653
+ active: isActive
4654
+ },
4655
+ children: calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
4656
+ });
4657
+ const groupProps = {
4658
+ "data-copilotkit": true,
4659
+ "data-testid": "copilot-tool-call-group",
4660
+ "data-appearance": appearance,
4661
+ "data-state": status,
4662
+ ...props
4663
+ };
4664
+ const runningLabel = current && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4665
+ className: "cpk:shrink-0 cpk:font-medium cpk-shimmer",
4666
+ children: ["Running ", current.toolCall.function.name]
4667
+ }), preview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4668
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:opacity-70",
4669
+ children: preview
4670
+ })] });
4671
+ if (appearance === "line") {
4672
+ if (calls.length === 1 && !current) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4673
+ ...groupProps,
4674
+ className: (0, tailwind_merge.twMerge)("cpk:my-2", className),
4675
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
4676
+ value: {
4677
+ appearance: "line",
4678
+ failedIds,
4679
+ active: isActive
4680
+ },
4681
+ children: renderCall(latest)
4682
+ })
4683
+ });
4684
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4685
+ ...groupProps,
4686
+ className: (0, tailwind_merge.twMerge)("cpk:my-3", className),
4687
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4688
+ type: "button",
4689
+ "aria-expanded": open,
4690
+ "aria-controls": bodyId,
4691
+ onClick: () => setOpen((value) => !value),
4692
+ className: "cpk:m-0 cpk:inline-flex cpk:max-w-full cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:gap-1.5 cpk:rounded-md cpk:border-none cpk:bg-transparent cpk:p-0 cpk:py-0.5 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:hover:text-foreground cpk:focus-visible:outline-2 cpk:focus-visible:outline-ring",
4693
+ children: [runningLabel || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4694
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4695
+ className: "cpk:shrink-0 cpk:font-medium",
4696
+ children: ["Used ", pluralize(calls.length, "tool")]
4697
+ }),
4698
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4699
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:opacity-70",
4700
+ children: toolNames(calls)
4701
+ }),
4702
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls })
4703
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
4704
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
4705
+ open,
4706
+ id: bodyId,
4707
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4708
+ className: "cpk:mt-1 cpk:ml-1 cpk:border-l-2 cpk:border-border cpk:py-0.5 cpk:pl-3",
4709
+ children: rows("line")
4710
+ })
4711
+ })]
4712
+ });
4713
+ }
4714
+ if (appearance === "stack") {
4715
+ if (calls.length === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4716
+ ...groupProps,
4717
+ className: (0, tailwind_merge.twMerge)("cpk:py-1", className),
4718
+ children: renderCall(latest)
4719
+ });
4720
+ const behind = open ? 0 : Math.min(calls.length - 1, 2);
4721
+ const layer = "cpk:rounded-xl cpk:border cpk:border-border cpk:bg-card cpk:text-card-foreground";
4722
+ const latestStatus = entryStatus(latest, isActive);
4723
+ const latestPreview = entryPreview(latest);
4724
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4725
+ ...groupProps,
4726
+ className: (0, tailwind_merge.twMerge)("cpk:my-3", className),
4727
+ style: {
4728
+ paddingBottom: behind * 6,
4729
+ ...props.style
4730
+ },
4731
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4732
+ className: "cpk:relative cpk:isolate",
4733
+ children: [Array.from({ length: behind }, (_, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4734
+ "aria-hidden": "true",
4735
+ className: (0, tailwind_merge.twMerge)(layer, "cpk:absolute cpk:inset-0"),
4736
+ style: {
4737
+ transform: `translateY(${(index + 1) * 6}px) scaleX(${1 - (index + 1) * .045})`,
4738
+ zIndex: -1 - index,
4739
+ opacity: 1 - (index + 1) * .28
4740
+ }
4741
+ }, index)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4742
+ type: "button",
4743
+ "aria-expanded": open,
4744
+ "aria-controls": open ? bodyId : void 0,
4745
+ onClick: () => setOpen((value) => !value),
4746
+ className: (0, tailwind_merge.twMerge)("cpk:relative cpk:m-0 cpk:flex cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:text-left cpk:transition-colors cpk:focus-visible:outline-2 cpk:focus-visible:outline-ring", open ? "cpk:mb-1 cpk:gap-1.5 cpk:rounded-md cpk:border-none cpk:bg-transparent cpk:p-0 cpk:py-0.5 cpk:text-xs cpk:text-muted-foreground cpk:hover:text-foreground" : (0, tailwind_merge.twMerge)(layer, "cpk:w-full cpk:gap-2 cpk:px-3.5 cpk:py-2.5 cpk:text-[13px] cpk:shadow-[0_1px_2px_rgb(0_0_0/0.04)] cpk:hover:bg-accent")),
4747
+ children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4748
+ pluralize(calls.length, "tool call"),
4749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }),
4750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
4751
+ open: true,
4752
+ className: "cpk:-rotate-90"
4753
+ }),
4754
+ "Collapse"
4755
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4756
+ className: "cpk:flex cpk:min-w-0 cpk:flex-1 cpk:items-center cpk:gap-2 cpk:animate-[cpk-stack-drop_260ms_cubic-bezier(0.16,1,0.3,1)] cpk:motion-reduce:animate-none",
4757
+ children: [
4758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status: latestStatus }),
4759
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4760
+ className: (0, tailwind_merge.twMerge)("cpk:shrink-0 cpk:font-mono cpk:font-medium cpk:text-foreground", latestStatus === "running" && "cpk-shimmer"),
4761
+ children: latest.toolCall.function.name
4762
+ }),
4763
+ latestPreview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4764
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-muted-foreground",
4765
+ children: latestPreview
4766
+ })
4767
+ ]
4768
+ }, latest.toolCall.id), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4769
+ className: "cpk:ml-2 cpk:inline-flex cpk:shrink-0 cpk:items-center cpk:gap-2 cpk:text-xs cpk:text-muted-foreground",
4770
+ children: [
4771
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }),
4772
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4773
+ className: "cpk:tabular-nums",
4774
+ children: pluralize(calls.length, "call")
4775
+ }),
4776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
4777
+ open: false,
4778
+ className: "cpk:rotate-90"
4779
+ })
4780
+ ]
4781
+ })] })
4782
+ })]
4783
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4784
+ id: bodyId,
4785
+ className: "cpk:[&>*]:animate-[cpk-stack-fan_300ms_cubic-bezier(0.16,1,0.3,1)_both] cpk:motion-reduce:[&>*]:animate-none",
4786
+ children: calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
4787
+ })]
4788
+ });
4789
+ }
4790
+ if (calls.length === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4791
+ ...groupProps,
4792
+ className: (0, tailwind_merge.twMerge)("cpk:py-1", className),
4793
+ children: renderCall(latest)
4794
+ });
4795
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4796
+ ...groupProps,
4797
+ className: (0, tailwind_merge.twMerge)("cpk:my-3 cpk:overflow-hidden cpk:rounded-xl cpk:border cpk:border-border cpk:bg-card cpk:text-card-foreground", className),
4798
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4799
+ type: "button",
4800
+ "aria-expanded": open,
4801
+ "aria-controls": bodyId,
4802
+ onClick: () => setOpen((value) => !value),
4803
+ className: "cpk:m-0 cpk:flex cpk:w-full cpk:min-w-0 cpk:cursor-pointer cpk:items-center cpk:gap-2.5 cpk:border-none cpk:bg-transparent cpk:px-3.5 cpk:py-2.5 cpk:text-left cpk:text-[13px] cpk:text-inherit cpk:transition-colors cpk:hover:bg-accent/60 cpk:focus-visible:outline-2 cpk:focus-visible:-outline-offset-2 cpk:focus-visible:outline-ring",
4804
+ children: [
4805
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status }),
4806
+ runningLabel || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4807
+ className: "cpk:shrink-0 cpk:font-medium cpk:text-foreground",
4808
+ children: ["Used ", pluralize(calls.length, "tool")]
4809
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4810
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:text-muted-foreground",
4811
+ children: toolNames(calls)
4812
+ })] }),
4813
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4814
+ className: "cpk:ml-auto cpk:flex cpk:shrink-0 cpk:items-center cpk:gap-2 cpk:pl-2 cpk:text-xs cpk:text-muted-foreground",
4815
+ children: [status === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4816
+ className: "cpk:tabular-nums",
4817
+ children: status === "running" ? `${done} done` : statusLabels[status]
4818
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
4819
+ })
4820
+ ]
4821
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
4822
+ open,
4823
+ id: bodyId,
4824
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: rows("card") })
4825
+ })]
4826
+ });
4827
+ }
4828
+
4392
4829
  //#endregion
4393
4830
  //#region src/v2/hooks/use-default-render-tool.tsx
4394
4831
  /**
@@ -4472,7 +4909,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
4472
4909
  const userRender = config?.render;
4473
4910
  useRenderTool({
4474
4911
  name: "*",
4475
- render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) })
4912
+ render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) }),
4913
+ group: userRender ? config?.group === true : true
4476
4914
  }, deps);
4477
4915
  }
4478
4916
  /**
@@ -4493,9 +4931,23 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
4493
4931
  }
4494
4932
  }
4495
4933
  function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
4934
+ const group = (0, react.useContext)(ToolCallGroupContext);
4496
4935
  const [isExpanded, setIsExpanded] = (0, react.useState)(false);
4497
4936
  const isActive = status === "inProgress" || status === "executing";
4498
4937
  const isComplete = status === "complete";
4938
+ if (group) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRow, {
4939
+ "data-testid": "copilot-tool-render",
4940
+ "data-tool-name": name,
4941
+ "data-tool-call-id": toolCallId,
4942
+ "data-status": status,
4943
+ "data-args": safeStringifyForAttr(parameters),
4944
+ "data-result": safeStringifyForAttr(result),
4945
+ name,
4946
+ parameters,
4947
+ result,
4948
+ status: isComplete ? group.failedIds.has(toolCallId) ? "failed" : "done" : group.active ? "running" : "stopped",
4949
+ appearance: group.appearance
4950
+ });
4499
4951
  const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
4500
4952
  const dotClassName = isActive ? "cpk:bg-amber-500" : isComplete ? "cpk:bg-emerald-500" : "cpk:bg-zinc-400";
4501
4953
  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";
@@ -6192,38 +6644,227 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6192
6644
  });
6193
6645
  }
6194
6646
 
6647
+ //#endregion
6648
+ //#region src/v2/components/chat/tool-call-display-context.ts
6649
+ /** @internal */
6650
+ const ToolCallDisplayContext = (0, react.createContext)(null);
6651
+
6652
+ //#endregion
6653
+ //#region src/v2/components/chat/tool-call-layout.ts
6654
+ function contentLength(content) {
6655
+ return typeof content === "string" ? content.length : JSON.stringify(content ?? "").length;
6656
+ }
6657
+ const entrySignature = ({ toolCall, toolMessage }) => [
6658
+ toolCall.id,
6659
+ toolCall.function.name,
6660
+ toolCall.function.arguments.length,
6661
+ toolMessage ? `${contentLength(toolMessage.content)}${toolMessage.error ? "!" : ""}` : "-"
6662
+ ].join(":");
6663
+ function signatureOf(segments) {
6664
+ return segments.map((segment) => segment.kind === "call" ? `c(${entrySignature(segment.call)})` : `g${segment.trailing ? "*" : ""}(${segment.calls.map(entrySignature).join(",")})`).join("|");
6665
+ }
6666
+ /** Index of tool results by the call they answer. */
6667
+ function getToolResults(messages) {
6668
+ const results = /* @__PURE__ */ new Map();
6669
+ for (const message of messages) if (message.role === "tool") results.set(message.toolCallId, message);
6670
+ return results;
6671
+ }
6672
+ /**
6673
+ * Lays out the tool calls of every assistant message in `rendered`.
6674
+ *
6675
+ * Calls drawn by a renderer that opted into grouping join the group that's
6676
+ * open; anything else that shows between them closes it: assistant text, a
6677
+ * user, reasoning or activity message, or a call drawn by any other renderer. A group belongs to
6678
+ * the message where it starts, so later messages whose calls joined it render
6679
+ * no tool calls themselves.
6680
+ *
6681
+ * @param rendered - The messages as the chat renders them, in order.
6682
+ * @param all - Every message, for finding tool results (a transform may hide them).
6683
+ * @param renderers - The registered renderers.
6684
+ */
6685
+ function getToolCallLayouts(rendered, all, renderers) {
6686
+ const results = getToolResults(all);
6687
+ const layouts = /* @__PURE__ */ new Map();
6688
+ let open;
6689
+ for (const message of rendered) {
6690
+ if (message.role === "tool") continue;
6691
+ if (message.role !== "assistant") {
6692
+ open = void 0;
6693
+ continue;
6694
+ }
6695
+ if (typeof message.content === "string" && message.content.trim()) open = void 0;
6696
+ const segments = [];
6697
+ layouts.set(message.id, segments);
6698
+ const toolCalls = /* @__PURE__ */ new Map();
6699
+ for (const toolCall of message.toolCalls ?? []) toolCalls.set(toolCall.id, toolCall);
6700
+ for (const toolCall of toolCalls.values()) {
6701
+ const entry = {
6702
+ toolCall,
6703
+ toolMessage: results.get(toolCall.id)
6704
+ };
6705
+ const name = toolCall.function.name;
6706
+ const named = renderers.named.has(name);
6707
+ const rendered = named || renderers.wildcard;
6708
+ const groups = named ? renderers.groupedNames.has(name) : renderers.wildcardGroups;
6709
+ if (!rendered || !groups) {
6710
+ if (rendered) open = void 0;
6711
+ segments.push({
6712
+ kind: "call",
6713
+ id: toolCall.id,
6714
+ call: entry,
6715
+ rendered
6716
+ });
6717
+ } else if (open) open.calls.push(entry);
6718
+ else {
6719
+ open = {
6720
+ kind: "group",
6721
+ id: toolCall.id,
6722
+ calls: [entry],
6723
+ trailing: false
6724
+ };
6725
+ segments.push(open);
6726
+ }
6727
+ }
6728
+ }
6729
+ if (open) open.trailing = true;
6730
+ const layout = /* @__PURE__ */ new Map();
6731
+ for (const [id, segments] of layouts) layout.set(id, {
6732
+ segments,
6733
+ signature: signatureOf(segments)
6734
+ });
6735
+ return layout;
6736
+ }
6737
+ /**
6738
+ * Finds the working phase of every reply in `rendered`: its messages up to the
6739
+ * last one that calls a tool. Text in there was narration; what follows is the
6740
+ * reply itself. Replies that called no tools are omitted, except the latest,
6741
+ * which may be about to.
6742
+ */
6743
+ function getTurnWork(rendered, layouts) {
6744
+ const turns = /* @__PURE__ */ new Map();
6745
+ let start = 0;
6746
+ while (start < rendered.length) {
6747
+ let end = start;
6748
+ while (end < rendered.length && rendered[end].role !== "user") end++;
6749
+ const key = start > 0 ? rendered[start - 1].id : "start";
6750
+ const visible = rendered.slice(start, end).filter((message) => message.role !== "tool");
6751
+ const isLatest = end === rendered.length;
6752
+ const calls = [];
6753
+ for (const message of visible) for (const segment of layouts.get(message.id)?.segments ?? []) if (segment.kind === "group") calls.push(...segment.calls);
6754
+ else if (segment.rendered) calls.push(segment.call);
6755
+ const shown = new Set(calls.map((entry) => entry.toolCall.id));
6756
+ let lastWork = -1;
6757
+ visible.forEach((message, index) => {
6758
+ if (message.role === "assistant" && message.toolCalls?.some((call) => shown.has(call.id))) lastWork = index;
6759
+ });
6760
+ const first = visible[0];
6761
+ if (first && (calls.length > 0 || isLatest)) {
6762
+ let lastAssistant = -1;
6763
+ visible.forEach((message, index) => {
6764
+ if (message.role === "assistant") lastAssistant = index;
6765
+ });
6766
+ const replyText = visible[lastAssistant]?.content;
6767
+ const endsWithWork = lastAssistant >= 0 && lastAssistant <= lastWork && typeof replyText === "string" && replyText.trim().length > 0;
6768
+ turns.set(key, {
6769
+ key,
6770
+ firstMessageId: first.id,
6771
+ workMessageIds: new Set(visible.slice(0, lastWork + 1).map((message) => message.id)),
6772
+ replyTextMessageId: endsWithWork ? visible[lastAssistant].id : void 0,
6773
+ calls,
6774
+ isLatest
6775
+ });
6776
+ }
6777
+ start = end + 1;
6778
+ }
6779
+ return turns;
6780
+ }
6781
+
6195
6782
  //#endregion
6196
6783
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
6197
- function CopilotChatToolCallsView({ message, messages = [] }) {
6784
+ const appearances = {
6785
+ grouped: "card",
6786
+ line: "line",
6787
+ stacked: "stack"
6788
+ };
6789
+ function CopilotChatToolCallsView({ message, messages = [], display: displayProp, group }) {
6198
6790
  const renderToolCall = useRenderToolCall();
6791
+ const rendererNames = useToolCallRendererNames();
6199
6792
  const { copilotkit } = useCopilotKit();
6200
6793
  const subscribe = (0, react.useCallback)((onChange) => copilotkit.ɵsubscribeToLearningConfigured(onChange), [copilotkit]);
6201
6794
  const getSnapshot = () => copilotkit.ɵlearningConfigured;
6202
6795
  const learningConfigured = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
6796
+ const context = (0, react.useContext)(ToolCallDisplayContext);
6797
+ const display = displayProp ?? context?.display ?? "grouped";
6798
+ const viewLayout = context?.layouts.get(message.id);
6799
+ const layout = (0, react.useMemo)(() => {
6800
+ if (display === "separate") return void 0;
6801
+ return viewLayout ?? getToolCallLayouts([message], messages, rendererNames).get(message.id);
6802
+ }, [
6803
+ display,
6804
+ viewLayout,
6805
+ message,
6806
+ messages,
6807
+ rendererNames
6808
+ ]);
6203
6809
  if (!message.toolCalls || message.toolCalls.length === 0) return null;
6204
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: message.toolCalls.map((toolCall) => {
6810
+ const renderCall = (entry) => {
6205
6811
  const rendered = renderToolCall({
6206
- toolCall,
6207
- toolMessage: messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id)
6812
+ toolCall: entry.toolCall,
6813
+ toolMessage: entry.toolMessage
6208
6814
  });
6209
- if (rendered === null) return null;
6210
- if (!learningConfigured) return rendered;
6815
+ if (rendered === null || !learningConfigured) return rendered;
6211
6816
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6212
- "data-tool-call-id": toolCall.id,
6817
+ "data-tool-call-id": entry.toolCall.id,
6213
6818
  style: { display: "contents" },
6214
6819
  children: rendered
6215
- }, toolCall.id);
6820
+ });
6821
+ };
6822
+ if (!layout) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: message.toolCalls.map((toolCall) => {
6823
+ const toolMessage = messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id);
6824
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall({
6825
+ toolCall,
6826
+ toolMessage
6827
+ }) }, toolCall.id);
6828
+ }) });
6829
+ const workOpen = context?.workOpen.get(message.id);
6830
+ const groupActive = (trailing) => trailing && (context ? context.isRunning : true);
6831
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: layout.segments.map((segment) => {
6832
+ if (segment.kind === "call") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(segment.call) }, segment.id);
6833
+ if (display === "turn") {
6834
+ const failedIds = new Set(segment.calls.filter((call) => call.toolMessage?.error).map((call) => call.toolCall.id));
6835
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
6836
+ open: workOpen ?? true,
6837
+ className: "cpk:-mx-2",
6838
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6839
+ className: "cpk:px-2 cpk:py-1.5",
6840
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
6841
+ value: {
6842
+ appearance: "line",
6843
+ failedIds,
6844
+ active: groupActive(segment.trailing)
6845
+ },
6846
+ children: segment.calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
6847
+ })
6848
+ })
6849
+ }, segment.id);
6850
+ }
6851
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderSlot(group, CopilotChatToolCallGroup, {
6852
+ calls: segment.calls,
6853
+ renderCall,
6854
+ appearance: appearances[display] ?? "card",
6855
+ isActive: groupActive(segment.trailing)
6856
+ }) }, segment.id);
6216
6857
  }) });
6217
6858
  }
6218
6859
 
6219
6860
  //#endregion
6220
6861
  //#region src/v2/components/chat/CopilotChatInspectorButton.tsx
6221
6862
  let shortcutsHidden = false;
6222
- const listeners = /* @__PURE__ */ new Set();
6863
+ const listeners$1 = /* @__PURE__ */ new Set();
6223
6864
  const subscribe = (listener) => {
6224
- listeners.add(listener);
6865
+ listeners$1.add(listener);
6225
6866
  return () => {
6226
- listeners.delete(listener);
6867
+ listeners$1.delete(listener);
6227
6868
  };
6228
6869
  };
6229
6870
  const getSnapshot = () => shortcutsHidden;
@@ -6233,7 +6874,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6233
6874
  }
6234
6875
  function hideShortcuts() {
6235
6876
  shortcutsHidden = true;
6236
- listeners.forEach((listener) => listener());
6877
+ listeners$1.forEach((listener) => listener());
6237
6878
  }
6238
6879
  function InspectorKiteIcon({ className, ...props }) {
6239
6880
  const id = (0, react.useId)();
@@ -6499,9 +7140,321 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6499
7140
  });
6500
7141
  }
6501
7142
 
7143
+ //#endregion
7144
+ //#region src/v2/components/ui/popover.tsx
7145
+ function Popover({ ...props }) {
7146
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Root, {
7147
+ "data-slot": "popover",
7148
+ ...props
7149
+ });
7150
+ }
7151
+ function PopoverAnchor({ ...props }) {
7152
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Anchor, {
7153
+ "data-slot": "popover-anchor",
7154
+ ...props
7155
+ });
7156
+ }
7157
+ /**
7158
+ * Popover content in a portal, so it floats over the page instead of taking
7159
+ * room in the layout, with the CopilotKit style scope on it.
7160
+ */
7161
+ const PopoverContent = react.forwardRef(({ className, align = "start", sideOffset = 6, ...props }, ref) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Portal, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Content, {
7162
+ ref,
7163
+ "data-copilotkit": true,
7164
+ "data-copilotkit-popover": true,
7165
+ "data-slot": "popover-content",
7166
+ align,
7167
+ sideOffset,
7168
+ className: cn("cpk:z-50 cpk:outline-none cpk:data-[state=open]:animate-in cpk:data-[state=open]:fade-in-0 cpk:data-[state=open]:zoom-in-95 cpk:data-[side=bottom]:slide-in-from-top-1 cpk:data-[side=top]:slide-in-from-bottom-1", className),
7169
+ ...props
7170
+ }) }));
7171
+ PopoverContent.displayName = "PopoverContent";
7172
+
7173
+ //#endregion
7174
+ //#region src/v2/lib/message-feedback.ts
7175
+ /** Trajectory event name for a rating of one assistant message. */
7176
+ const MESSAGE_FEEDBACK_EVENT_NAME = "message.feedback";
7177
+ /** Longest comment kept, in characters; the rest is cut so the event stays small. */
7178
+ const MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH = 1e3;
7179
+ /** Longest excerpt of the rated response sent with the rating, in characters. */
7180
+ const RESPONSE_EXCERPT_LENGTH = 500;
7181
+ const ratings = /* @__PURE__ */ new Map();
7182
+ const listeners = /* @__PURE__ */ new Map();
7183
+ const threadKey = (agentId, threadId) => JSON.stringify([agentId, threadId]);
7184
+ const messageKey = (agentId, threadId, messageId) => JSON.stringify([
7185
+ agentId,
7186
+ threadId,
7187
+ messageId
7188
+ ]);
7189
+ /** Cuts `text` to `max` characters without splitting a surrogate pair. */
7190
+ const clip = (text, max) => {
7191
+ const characters = Array.from(text);
7192
+ return characters.length > max ? characters.slice(0, max).join("") : text;
7193
+ };
7194
+ /** The comment as kept and sent: trimmed and capped, or none. */
7195
+ const normalizeComment = (comment) => {
7196
+ if (typeof comment !== "string") return void 0;
7197
+ const text = clip(comment.trim(), MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH);
7198
+ return text === "" ? void 0 : text;
7199
+ };
7200
+ /** The rating this page gave one message, if any. */
7201
+ function getMessageFeedback(agentId, threadId, messageId) {
7202
+ return ratings.get(messageKey(agentId, threadId, messageId));
7203
+ }
7204
+ /** Calls `listener` whenever a rating in this Thread changes. */
7205
+ function subscribeToMessageFeedback(agentId, threadId, listener) {
7206
+ const key = threadKey(agentId, threadId);
7207
+ let set = listeners.get(key);
7208
+ if (set === void 0) {
7209
+ set = /* @__PURE__ */ new Set();
7210
+ listeners.set(key, set);
7211
+ }
7212
+ set.add(listener);
7213
+ return () => {
7214
+ set.delete(listener);
7215
+ if (set.size === 0) listeners.delete(key);
7216
+ };
7217
+ }
7218
+ /**
7219
+ * Sets (or, with `null`, clears) this page's rating of one message and sends
7220
+ * it to the active Trajectory. Every change is a new event; the newest event
7221
+ * for a `feedbackId` is the current one. Like any Trajectory event, a change
7222
+ * made while no Trajectory captures, for example during a reconnect, is not
7223
+ * sent; the page still shows it.
7224
+ */
7225
+ function setMessageFeedback(params) {
7226
+ const { copilotkit, agentId, threadId, message } = params;
7227
+ const comment = normalizeComment(params.feedback?.comment);
7228
+ const feedback = params.feedback && {
7229
+ rating: params.feedback.rating,
7230
+ ...comment ? { comment } : {}
7231
+ };
7232
+ const key = messageKey(agentId, threadId, message.id);
7233
+ if (feedback) ratings.set(key, feedback);
7234
+ else ratings.delete(key);
7235
+ for (const listener of listeners.get(threadKey(agentId, threadId)) ?? []) listener();
7236
+ const runId = copilotkit.getRunIdForMessage(agentId, threadId, message.id);
7237
+ copilotkit.emitTrajectoryEvent(MESSAGE_FEEDBACK_EVENT_NAME, {
7238
+ feedbackId: `${threadId}:${message.id}`,
7239
+ threadId,
7240
+ messageId: message.id,
7241
+ agentId,
7242
+ ...runId ? { runId } : {},
7243
+ rating: feedback?.rating ?? null,
7244
+ ...feedback?.comment ? { comment: feedback.comment } : {},
7245
+ ...copilotkit.ɵlearningCapturesAgentText ? { response: clip(message.content ?? "", RESPONSE_EXCERPT_LENGTH) } : {}
7246
+ });
7247
+ }
7248
+
7249
+ //#endregion
7250
+ //#region src/v2/components/chat/message-feedback.tsx
7251
+ /**
7252
+ * Set by the message view while Learning is on for this chat. Assistant
7253
+ * messages without it show thumbs only when the app passes its own handlers
7254
+ * or thumb slots.
7255
+ */
7256
+ const MessageFeedbackScopeContext = (0, react.createContext)(null);
7257
+ const capturedCores = /* @__PURE__ */ new WeakSet();
7258
+ const rememberCapture = (copilotkit) => {
7259
+ if (capturedCores.has(copilotkit)) return;
7260
+ capturedCores.add(copilotkit);
7261
+ const unsubscribe = copilotkit.ɵsubscribeToLearningConfigured(() => {
7262
+ if (copilotkit.ɵlearningConfigured) return;
7263
+ capturedCores.delete(copilotkit);
7264
+ unsubscribe();
7265
+ });
7266
+ };
7267
+ /**
7268
+ * Whether the thumbs record feedback for Learning. True once a Trajectory of
7269
+ * this Core has started and as long as Learning stays configured, also while
7270
+ * capture reconnects or is stopped, so the buttons do not flicker. Ratings
7271
+ * given meanwhile still show but, like any Trajectory event, are not
7272
+ * sent. A capture that never starts (for example without a license) leaves it
7273
+ * false.
7274
+ */
7275
+ function useLearningFeedbackEnabled(copilotkit) {
7276
+ const configured = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => copilotkit.ɵsubscribeToLearningConfigured(onChange), [copilotkit]), () => copilotkit.ɵlearningConfigured, () => false);
7277
+ const capturing = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => copilotkit.subscribe({ onTrajectoryChanged: onChange }).unsubscribe, [copilotkit]), () => copilotkit.trajectoryId !== null, () => false);
7278
+ (0, react.useEffect)(() => {
7279
+ if (capturing) rememberCapture(copilotkit);
7280
+ }, [capturing, copilotkit]);
7281
+ return configured && (capturing || capturedCores.has(copilotkit));
7282
+ }
7283
+ const noSubscription = () => () => {};
7284
+ /** Feedback controls for `message`, or `null` when this chat collects none. */
7285
+ function useMessageFeedback(message) {
7286
+ const scope = (0, react.useContext)(MessageFeedbackScopeContext);
7287
+ const subscribe = (0, react.useCallback)((onChange) => scope ? subscribeToMessageFeedback(scope.agentId, scope.threadId, onChange) : noSubscription(), [scope]);
7288
+ const getSnapshot = () => scope ? getMessageFeedback(scope.agentId, scope.threadId, message.id) : void 0;
7289
+ const feedback = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
7290
+ if (!scope) return null;
7291
+ const save = (next) => setMessageFeedback({
7292
+ ...scope,
7293
+ message,
7294
+ feedback: next
7295
+ });
7296
+ return {
7297
+ feedback,
7298
+ rate: (rating) => {
7299
+ if (feedback?.rating !== rating) save({ rating });
7300
+ },
7301
+ clear: () => {
7302
+ if (feedback) save(null);
7303
+ },
7304
+ comment: (comment) => {
7305
+ if (feedback) save({
7306
+ rating: feedback.rating,
7307
+ comment
7308
+ });
7309
+ }
7310
+ };
7311
+ }
7312
+ /**
7313
+ * How long the pointer may be away from the rating and its comment before the
7314
+ * comment closes: enough to cross the gap between them, short enough that
7315
+ * passing over a rating does not leave it open.
7316
+ */
7317
+ const FEEDBACK_COMMENT_CLOSE_DELAY_MS = 150;
7318
+ /**
7319
+ * Drives the comment popover of a rated message.
7320
+ *
7321
+ * It opens when a rating is given and whenever the pointer or keyboard focus
7322
+ * reaches the chosen thumb, with the saved comment in the field. It closes by
7323
+ * itself shortly after the pointer leaves the thumb and the popover, unless
7324
+ * focus is in the field: then it stays until focus moves away, Enter, or
7325
+ * Escape. Closing saves a changed comment; Escape, a click on a thumb, and
7326
+ * removing the rating discard it.
7327
+ */
7328
+ function useFeedbackComment(controls) {
7329
+ const [open, setOpen] = (0, react.useState)(false);
7330
+ const [draft, setDraft] = (0, react.useState)("");
7331
+ const [focusRequest, setFocusRequest] = (0, react.useState)(0);
7332
+ const inputRef = (0, react.useRef)(null);
7333
+ const timer = (0, react.useRef)(void 0);
7334
+ const editing = (0, react.useRef)(false);
7335
+ const focusWaiting = (0, react.useRef)(false);
7336
+ const pointerOver = (0, react.useRef)(false);
7337
+ const returning = (0, react.useRef)(false);
7338
+ const latest = (0, react.useRef)({
7339
+ controls,
7340
+ draft,
7341
+ open
7342
+ });
7343
+ latest.current = {
7344
+ ...latest.current,
7345
+ controls,
7346
+ draft,
7347
+ open
7348
+ };
7349
+ const takeFocusRequest = (0, react.useCallback)(() => {
7350
+ const waiting = focusWaiting.current;
7351
+ focusWaiting.current = false;
7352
+ return waiting;
7353
+ }, []);
7354
+ const cancelClose = (0, react.useCallback)(() => {
7355
+ if (timer.current === void 0) return;
7356
+ clearTimeout(timer.current);
7357
+ timer.current = void 0;
7358
+ }, []);
7359
+ (0, react.useEffect)(() => cancelClose, [cancelClose]);
7360
+ const savedComment = (0, react.useCallback)(() => latest.current.controls?.feedback?.comment ?? "", []);
7361
+ (0, react.useEffect)(() => () => {
7362
+ const { open: wasOpen, controls: current, draft: text } = latest.current;
7363
+ if (wasOpen && current?.feedback && text.trim() !== savedComment()) current.comment(text);
7364
+ }, []);
7365
+ const close = (0, react.useCallback)(({ save = true } = {}) => {
7366
+ cancelClose();
7367
+ focusWaiting.current = false;
7368
+ pointerOver.current = false;
7369
+ if (!latest.current.open) return;
7370
+ const current = latest.current.controls;
7371
+ if (save && current?.feedback && latest.current.draft.trim() !== savedComment()) current.comment(latest.current.draft);
7372
+ editing.current = false;
7373
+ latest.current = {
7374
+ ...latest.current,
7375
+ open: false
7376
+ };
7377
+ setOpen(false);
7378
+ }, [cancelClose, savedComment]);
7379
+ const show = ({ comment, focus }) => {
7380
+ cancelClose();
7381
+ latest.current = {
7382
+ ...latest.current,
7383
+ open: true,
7384
+ draft: comment
7385
+ };
7386
+ setDraft(comment);
7387
+ if (focus) {
7388
+ focusWaiting.current = true;
7389
+ setFocusRequest((request) => request + 1);
7390
+ }
7391
+ setOpen(true);
7392
+ };
7393
+ return {
7394
+ open,
7395
+ draft,
7396
+ setDraft,
7397
+ focusRequest,
7398
+ takeFocusRequest,
7399
+ inputRef,
7400
+ show,
7401
+ enter: (via = "focus") => {
7402
+ cancelClose();
7403
+ if (via === "pointer") pointerOver.current = true;
7404
+ if (returning.current) return;
7405
+ if (!latest.current.open && latest.current.controls?.feedback) show({
7406
+ comment: savedComment(),
7407
+ focus: false
7408
+ });
7409
+ },
7410
+ leave: (via = "focus") => {
7411
+ if (via === "pointer") pointerOver.current = false;
7412
+ else if (pointerOver.current) return;
7413
+ cancelClose();
7414
+ timer.current = setTimeout(() => {
7415
+ timer.current = void 0;
7416
+ if (!editing.current) close();
7417
+ }, FEEDBACK_COMMENT_CLOSE_DELAY_MS);
7418
+ },
7419
+ setEditing: (value) => {
7420
+ editing.current = value;
7421
+ },
7422
+ close,
7423
+ returnFocus: (target) => {
7424
+ returning.current = true;
7425
+ try {
7426
+ target?.focus();
7427
+ } finally {
7428
+ returning.current = false;
7429
+ }
7430
+ }
7431
+ };
7432
+ }
7433
+
6502
7434
  //#endregion
6503
7435
  //#region src/v2/components/chat/CopilotChatAssistantMessage.tsx
6504
7436
  /**
7437
+ * In the `"turn"` tool-call display, text in a reply's working phase was
7438
+ * narration: it fades to muted once a call follows it, and folds away with the
7439
+ * rest of the work when the reply is done. The wrapper is there for every
7440
+ * message in that display, so text that turns into narration keeps its node
7441
+ * (and its place) rather than remounting.
7442
+ */
7443
+ function WorkText({ messageId, children }) {
7444
+ const context = (0, react.useContext)(ToolCallDisplayContext);
7445
+ if (context?.display !== "turn") return children;
7446
+ const open = context.workTextOpen.get(messageId);
7447
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
7448
+ open: open ?? true,
7449
+ className: "cpk:-mx-2",
7450
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7451
+ className: "cpk:px-2",
7452
+ "data-work-text": open === void 0 ? "reply" : "narration",
7453
+ children
7454
+ })
7455
+ });
7456
+ }
7457
+ /**
6505
7458
  * Binds the Inspector action to the active chat without making the full
6506
7459
  * assistant message subscribe to chat configuration changes.
6507
7460
  */
@@ -6516,31 +7469,216 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6516
7469
  })
6517
7470
  });
6518
7471
  }
6519
- function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, onThumbsUp, onThumbsDown, onReadAloud, onRegenerate, additionalToolbarItems, toolbarVisible = true, markdownRenderer, toolbar, copyButton, inspectorButton, thumbsUpButton, thumbsDownButton, readAloudButton, regenerateButton, toolCallsView, children, className, ...props }) {
7472
+ /** Whether focus came from the keyboard; a pointer click also focuses buttons. */
7473
+ function isFocusVisible(element) {
7474
+ try {
7475
+ return element.matches(":focus-visible");
7476
+ } catch {
7477
+ return false;
7478
+ }
7479
+ }
7480
+ const HIDDEN_TOO = {
7481
+ checkVisibilityCSS: true,
7482
+ visibilityProperty: true
7483
+ };
7484
+ const FOCUSABLE = [
7485
+ "a[href]",
7486
+ "button:not([disabled])",
7487
+ "input:not([disabled])",
7488
+ "select:not([disabled])",
7489
+ "textarea:not([disabled])",
7490
+ "[tabindex]"
7491
+ ].map((selector) => `${selector}:not([tabindex="-1"]):not([data-radix-focus-guard])`).join(", ");
7492
+ /** Approximately the element Tab reaches after `element`, outside `skip`. */
7493
+ function nextFocusable(element, skip) {
7494
+ const all = Array.from(element.ownerDocument.querySelectorAll(FOCUSABLE)).filter((candidate) => candidate === element || !skip?.contains(candidate) && !candidate.closest("[hidden], [inert]"));
7495
+ const index = all.indexOf(element);
7496
+ return index === -1 ? null : all[index + 1] ?? null;
7497
+ }
7498
+ /**
7499
+ * The comment popover of a rated message, pointing at the chosen thumb. Kept
7500
+ * apart so only it, not the whole message, follows chat configuration.
7501
+ *
7502
+ * The popover lives in a portal, so it keeps keyboard order by hand: Shift+Tab
7503
+ * from its start returns to the thumb, Tab from its end moves on to the
7504
+ * control after the thumb, and Enter or Escape inside it returns to the thumb.
7505
+ */
7506
+ function FeedbackCommentPopover({ feedbackComment, comment, savedComment, rating, anchor, messageId }) {
7507
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
7508
+ const contentRef = (0, react.useRef)(null);
7509
+ const focusInside = () => contentRef.current?.contains(document.activeElement) ?? false;
7510
+ const { focusRequest, takeFocusRequest, inputRef } = comment;
7511
+ const focusField = (0, react.useCallback)(() => {
7512
+ if (inputRef.current && takeFocusRequest()) inputRef.current.focus();
7513
+ }, [inputRef, takeFocusRequest]);
7514
+ (0, react.useEffect)(() => focusField(), [focusRequest, focusField]);
7515
+ /** Whether `target` is a thumb of this message, whose click decides itself. */
7516
+ const isOwnThumb = (target) => {
7517
+ return (target instanceof Element ? target.closest("[data-copilotkit-rating]") : null)?.closest("[data-message-id]")?.getAttribute("data-message-id") === messageId;
7518
+ };
7519
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Popover, {
7520
+ open: comment.open,
7521
+ onOpenChange: (open) => {
7522
+ if (!open) comment.close();
7523
+ },
7524
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverAnchor, { virtualRef: anchor }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverContent, {
7525
+ ref: contentRef,
7526
+ side: "bottom",
7527
+ collisionPadding: 8,
7528
+ hideWhenDetached: true,
7529
+ "aria-label": labels.assistantMessageFeedbackCommentPlaceholder,
7530
+ "data-copilotkit-ignore": true,
7531
+ onOpenAutoFocus: (event) => {
7532
+ event.preventDefault();
7533
+ focusField();
7534
+ },
7535
+ onCloseAutoFocus: (event) => event.preventDefault(),
7536
+ onInteractOutside: (event) => {
7537
+ if (isOwnThumb(event.target)) event.preventDefault();
7538
+ },
7539
+ onEscapeKeyDown: (event) => {
7540
+ const returnFocus = focusInside();
7541
+ if (returnFocus) event.stopPropagation();
7542
+ comment.close({ save: false });
7543
+ if (returnFocus) comment.returnFocus(anchor.current);
7544
+ },
7545
+ onKeyDown: (event) => {
7546
+ if (event.key !== "Tab") return;
7547
+ const focusables = Array.from(contentRef.current?.querySelectorAll(FOCUSABLE) ?? []);
7548
+ if (!(event.shiftKey ? document.activeElement === focusables[0] : document.activeElement === focusables[focusables.length - 1]) || !anchor.current?.isConnected) return;
7549
+ event.preventDefault();
7550
+ const target = event.shiftKey ? anchor.current : nextFocusable(anchor.current, contentRef.current) ?? anchor.current;
7551
+ comment.close();
7552
+ if (target === anchor.current) comment.returnFocus(target);
7553
+ else target.focus();
7554
+ },
7555
+ onPointerEnter: () => comment.enter("pointer"),
7556
+ onPointerLeave: () => comment.leave("pointer"),
7557
+ className: "cpk:z-[1300]",
7558
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderSlot(feedbackComment, CopilotChatAssistantMessage.FeedbackComment, {
7559
+ value: comment.draft,
7560
+ savedComment,
7561
+ inputRef: comment.inputRef,
7562
+ onChange: comment.setDraft,
7563
+ onSubmit: () => {
7564
+ comment.close();
7565
+ comment.returnFocus(anchor.current);
7566
+ },
7567
+ onEditingChange: comment.setEditing,
7568
+ onLeave: (next) => {
7569
+ if (next && (contentRef.current?.contains(next) || isOwnThumb(next))) return;
7570
+ comment.leave();
7571
+ }
7572
+ }) }, rating)
7573
+ })]
7574
+ });
7575
+ }
7576
+ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, onThumbsUp, onThumbsDown, onReadAloud, onRegenerate, additionalToolbarItems, toolbarVisible = true, markdownRenderer, toolbar, copyButton, inspectorButton, thumbsUpButton, thumbsDownButton, readAloudButton, regenerateButton, feedbackComment: feedbackCommentSlot, toolCallsView, children, className, ...props }) {
6520
7577
  useKatexStyles();
6521
7578
  const { isInspectorEnabled, openInspector } = useCopilotKitInspector();
6522
7579
  const shortcutsHidden = useInspectorShortcutsHidden();
6523
7580
  const showInspectorShortcut = isInspectorEnabled && !shortcutsHidden;
7581
+ const feedback = useMessageFeedback(message);
7582
+ const comment = useFeedbackComment(feedback);
7583
+ const rating = feedback?.feedback?.rating;
7584
+ const root = (0, react.useRef)(null);
7585
+ const commentAnchor = (0, react.useRef)(null);
7586
+ const commentRating = (0, react.useRef)(rating);
7587
+ (0, react.useLayoutEffect)(() => {
7588
+ if (comment.open && !commentAnchor.current?.isConnected && rating) commentAnchor.current = root.current?.querySelector(`[data-copilotkit-rating="${rating}"]`) ?? root.current?.querySelector("button[aria-pressed=\"true\"]") ?? null;
7589
+ });
7590
+ /**
7591
+ * Thumbs are toggles: a new rating is saved and opens its comment, and
7592
+ * clicking the chosen thumb again removes the rating. A keyboard click
7593
+ * (`detail` 0) moves focus into the comment, as there is no pointer.
7594
+ * Custom thumb slots may call `onClick` without an event.
7595
+ *
7596
+ * @returns Whether the app's handler should run: true for a new rating and
7597
+ * whenever this chat does not record ratings, false when one is removed.
7598
+ */
7599
+ const rate = (value, event) => {
7600
+ if (!feedback) return true;
7601
+ if (rating === value) {
7602
+ comment.close({ save: false });
7603
+ feedback.clear();
7604
+ return false;
7605
+ }
7606
+ feedback.rate(value);
7607
+ commentRating.current = value;
7608
+ commentAnchor.current = event?.currentTarget ?? null;
7609
+ comment.show({
7610
+ comment: "",
7611
+ focus: event?.detail === 0
7612
+ });
7613
+ return true;
7614
+ };
7615
+ /** Props that let the chosen thumb open and point at its comment. */
7616
+ const thumbFeedbackProps = (value) => {
7617
+ if (!feedback) return {};
7618
+ const thumb = { "data-copilotkit-rating": value };
7619
+ if (rating !== value) return {
7620
+ ...thumb,
7621
+ "aria-pressed": false
7622
+ };
7623
+ const enter = (target, via) => {
7624
+ if (!comment.open) commentRating.current = value;
7625
+ commentAnchor.current = target;
7626
+ comment.enter(via);
7627
+ };
7628
+ return {
7629
+ ...thumb,
7630
+ "aria-pressed": true,
7631
+ "aria-haspopup": "dialog",
7632
+ "aria-expanded": comment.open,
7633
+ onPointerEnter: (event) => {
7634
+ if (event.pointerType !== "touch") enter(event.currentTarget, "pointer");
7635
+ },
7636
+ onPointerLeave: (event) => {
7637
+ if (event.pointerType !== "touch") comment.leave("pointer");
7638
+ },
7639
+ onFocus: (event) => {
7640
+ if (isFocusVisible(event.currentTarget)) enter(event.currentTarget, "focus");
7641
+ },
7642
+ onBlur: () => comment.leave(),
7643
+ onKeyDown: (event) => {
7644
+ const input = comment.inputRef.current;
7645
+ if (event.key === "Tab" && !event.shiftKey && comment.open && input && input.checkVisibility?.(HIDDEN_TOO) !== false) {
7646
+ event.preventDefault();
7647
+ input.focus();
7648
+ }
7649
+ }
7650
+ };
7651
+ };
6524
7652
  const boundMarkdownRenderer = renderSlot(markdownRenderer, CopilotChatAssistantMessage.MarkdownRenderer, { content: message.content || "" });
6525
7653
  const boundCopyButton = renderSlot(copyButton, CopilotChatAssistantMessage.CopyButton, { onClick: async () => {
6526
7654
  if (message.content) return await (0, _copilotkit_shared.copyToClipboard)(message.content);
6527
7655
  return false;
6528
7656
  } });
6529
- const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp ? () => onThumbsUp(message) : void 0 });
7657
+ const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, {
7658
+ onClick: onThumbsUp || feedback ? (event) => {
7659
+ if (rate("up", event)) onThumbsUp?.(message);
7660
+ } : void 0,
7661
+ ...thumbFeedbackProps("up")
7662
+ });
6530
7663
  const boundInspectorButton = showInspectorShortcut ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundInspectorButton, {
6531
7664
  inspectorButton,
6532
7665
  messageId: message.id,
6533
7666
  openInspector
6534
7667
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
6535
- const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown ? () => onThumbsDown(message) : void 0 });
7668
+ const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, {
7669
+ onClick: onThumbsDown || feedback ? (event) => {
7670
+ if (rate("down", event)) onThumbsDown?.(message);
7671
+ } : void 0,
7672
+ ...thumbFeedbackProps("down")
7673
+ });
6536
7674
  const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud ? () => onReadAloud(message) : void 0 });
6537
7675
  const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate ? () => onRegenerate(message) : void 0 });
6538
7676
  const boundToolbar = renderSlot(toolbar, CopilotChatAssistantMessage.Toolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6539
7677
  className: "cpk:flex cpk:w-full cpk:items-center cpk:gap-1",
6540
7678
  children: [
6541
7679
  boundCopyButton,
6542
- (onThumbsUp || thumbsUpButton) && boundThumbsUpButton,
6543
- (onThumbsDown || thumbsDownButton) && boundThumbsDownButton,
7680
+ (onThumbsUp || thumbsUpButton || feedback) && boundThumbsUpButton,
7681
+ (onThumbsDown || thumbsDownButton || feedback) && boundThumbsDownButton,
6544
7682
  (onReadAloud || readAloudButton) && boundReadAloudButton,
6545
7683
  (onRegenerate || regenerateButton) && boundRegenerateButton,
6546
7684
  additionalToolbarItems,
@@ -6554,11 +7692,31 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6554
7692
  const hasContent = !!(message.content && message.content.trim().length > 0);
6555
7693
  const isLatestAssistantMessage = message.role === "assistant" && (isLatest ?? messages?.[messages.length - 1]?.id === message.id);
6556
7694
  const shouldShowToolbar = toolbarVisible && (hasContent || showInspectorShortcut) && !(isRunning && isLatestAssistantMessage);
6557
- if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7695
+ const { open: commentOpen, close: closeComment } = comment;
7696
+ (0, react.useEffect)(() => {
7697
+ if (!commentOpen) return;
7698
+ if (rating !== commentRating.current) closeComment({ save: false });
7699
+ else if (!shouldShowToolbar) closeComment();
7700
+ }, [
7701
+ commentOpen,
7702
+ shouldShowToolbar,
7703
+ rating,
7704
+ closeComment
7705
+ ]);
7706
+ const feedbackComment = feedback?.feedback && shouldShowToolbar && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FeedbackCommentPopover, {
7707
+ feedbackComment: feedbackCommentSlot,
7708
+ comment,
7709
+ savedComment: feedback.feedback.comment ?? "",
7710
+ rating: feedback.feedback.rating,
7711
+ anchor: commentAnchor,
7712
+ messageId: message.id
7713
+ });
7714
+ if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7715
+ ref: root,
6558
7716
  "data-copilotkit": true,
6559
7717
  "data-message-id": message.id,
6560
7718
  style: { display: "contents" },
6561
- children: children({
7719
+ children: [children({
6562
7720
  markdownRenderer: boundMarkdownRenderer,
6563
7721
  toolbar: boundToolbar,
6564
7722
  toolCallsView: boundToolCallsView,
@@ -6577,21 +7735,26 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6577
7735
  onRegenerate,
6578
7736
  additionalToolbarItems,
6579
7737
  toolbarVisible: shouldShowToolbar
6580
- })
7738
+ }), feedbackComment]
6581
7739
  });
6582
7740
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7741
+ ref: root,
6583
7742
  "data-copilotkit": true,
6584
7743
  "data-testid": "copilot-assistant-message",
6585
7744
  className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitAssistantMessage", className),
6586
7745
  ...props,
6587
7746
  "data-message-id": message.id,
6588
7747
  children: [
6589
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6590
- className: "cpk:prose cpk:max-w-full cpk:break-words cpk:dark:prose-invert",
6591
- children: boundMarkdownRenderer
7748
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkText, {
7749
+ messageId: message.id,
7750
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7751
+ className: "cpk:prose cpk:max-w-full cpk:break-words cpk:dark:prose-invert",
7752
+ children: boundMarkdownRenderer
7753
+ })
6592
7754
  }),
6593
7755
  boundToolCallsView,
6594
- shouldShowToolbar && boundToolbar
7756
+ shouldShowToolbar && boundToolbar,
7757
+ feedbackComment
6595
7758
  ]
6596
7759
  });
6597
7760
  }
@@ -6607,20 +7770,27 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6607
7770
  ...props
6608
7771
  });
6609
7772
  const ToolbarButton = _CopilotChatAssistantMessage.ToolbarButton = ({ title, tooltip, tooltipClassName, children, ...props }) => {
6610
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
6611
- asChild: true,
6612
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6613
- type: "button",
6614
- variant: "assistantMessageToolbarButton",
6615
- "aria-label": title,
6616
- ...props,
6617
- children
6618
- })
6619
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
6620
- side: "bottom",
6621
- className: tooltipClassName,
6622
- children: tooltip ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
6623
- })] });
7773
+ const [tooltipOpen, setTooltipOpen] = (0, react.useState)(false);
7774
+ const popupOpen = props["aria-expanded"] === true;
7775
+ if (popupOpen && tooltipOpen) setTooltipOpen(false);
7776
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, {
7777
+ open: tooltipOpen && !popupOpen,
7778
+ onOpenChange: (open) => setTooltipOpen(open && !popupOpen),
7779
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
7780
+ asChild: true,
7781
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7782
+ type: "button",
7783
+ variant: "assistantMessageToolbarButton",
7784
+ "aria-label": title,
7785
+ ...props,
7786
+ children
7787
+ })
7788
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
7789
+ side: "bottom",
7790
+ className: tooltipClassName,
7791
+ children: tooltip ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
7792
+ })]
7793
+ });
6624
7794
  };
6625
7795
  _CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
6626
7796
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
@@ -6659,7 +7829,10 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6659
7829
  "data-testid": "copilot-thumbs-up-button",
6660
7830
  title: title || labels.assistantMessageToolbarThumbsUpLabel,
6661
7831
  ...props,
6662
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, { className: "cpk:size-[18px]" })
7832
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, {
7833
+ className: "cpk:size-[18px]",
7834
+ fill: props["aria-pressed"] === true ? "currentColor" : "none"
7835
+ })
6663
7836
  });
6664
7837
  };
6665
7838
  _CopilotChatAssistantMessage.ThumbsDownButton = ({ title, ...props }) => {
@@ -6668,7 +7841,45 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6668
7841
  "data-testid": "copilot-thumbs-down-button",
6669
7842
  title: title || labels.assistantMessageToolbarThumbsDownLabel,
6670
7843
  ...props,
6671
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, { className: "cpk:size-[18px]" })
7844
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, {
7845
+ className: "cpk:size-[18px]",
7846
+ fill: props["aria-pressed"] === true ? "currentColor" : "none"
7847
+ })
7848
+ });
7849
+ };
7850
+ _CopilotChatAssistantMessage.FeedbackComment = ({ value, savedComment, inputRef, onChange, onSubmit, onEditingChange, onLeave, className }) => {
7851
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
7852
+ const changed = value.trim() !== savedComment;
7853
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
7854
+ "data-testid": "copilot-feedback-comment",
7855
+ className: (0, tailwind_merge.twMerge)("cpk:flex cpk:w-80 cpk:max-w-[calc(100vw-1rem)] cpk:items-center cpk:gap-1 cpk:rounded-full cpk:border cpk:border-border cpk:bg-background cpk:py-0.5 cpk:pl-3 cpk:pr-0.5 cpk:text-sm cpk:shadow-md", className),
7856
+ onSubmit: (event) => {
7857
+ event.preventDefault();
7858
+ onSubmit();
7859
+ },
7860
+ onFocus: () => onEditingChange?.(true),
7861
+ onBlur: (event) => {
7862
+ const next = event.relatedTarget instanceof Element ? event.relatedTarget : null;
7863
+ if (event.currentTarget.contains(next)) return;
7864
+ if (typeof document !== "undefined" && !document.hasFocus()) return;
7865
+ onEditingChange?.(false);
7866
+ onLeave?.(next);
7867
+ },
7868
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7869
+ ref: inputRef,
7870
+ value,
7871
+ maxLength: MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH,
7872
+ onChange: (event) => onChange(event.target.value),
7873
+ placeholder: labels.assistantMessageFeedbackCommentPlaceholder,
7874
+ "aria-label": labels.assistantMessageFeedbackCommentPlaceholder,
7875
+ className: "cpk:min-w-0 cpk:flex-1 cpk:bg-transparent cpk:py-1 cpk:text-foreground cpk:outline-none cpk:placeholder:text-muted-foreground"
7876
+ }), changed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7877
+ type: "submit",
7878
+ variant: "assistantMessageToolbarButton",
7879
+ "aria-label": labels.assistantMessageFeedbackCommentSubmitLabel,
7880
+ title: labels.assistantMessageFeedbackCommentSubmitLabel,
7881
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[16px]" })
7882
+ })]
6672
7883
  });
6673
7884
  };
6674
7885
  _CopilotChatAssistantMessage.ReadAloudButton = ({ title, ...props }) => {
@@ -6696,6 +7907,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
6696
7907
  CopilotChatAssistantMessage.InspectorButton.displayName = "CopilotChatAssistantMessage.InspectorButton";
6697
7908
  CopilotChatAssistantMessage.ThumbsUpButton.displayName = "CopilotChatAssistantMessage.ThumbsUpButton";
6698
7909
  CopilotChatAssistantMessage.ThumbsDownButton.displayName = "CopilotChatAssistantMessage.ThumbsDownButton";
7910
+ CopilotChatAssistantMessage.FeedbackComment.displayName = "CopilotChatAssistantMessage.FeedbackComment";
6699
7911
  CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantMessage.ReadAloudButton";
6700
7912
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
6701
7913
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
@@ -7001,6 +8213,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7001
8213
  });
7002
8214
  };
7003
8215
  _CopilotChatUserMessage.BranchNavigation = ({ className, currentBranch = 0, numberOfBranches = 1, onSwitchToBranch, message, ...props }) => {
8216
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
7004
8217
  if (!numberOfBranches || numberOfBranches <= 1 || !onSwitchToBranch) return null;
7005
8218
  const canGoPrev = currentBranch > 0;
7006
8219
  const canGoNext = currentBranch < numberOfBranches - 1;
@@ -7019,6 +8232,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7019
8232
  }),
7020
8233
  disabled: !canGoPrev,
7021
8234
  className: "cpk:h-6 cpk:w-6 cpk:p-0",
8235
+ "aria-label": labels.userMessageToolbarPreviousBranchLabel,
7022
8236
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "cpk:size-[20px]" })
7023
8237
  }),
7024
8238
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -7039,6 +8253,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7039
8253
  }),
7040
8254
  disabled: !canGoNext,
7041
8255
  className: "cpk:h-6 cpk:w-6 cpk:p-0",
8256
+ "aria-label": labels.userMessageToolbarNextBranchLabel,
7042
8257
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "cpk:size-[20px]" })
7043
8258
  })
7044
8259
  ]
@@ -7059,7 +8274,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7059
8274
  /**
7060
8275
  * Formats an elapsed duration (in seconds) to a human-readable string.
7061
8276
  */
7062
- function formatDuration(seconds) {
8277
+ function formatDuration$1(seconds) {
7063
8278
  if (seconds < 1) return "a few seconds";
7064
8279
  if (seconds < 60) return `${Math.round(seconds)} seconds`;
7065
8280
  const mins = Math.floor(seconds / 60);
@@ -7097,7 +8312,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7097
8312
  userToggledRef.current = true;
7098
8313
  setIsOpen((prev) => !prev);
7099
8314
  } : void 0;
7100
- const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration(elapsed)}`;
8315
+ const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration$1(elapsed)}`;
7101
8316
  const boundHeader = renderSlot(header, CopilotChatReasoningMessage.Header, {
7102
8317
  isOpen,
7103
8318
  label,
@@ -7319,6 +8534,71 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7319
8534
  */
7320
8535
  const ScrollPinnedContext = react.default.createContext(false);
7321
8536
 
8537
+ //#endregion
8538
+ //#region src/v2/components/chat/CopilotChatWorkSummary.tsx
8539
+ /** "45s", "1m 5s", "2m". Used for the live timer and the final duration. */
8540
+ function formatDuration(ms) {
8541
+ const seconds = Math.max(0, Math.floor(ms / 1e3));
8542
+ if (seconds < 60) return `${seconds}s`;
8543
+ const minutes = Math.floor(seconds / 60);
8544
+ const rest = seconds % 60;
8545
+ return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
8546
+ }
8547
+ /** Re-renders every second while `active`, returning the current time. */
8548
+ function useNow(active) {
8549
+ const [now, setNow] = (0, react.useState)(() => Date.now());
8550
+ (0, react.useEffect)(() => {
8551
+ if (!active) return;
8552
+ setNow(Date.now());
8553
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
8554
+ return () => clearInterval(timer);
8555
+ }, [active]);
8556
+ return now;
8557
+ }
8558
+ /**
8559
+ * The line at the top of a reply in the `"turn"` tool-call display. While the
8560
+ * agent works it reads "Working 4s"; once the reply is done it reads "Worked
8561
+ * for 9s · 7 tools" and toggles the work it folded away.
8562
+ */
8563
+ function CopilotChatWorkSummary({ isWorking, isOpen, calls, elapsedMs, workingSince, className, ...props }) {
8564
+ const now = useNow(isWorking);
8565
+ const failed = calls.filter((call) => call.toolMessage?.error).length;
8566
+ const liveMs = elapsedMs !== void 0 && workingSince !== void 0 ? elapsedMs + Math.max(0, now - workingSince) : void 0;
8567
+ const duration = elapsedMs !== void 0 ? formatDuration(Math.max(1e3, elapsedMs)) : void 0;
8568
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8569
+ type: "button",
8570
+ "data-copilotkit": true,
8571
+ "data-testid": "copilot-work-summary",
8572
+ "data-state": isWorking ? "working" : "done",
8573
+ "aria-expanded": isOpen,
8574
+ className: (0, tailwind_merge.twMerge)("cpk:m-0 cpk:inline-flex cpk:max-w-full cpk:cursor-pointer cpk:items-center cpk:gap-1.5 cpk:rounded-md cpk:border-none cpk:bg-transparent cpk:p-0 cpk:py-0.5 cpk:text-sm cpk:text-muted-foreground cpk:transition-colors cpk:hover:text-foreground cpk:focus-visible:outline-2 cpk:focus-visible:outline-ring", className),
8575
+ ...props,
8576
+ children: [isWorking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8577
+ className: "cpk:font-medium cpk-shimmer",
8578
+ children: "Working"
8579
+ }), liveMs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8580
+ className: "cpk:text-xs cpk:tabular-nums cpk:opacity-70",
8581
+ children: formatDuration(liveMs)
8582
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8583
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8584
+ className: "cpk:font-medium",
8585
+ children: duration ? `Worked for ${duration}` : `Used ${pluralize(calls.length, "tool")}`
8586
+ }),
8587
+ duration && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8588
+ className: "cpk:text-xs cpk:opacity-70",
8589
+ children: pluralize(calls.length, "tool")
8590
+ }),
8591
+ failed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8592
+ className: "cpk:text-destructive",
8593
+ children: [failed, " failed"]
8594
+ })
8595
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
8596
+ "aria-hidden": "true",
8597
+ className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", isOpen && "cpk:rotate-90")
8598
+ })]
8599
+ });
8600
+ }
8601
+
7322
8602
  //#endregion
7323
8603
  //#region src/v2/components/intelligence-indicator/IntelligenceIndicatorView.tsx
7324
8604
  /**
@@ -7805,7 +9085,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7805
9085
  }
7806
9086
  const GROUP_ROW_KEY_PREFIX = "copilotkit-group:";
7807
9087
  const VIRTUALIZE_THRESHOLD = 50;
7808
- function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, isRunning = false, transformMessages, groupMessages, children, className, ...props }) {
9088
+ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, workSummary, isRunning = false, transformMessages, groupMessages, messageFeedback = true, toolCallDisplay = "grouped", children, className, ...props }) {
7809
9089
  const isPinnedToBottom = (0, react.useContext)(ScrollPinnedContext);
7810
9090
  const renderCustomMessage = useRenderCustomMessages();
7811
9091
  const { renderActivityMessage } = useRenderActivityMessage();
@@ -7846,6 +9126,100 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7846
9126
  transformedMessages
7847
9127
  ]);
7848
9128
  const latestRenderedId = renderedMessages[renderedMessages.length - 1]?.id;
9129
+ const rendererNames = useToolCallRendererNames();
9130
+ const toolCallLayouts = (0, react.useMemo)(() => toolCallDisplay === "separate" ? /* @__PURE__ */ new Map() : getToolCallLayouts(renderedMessages, messages, rendererNames), [
9131
+ toolCallDisplay,
9132
+ renderedMessages,
9133
+ messages,
9134
+ rendererNames
9135
+ ]);
9136
+ const turnWork = (0, react.useMemo)(() => toolCallDisplay === "turn" ? getTurnWork(renderedMessages, toolCallLayouts) : void 0, [
9137
+ toolCallDisplay,
9138
+ renderedMessages,
9139
+ toolCallLayouts
9140
+ ]);
9141
+ const turnByFirstMessage = (0, react.useMemo)(() => {
9142
+ const byFirst = /* @__PURE__ */ new Map();
9143
+ for (const turn of turnWork?.values() ?? []) byFirst.set(turn.firstMessageId, turn);
9144
+ return byFirst;
9145
+ }, [turnWork]);
9146
+ let latestTurnKey = "start";
9147
+ for (let i = renderedMessages.length - 1; i >= 0; i--) if (renderedMessages[i].role === "user") {
9148
+ latestTurnKey = renderedMessages[i].id;
9149
+ break;
9150
+ }
9151
+ const isWorkingOn = (turnKey) => isRunning && turnKey === latestTurnKey;
9152
+ const [toggledTurns, setToggledTurns] = (0, react.useState)(() => /* @__PURE__ */ new Map());
9153
+ const isWorkOpen = (turnKey) => toggledTurns.get(turnKey) ?? isWorkingOn(turnKey);
9154
+ const workTimesRef = (0, react.useRef)(/* @__PURE__ */ new Map());
9155
+ if (toolCallDisplay === "turn") {
9156
+ const now = Date.now();
9157
+ const workTimes = workTimesRef.current;
9158
+ if (isRunning) {
9159
+ const times = workTimes.get(latestTurnKey);
9160
+ if (times) times.workingSince ?? (times.workingSince = now);
9161
+ else if (renderedMessages[renderedMessages.length - 1]?.role === "user") workTimes.set(latestTurnKey, {
9162
+ elapsedMs: 0,
9163
+ workingSince: now
9164
+ });
9165
+ } else for (const times of workTimes.values()) {
9166
+ if (times.workingSince === void 0) continue;
9167
+ times.elapsedMs += now - times.workingSince;
9168
+ times.workingSince = void 0;
9169
+ }
9170
+ }
9171
+ const { workOpen, workTextOpen } = (0, react.useMemo)(() => {
9172
+ const open = /* @__PURE__ */ new Map();
9173
+ const textOpen = /* @__PURE__ */ new Map();
9174
+ for (const turn of turnWork?.values() ?? []) {
9175
+ const turnOpen = toggledTurns.get(turn.key) ?? (isRunning && turn.key === latestTurnKey);
9176
+ for (const id of turn.workMessageIds) {
9177
+ open.set(id, turnOpen);
9178
+ if (id !== turn.replyTextMessageId) textOpen.set(id, turnOpen);
9179
+ }
9180
+ }
9181
+ return {
9182
+ workOpen: open,
9183
+ workTextOpen: textOpen
9184
+ };
9185
+ }, [
9186
+ turnWork,
9187
+ toggledTurns,
9188
+ isRunning,
9189
+ latestTurnKey
9190
+ ]);
9191
+ const toolCallDisplayContext = (0, react.useMemo)(() => ({
9192
+ display: toolCallDisplay,
9193
+ isRunning,
9194
+ layouts: toolCallLayouts,
9195
+ workOpen,
9196
+ workTextOpen
9197
+ }), [
9198
+ toolCallDisplay,
9199
+ isRunning,
9200
+ toolCallLayouts,
9201
+ workOpen,
9202
+ workTextOpen
9203
+ ]);
9204
+ const renderWorkSummary = (turnKey, calls) => {
9205
+ const working = isWorkingOn(turnKey);
9206
+ const open = isWorkOpen(turnKey);
9207
+ const times = workTimesRef.current.get(turnKey);
9208
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
9209
+ open: working || calls.length > 0,
9210
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9211
+ className: "cpk:pt-1 cpk:pb-2",
9212
+ children: renderSlot(workSummary, CopilotChatWorkSummary, {
9213
+ isWorking: working,
9214
+ isOpen: open,
9215
+ calls,
9216
+ elapsedMs: times?.elapsedMs,
9217
+ workingSince: times?.workingSince,
9218
+ onClick: () => setToggledTurns((toggled) => new Map(toggled).set(turnKey, !open))
9219
+ })
9220
+ })
9221
+ }, `work-summary-${turnKey}`);
9222
+ };
7849
9223
  const transformDuplicateId = (0, react.useMemo)(() => {
7850
9224
  if (process.env.NODE_ENV === "production" || !transformMessages && !groupMessages) return;
7851
9225
  const seen = /* @__PURE__ */ new Set();
@@ -7883,8 +9257,6 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7883
9257
  (0, _copilotkit_shared.commitRowKeyStore)(rowKeyStore, renderedMessages);
7884
9258
  }, [rowKeyStore, renderedMessages]);
7885
9259
  const stableGroupKey = (key) => rowRenderKeys.get(key) ?? key;
7886
- const rowKey = (row) => row.type === "group" ? `${GROUP_ROW_KEY_PREFIX}${stableGroupKey(row.key)}` : rowRenderKeys.get(row.message.id) ?? row.message.id;
7887
- const rowKeys = (0, react.useMemo)(() => rows.map(rowKey), [rows, rowRenderKeys]);
7888
9260
  const groupStateRef = (0, react.useRef)(/* @__PURE__ */ new Map());
7889
9261
  const groupSettersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
7890
9262
  const groupStateThreadRef = (0, react.useRef)(config?.threadId);
@@ -7913,6 +9285,18 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7913
9285
  const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
7914
9286
  const agentId = config?.agentId;
7915
9287
  const threadId = config?.threadId;
9288
+ const learningFeedbackEnabled = useLearningFeedbackEnabled(copilotkit);
9289
+ const feedbackScope = (0, react.useMemo)(() => messageFeedback && learningFeedbackEnabled && agentId !== void 0 && threadId !== void 0 ? {
9290
+ copilotkit,
9291
+ agentId,
9292
+ threadId
9293
+ } : null, [
9294
+ messageFeedback,
9295
+ learningFeedbackEnabled,
9296
+ copilotkit,
9297
+ agentId,
9298
+ threadId
9299
+ ]);
7916
9300
  const assistantSlotPropsWithFeedback = (0, react.useMemo)(() => {
7917
9301
  const onThumbsUp = assistantSlotProps?.onThumbsUp;
7918
9302
  const onThumbsDown = assistantSlotProps?.onThumbsDown;
@@ -7958,8 +9342,6 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7958
9342
  total: 0,
7959
9343
  sizes: /* @__PURE__ */ new Map()
7960
9344
  });
7961
- const rowKeysRef = react.default.useRef(rowKeys);
7962
- rowKeysRef.current = rowKeys;
7963
9345
  const firstMessageId = deduplicatedMessages[0]?.id;
7964
9346
  const measuredThreadRef = react.default.useRef(firstMessageId);
7965
9347
  if (measuredThreadRef.current !== firstMessageId) {
@@ -7969,16 +9351,6 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7969
9351
  sizes: /* @__PURE__ */ new Map()
7970
9352
  };
7971
9353
  }
7972
- const measuredKeysRef = react.default.useRef(rowKeys);
7973
- if (measuredKeysRef.current !== rowKeys) {
7974
- measuredKeysRef.current = rowKeys;
7975
- const live = new Set(rowKeys);
7976
- const measured = measuredRef.current;
7977
- for (const [key, height] of measured.sizes) if (!live.has(key)) {
7978
- measured.sizes.delete(key);
7979
- measured.total -= height;
7980
- }
7981
- }
7982
9354
  const estimateRowSize = react.default.useCallback(() => {
7983
9355
  const { total, sizes } = measuredRef.current;
7984
9356
  return sizes.size > 0 ? Math.max(1, Math.round(total / sizes.size)) : 100;
@@ -7986,15 +9358,13 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
7986
9358
  const measureRowElement = react.default.useCallback((el) => {
7987
9359
  const height = el?.getBoundingClientRect().height ?? 0;
7988
9360
  const index = Number(el?.dataset?.index);
7989
- const key = Number.isInteger(index) ? rowKeysRef.current[index] : void 0;
7990
- if (height > 0 && key !== void 0) {
9361
+ if (height > 0 && Number.isInteger(index)) {
7991
9362
  const { total, sizes } = measuredRef.current;
7992
- measuredRef.current.total = total - (sizes.get(key) ?? 0) + height;
7993
- sizes.set(key, height);
9363
+ measuredRef.current.total = total - (sizes.get(index) ?? 0) + height;
9364
+ sizes.set(index, height);
7994
9365
  }
7995
- return height;
9366
+ return height > 0 ? height : measuredRef.current.sizes.get(index) ?? 100;
7996
9367
  }, []);
7997
- const getRowKey = react.default.useCallback((index) => rowKeys[index] ?? index, [rowKeys]);
7998
9368
  const isPinnedToBottomRef = react.default.useRef(isPinnedToBottom);
7999
9369
  const shouldAdjustScrollOnResize = react.default.useCallback((item, _delta, instance) => {
8000
9370
  if (isPinnedToBottomRef.current) return false;
@@ -8003,9 +9373,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8003
9373
  }, []);
8004
9374
  const virtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
8005
9375
  count: shouldVirtualize ? rows.length : 0,
8006
- getScrollElement: () => scrollElement,
9376
+ getScrollElement: () => shouldVirtualize ? scrollElement : null,
8007
9377
  estimateSize: estimateRowSize,
8008
- getItemKey: getRowKey,
8009
9378
  overscan: 5,
8010
9379
  measureElement: measureRowElement,
8011
9380
  initialRect: {
@@ -8015,8 +9384,11 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8015
9384
  });
8016
9385
  isPinnedToBottomRef.current = isPinnedToBottom;
8017
9386
  virtualizer.shouldAdjustScrollPositionOnItemSizeChange = shouldAdjustScrollOnResize;
9387
+ const lastEndAlignedFirstMessageRef = react.default.useRef(void 0);
8018
9388
  (0, react.useLayoutEffect)(() => {
8019
9389
  if (!shouldVirtualize || !rows.length) return;
9390
+ if (lastEndAlignedFirstMessageRef.current === firstMessageId) return;
9391
+ lastEndAlignedFirstMessageRef.current = firstMessageId;
8020
9392
  virtualizer.scrollToIndex(rows.length - 1, { align: "end" });
8021
9393
  }, [shouldVirtualize, firstMessageId]);
8022
9394
  const intelligenceTurnAnchors = (0, react.useMemo)(() => getIntelligenceTurnAnchors(deduplicatedMessages, renderedMessages), [deduplicatedMessages, renderedMessages]);
@@ -8024,6 +9396,8 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8024
9396
  const elements = [];
8025
9397
  const stateSnapshot = renderCustomMessage ? getStateSnapshotForMessage(message.id) : void 0;
8026
9398
  const rowKey = rowRenderKeys.get(message.id) ?? message.id;
9399
+ const turn = turnByFirstMessage.get(message.id);
9400
+ if (turn) elements.push(renderWorkSummary(turn.key, turn.calls));
8027
9401
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
8028
9402
  message,
8029
9403
  position: "before",
@@ -8047,14 +9421,20 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8047
9421
  message,
8048
9422
  renderActivityMessage
8049
9423
  }, rowKey));
8050
- else if (message.role === "reasoning") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
8051
- message,
8052
- messages,
8053
- isRunning,
8054
- isLatest: message.id === latestRenderedId,
8055
- ReasoningMessageComponent: ReasoningComponent,
8056
- slotProps: reasoningSlotProps
8057
- }, rowKey));
9424
+ else if (message.role === "reasoning") {
9425
+ const reasoning = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
9426
+ message,
9427
+ messages,
9428
+ isRunning,
9429
+ isLatest: message.id === latestRenderedId,
9430
+ ReasoningMessageComponent: ReasoningComponent,
9431
+ slotProps: reasoningSlotProps
9432
+ }, rowKey);
9433
+ elements.push(toolCallDisplay === "turn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
9434
+ open: workOpen.get(message.id) ?? true,
9435
+ children: reasoning
9436
+ }, rowKey) : reasoning);
9437
+ }
8058
9438
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
8059
9439
  message,
8060
9440
  position: "after",
@@ -8069,6 +9449,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8069
9449
  }, `intelligence-${intelligenceTurnId}`));
8070
9450
  return elements.filter(Boolean);
8071
9451
  };
9452
+ const rowKey = (row) => row.type === "group" ? `${GROUP_ROW_KEY_PREFIX}${stableGroupKey(row.key)}` : rowRenderKeys.get(row.message.id) ?? row.message.id;
8072
9453
  const renderRow = (row) => {
8073
9454
  if (row.type === "message") return renderMessageBlock(row.message);
8074
9455
  const Wrapper = row.wrapper;
@@ -8081,52 +9462,157 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8081
9462
  children: row.messages.flatMap(renderMessageBlock)
8082
9463
  }, rowKey(row))];
8083
9464
  };
9465
+ const rowKeysRef = (0, react.useRef)([]);
9466
+ rowKeysRef.current = rows.map(rowKey);
9467
+ const virtualModeRef = (0, react.useRef)(shouldVirtualize);
9468
+ virtualModeRef.current = shouldVirtualize;
9469
+ (0, react.useEffect)(() => {
9470
+ const element = scrollElementFromCtx;
9471
+ if (!element) return;
9472
+ let anchor = null;
9473
+ let suspended = false;
9474
+ let frame = 0;
9475
+ const visibleRows = () => Array.from(element.querySelectorAll("[data-scroll-anchor]"));
9476
+ const remember = () => {
9477
+ if (suspended || element.clientHeight === 0 || !virtualModeRef.current) return;
9478
+ if (isPinnedToBottomRef.current) {
9479
+ anchor = null;
9480
+ return;
9481
+ }
9482
+ const viewport = element.getBoundingClientRect();
9483
+ const row = visibleRows().find((row) => {
9484
+ const bounds = row.getBoundingClientRect();
9485
+ return bounds.bottom > viewport.top && bounds.top < viewport.bottom;
9486
+ });
9487
+ if (row) anchor = {
9488
+ key: row.dataset.scrollAnchor,
9489
+ offset: row.getBoundingClientRect().top - viewport.top
9490
+ };
9491
+ };
9492
+ const observer = new ResizeObserver(() => {
9493
+ if (element.clientHeight === 0) {
9494
+ suspended = true;
9495
+ cancelAnimationFrame(frame);
9496
+ return;
9497
+ }
9498
+ if (!suspended || !anchor) {
9499
+ suspended = false;
9500
+ remember();
9501
+ return;
9502
+ }
9503
+ let attempts = 0;
9504
+ let stableFrames = 0;
9505
+ const restore = () => {
9506
+ if (!anchor || element.clientHeight === 0) return;
9507
+ const index = rowKeysRef.current.indexOf(anchor.key);
9508
+ if (index < 0) {
9509
+ anchor = null;
9510
+ suspended = false;
9511
+ return;
9512
+ }
9513
+ const row = visibleRows().find((row) => row.dataset.scrollAnchor === anchor.key);
9514
+ if (row) {
9515
+ const delta = row.getBoundingClientRect().top - element.getBoundingClientRect().top - anchor.offset;
9516
+ element.scrollTop += delta;
9517
+ stableFrames = Math.abs(delta) < 1 ? stableFrames + 1 : 0;
9518
+ } else {
9519
+ const offset = virtualizer.getOffsetForIndex(index, "start")?.[0];
9520
+ if (offset !== void 0) virtualizer.scrollToOffset(offset - anchor.offset);
9521
+ stableFrames = 0;
9522
+ }
9523
+ if (++attempts < 60 && stableFrames < 3) frame = requestAnimationFrame(restore);
9524
+ else {
9525
+ suspended = false;
9526
+ remember();
9527
+ }
9528
+ };
9529
+ frame = requestAnimationFrame(restore);
9530
+ });
9531
+ const interrupt = () => {
9532
+ cancelAnimationFrame(frame);
9533
+ anchor = null;
9534
+ suspended = false;
9535
+ };
9536
+ element.addEventListener("scroll", remember, { passive: true });
9537
+ element.addEventListener("wheel", interrupt, { passive: true });
9538
+ element.addEventListener("touchstart", interrupt, { passive: true });
9539
+ element.addEventListener("keydown", interrupt);
9540
+ observer.observe(element);
9541
+ remember();
9542
+ return () => {
9543
+ cancelAnimationFrame(frame);
9544
+ observer.disconnect();
9545
+ element.removeEventListener("scroll", remember);
9546
+ element.removeEventListener("wheel", interrupt);
9547
+ element.removeEventListener("touchstart", interrupt);
9548
+ element.removeEventListener("keydown", interrupt);
9549
+ };
9550
+ }, [
9551
+ scrollElementFromCtx,
9552
+ virtualizer,
9553
+ firstMessageId
9554
+ ]);
8084
9555
  const messageElements = shouldVirtualize ? [] : rows.flatMap(renderRow);
8085
- if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8086
- "data-copilotkit": true,
8087
- style: { display: "contents" },
8088
- children: children({
8089
- messageElements,
8090
- messages,
8091
- isRunning,
8092
- interruptElement
9556
+ if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFeedbackScopeContext.Provider, {
9557
+ value: feedbackScope,
9558
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallDisplayContext.Provider, {
9559
+ value: toolCallDisplayContext,
9560
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9561
+ "data-copilotkit": true,
9562
+ style: { display: "contents" },
9563
+ children: children({
9564
+ messageElements,
9565
+ messages,
9566
+ isRunning,
9567
+ interruptElement
9568
+ })
9569
+ })
8093
9570
  })
8094
9571
  });
8095
9572
  const lastMessage = renderedMessages[renderedMessages.length - 1];
8096
- const showCursor = isRunning && lastMessage?.role !== "reasoning";
8097
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8098
- "data-copilotkit": true,
8099
- "data-testid": "copilot-message-list",
8100
- className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
8101
- ...props,
8102
- children: [
8103
- shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8104
- style: {
8105
- height: virtualizer.getTotalSize(),
8106
- position: "relative"
8107
- },
8108
- children: virtualizer.getVirtualItems().map((virtualItem) => {
8109
- const row = rows[virtualItem.index];
8110
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8111
- "data-index": virtualItem.index,
8112
- ref: virtualizer.measureElement,
9573
+ const showCursor = isRunning && lastMessage?.role !== "reasoning" && toolCallDisplay !== "turn";
9574
+ const pendingWorkSummary = toolCallDisplay === "turn" && isRunning && lastMessage?.role === "user";
9575
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFeedbackScopeContext.Provider, {
9576
+ value: feedbackScope,
9577
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallDisplayContext.Provider, {
9578
+ value: toolCallDisplayContext,
9579
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9580
+ "data-copilotkit": true,
9581
+ "data-testid": "copilot-message-list",
9582
+ className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
9583
+ ...props,
9584
+ children: [
9585
+ shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8113
9586
  style: {
8114
- position: "absolute",
8115
- top: 0,
8116
- left: 0,
8117
- width: "100%",
8118
- transform: `translateY(${virtualItem.start}px)`
9587
+ height: virtualizer.getTotalSize(),
9588
+ position: "relative"
8119
9589
  },
8120
- children: renderRow(row)
8121
- }, rowKey(row));
8122
- })
8123
- }) : messageElements,
8124
- interruptElement,
8125
- showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8126
- className: "cpk:mt-2",
8127
- children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
9590
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
9591
+ const row = rows[virtualItem.index];
9592
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9593
+ "data-index": virtualItem.index,
9594
+ "data-scroll-anchor": rowKey(row),
9595
+ ref: virtualizer.measureElement,
9596
+ style: {
9597
+ position: "absolute",
9598
+ top: 0,
9599
+ left: 0,
9600
+ width: "100%",
9601
+ transform: `translateY(${virtualItem.start}px)`
9602
+ },
9603
+ children: renderRow(row)
9604
+ }, rowKey(row));
9605
+ })
9606
+ }) : messageElements,
9607
+ interruptElement,
9608
+ pendingWorkSummary && renderWorkSummary(latestTurnKey, []),
9609
+ showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9610
+ className: "cpk:mt-2",
9611
+ children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
9612
+ })
9613
+ ]
8128
9614
  })
8129
- ]
9615
+ })
8130
9616
  });
8131
9617
  }
8132
9618
  CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
@@ -8511,6 +9997,16 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8511
9997
  });
8512
9998
  }
8513
9999
  function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, intelligenceIndicator, children, className, ...props }) {
10000
+ const configuredThreadId = useCopilotChatConfiguration()?.threadId;
10001
+ const [fallbackThreadId, setFallbackThreadId] = (0, react.useState)(messages[0]?.id);
10002
+ const previousMessageIds = (0, react.useRef)(/* @__PURE__ */ new Set());
10003
+ (0, react.useEffect)(() => {
10004
+ if (messages.length === 0) return;
10005
+ const overlaps = messages.some((message) => previousMessageIds.current.has(message.id));
10006
+ previousMessageIds.current = new Set(messages.map((message) => message.id));
10007
+ if (!overlaps) setFallbackThreadId(messages[0].id);
10008
+ }, [messages]);
10009
+ const scrollThreadId = configuredThreadId ?? fallbackThreadId;
8514
10010
  const [inputContainerEl, setInputContainerEl] = (0, react.useState)(null);
8515
10011
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
8516
10012
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -8577,6 +10073,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8577
10073
  }) : null;
8578
10074
  const BoundScrollView = renderSlot(scrollView, CopilotChatView.ScrollView, {
8579
10075
  autoScroll,
10076
+ scrollThreadId,
8580
10077
  inputContainerHeight,
8581
10078
  isResizing,
8582
10079
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -8672,12 +10169,102 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8672
10169
  });
8673
10170
  }
8674
10171
  (function(_CopilotChatView) {
8675
- const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
8676
- const { isAtBottom, scrollToBottom, scrollRef, state } = (0, use_stick_to_bottom.useStickToBottomContext)();
10172
+ const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing, scrollThreadId }) => {
10173
+ const { isAtBottom, scrollToBottom, stopScroll, scrollRef, state } = (0, use_stick_to_bottom.useStickToBottomContext)();
8677
10174
  const [scrollEl, setScrollEl] = (0, react.useState)(null);
10175
+ const readingOffsetRef = (0, react.useRef)(null);
10176
+ const pinIntentRef = (0, react.useRef)(false);
10177
+ const stateRef = (0, react.useRef)(state);
10178
+ stateRef.current = state;
8678
10179
  (0, react.useLayoutEffect)(() => {
8679
10180
  setScrollEl(scrollRef.current ?? null);
8680
10181
  }, []);
10182
+ (0, react.useEffect)(() => {
10183
+ if (!scrollEl) return;
10184
+ let hidden = false;
10185
+ let resumeBottom = false;
10186
+ let pendingFrame = 0;
10187
+ let resumeFrame = 0;
10188
+ let settleFrame = 0;
10189
+ readingOffsetRef.current = null;
10190
+ const rememberScroll = () => {
10191
+ if (hidden || scrollEl.clientHeight === 0) return;
10192
+ if (resumeBottom) {
10193
+ readingOffsetRef.current = null;
10194
+ return;
10195
+ }
10196
+ if (pinIntentRef.current) if (stateRef.current.escapedFromLock) pinIntentRef.current = false;
10197
+ else {
10198
+ readingOffsetRef.current = null;
10199
+ if (!stateRef.current.isNearBottom) return;
10200
+ pinIntentRef.current = false;
10201
+ }
10202
+ readingOffsetRef.current = !stateRef.current.isAtBottom && (stateRef.current.escapedFromLock || !stateRef.current.isNearBottom) ? scrollEl.scrollTop : null;
10203
+ };
10204
+ const onScroll = () => {
10205
+ rememberScroll();
10206
+ cancelAnimationFrame(pendingFrame);
10207
+ pendingFrame = requestAnimationFrame(rememberScroll);
10208
+ };
10209
+ const onWheel = (event) => {
10210
+ if (event.deltaY < 0) {
10211
+ pinIntentRef.current = false;
10212
+ resumeBottom = false;
10213
+ }
10214
+ };
10215
+ const resizeObserver = new ResizeObserver(() => {
10216
+ if (scrollEl.clientHeight === 0) {
10217
+ hidden = true;
10218
+ resumeBottom = pinIntentRef.current || readingOffsetRef.current === null;
10219
+ if (!resumeBottom) stopScroll();
10220
+ } else if (hidden) {
10221
+ hidden = false;
10222
+ if (resumeBottom) {
10223
+ stopScroll();
10224
+ resumeFrame = requestAnimationFrame(() => {
10225
+ settleFrame = requestAnimationFrame(() => {
10226
+ if (scrollEl.clientHeight > 0 && resumeBottom) {
10227
+ resumeBottom = false;
10228
+ pinIntentRef.current = true;
10229
+ scrollToBottom({ animation: "instant" });
10230
+ }
10231
+ });
10232
+ });
10233
+ } else if (readingOffsetRef.current !== null) {
10234
+ stopScroll();
10235
+ scrollEl.scrollTop = readingOffsetRef.current;
10236
+ }
10237
+ }
10238
+ });
10239
+ rememberScroll();
10240
+ scrollEl.addEventListener("scroll", onScroll);
10241
+ scrollEl.addEventListener("wheel", onWheel, { passive: true });
10242
+ resizeObserver.observe(scrollEl);
10243
+ return () => {
10244
+ scrollEl.removeEventListener("scroll", onScroll);
10245
+ scrollEl.removeEventListener("wheel", onWheel);
10246
+ cancelAnimationFrame(pendingFrame);
10247
+ cancelAnimationFrame(resumeFrame);
10248
+ cancelAnimationFrame(settleFrame);
10249
+ resizeObserver.disconnect();
10250
+ };
10251
+ }, [
10252
+ scrollEl,
10253
+ scrollToBottom,
10254
+ stopScroll,
10255
+ scrollThreadId
10256
+ ]);
10257
+ const startBottomPin = (0, react.useCallback)((instant = false) => {
10258
+ readingOffsetRef.current = null;
10259
+ pinIntentRef.current = true;
10260
+ scrollToBottom(instant ? { animation: "instant" } : void 0);
10261
+ }, [scrollToBottom]);
10262
+ const previousScrollThreadId = (0, react.useRef)(scrollThreadId);
10263
+ (0, react.useEffect)(() => {
10264
+ if (previousScrollThreadId.current === scrollThreadId) return;
10265
+ previousScrollThreadId.current = scrollThreadId;
10266
+ startBottomPin(true);
10267
+ }, [scrollThreadId, startBottomPin]);
8681
10268
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
8682
10269
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
8683
10270
  value: scrollEl,
@@ -8699,7 +10286,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8699
10286
  !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8700
10287
  className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
8701
10288
  style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
8702
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
10289
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => startBottomPin() })
8703
10290
  })
8704
10291
  ] })
8705
10292
  })
@@ -8747,7 +10334,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8747
10334
  })
8748
10335
  });
8749
10336
  };
8750
- _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
10337
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollThreadId, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
8751
10338
  const mode = normalizeAutoScroll(autoScroll);
8752
10339
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
8753
10340
  const scrollRef = (0, react.useRef)(null);
@@ -8836,6 +10423,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8836
10423
  initial: "smooth",
8837
10424
  ...props,
8838
10425
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollContent, {
10426
+ scrollThreadId,
8839
10427
  scrollToBottomButton,
8840
10428
  feather,
8841
10429
  inputContainerHeight,
@@ -8844,14 +10432,18 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
8844
10432
  })
8845
10433
  });
8846
10434
  };
8847
- _CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8848
- "data-testid": "copilot-scroll-to-bottom",
8849
- variant: "outline",
8850
- size: "sm",
8851
- className: (0, tailwind_merge.twMerge)("cpk:rounded-full cpk:w-10 cpk:h-10 cpk:p-0 cpk:pointer-events-auto", "cpk:bg-white cpk:dark:bg-gray-900", "cpk:shadow-lg cpk:border cpk:border-gray-200 cpk:dark:border-gray-700", "cpk:hover:bg-gray-50 cpk:dark:hover:bg-gray-800", "cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer", className),
8852
- ...props,
8853
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
8854
- });
10435
+ _CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => {
10436
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
10437
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
10438
+ "data-testid": "copilot-scroll-to-bottom",
10439
+ variant: "outline",
10440
+ size: "sm",
10441
+ className: (0, tailwind_merge.twMerge)("cpk:rounded-full cpk:w-10 cpk:h-10 cpk:p-0 cpk:pointer-events-auto", "cpk:bg-white cpk:dark:bg-gray-900", "cpk:shadow-lg cpk:border cpk:border-gray-200 cpk:dark:border-gray-700", "cpk:hover:bg-gray-50 cpk:dark:hover:bg-gray-800", "cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer", className),
10442
+ "aria-label": labels.chatScrollToBottomLabel,
10443
+ ...props,
10444
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, { className: "cpk:w-4 cpk:h-4 cpk:text-gray-600 cpk:dark:text-white" })
10445
+ });
10446
+ };
8855
10447
  _CopilotChatView.Feather = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8856
10448
  className,
8857
10449
  ...props
@@ -10234,6 +11826,7 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
10234
11826
  if (containerRef.current?.contains(target)) return;
10235
11827
  const toggleButton = document.querySelector("[data-slot='chat-toggle-button']");
10236
11828
  if (toggleButton && toggleButton.contains(target)) return;
11829
+ if (target instanceof Element && target.closest("[data-copilotkit-popover]")) return;
10237
11830
  setModalOpen?.(false);
10238
11831
  };
10239
11832
  document.addEventListener("pointerdown", handlePointerDown);
@@ -10744,62 +12337,14 @@ _radix_ui_react_dropdown_menu = __toESM(_radix_ui_react_dropdown_menu);
10744
12337
 
10745
12338
  //#endregion
10746
12339
  //#region src/v2/components/WildcardToolCallRender.tsx
12340
+ /**
12341
+ * Catch-all (`"*"`) tool-call renderer that shows CopilotKit's default
12342
+ * tool-call card for any tool without a dedicated renderer.
12343
+ */
10747
12344
  const WildcardToolCallRender = defineToolCallRenderer({
10748
12345
  name: "*",
10749
- render: ({ args, result, name, status }) => {
10750
- const [isExpanded, setIsExpanded] = (0, react.useState)(false);
10751
- const statusString = String(status);
10752
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10753
- className: "cpk:mt-2 cpk:pb-2",
10754
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10755
- className: "cpk:rounded-xl cpk:border cpk:border-zinc-200/60 cpk:dark:border-zinc-800/60 cpk:bg-white/70 cpk:dark:bg-zinc-900/50 cpk:shadow-sm cpk:backdrop-blur cpk:p-4",
10756
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10757
- className: "cpk:flex cpk:items-center cpk:justify-between cpk:gap-3 cpk:cursor-pointer",
10758
- onClick: () => setIsExpanded(!isExpanded),
10759
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10760
- className: "cpk:flex cpk:items-center cpk:gap-2 cpk:min-w-0",
10761
- children: [
10762
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10763
- className: `cpk:h-4 cpk:w-4 cpk:text-zinc-500 cpk:dark:text-zinc-400 cpk:transition-transform ${isExpanded ? "cpk:rotate-90" : ""}`,
10764
- fill: "none",
10765
- viewBox: "0 0 24 24",
10766
- strokeWidth: 2,
10767
- stroke: "currentColor",
10768
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
10769
- strokeLinecap: "round",
10770
- strokeLinejoin: "round",
10771
- d: "M8.25 4.5l7.5 7.5-7.5 7.5"
10772
- })
10773
- }),
10774
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cpk:inline-block cpk:h-2 cpk:w-2 cpk:rounded-full cpk:bg-blue-500" }),
10775
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10776
- className: "cpk:truncate cpk:text-sm cpk:font-medium cpk:text-zinc-900 cpk:dark:text-zinc-100",
10777
- children: name
10778
- })
10779
- ]
10780
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10781
- className: `cpk:inline-flex cpk:items-center cpk:rounded-full cpk:px-2 cpk:py-1 cpk:text-xs cpk:font-medium ${statusString === "inProgress" || statusString === "executing" ? "cpk:bg-amber-100 cpk:text-amber-800 cpk:dark:bg-amber-500/15 cpk:dark:text-amber-400" : statusString === "complete" ? "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"}`,
10782
- children: String(status)
10783
- })]
10784
- }), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10785
- className: "cpk:mt-3 cpk:grid cpk:gap-4",
10786
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10787
- className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
10788
- children: "Arguments"
10789
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
10790
- className: "cpk:mt-2 cpk:max-h-64 cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-50 cpk:dark:bg-zinc-800/60 cpk:p-3 cpk:text-xs cpk:leading-relaxed cpk:text-zinc-800 cpk:dark:text-zinc-200 cpk:whitespace-pre-wrap cpk:break-words",
10791
- children: JSON.stringify(args ?? {}, null, 2)
10792
- })] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10793
- className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
10794
- children: "Result"
10795
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
10796
- className: "cpk:mt-2 cpk:max-h-64 cpk:overflow-auto cpk:rounded-md cpk:bg-zinc-50 cpk:dark:bg-zinc-800/60 cpk:p-3 cpk:text-xs cpk:leading-relaxed cpk:text-zinc-800 cpk:dark:text-zinc-200 cpk:whitespace-pre-wrap cpk:break-words",
10797
- children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
10798
- })] })]
10799
- })]
10800
- })
10801
- });
10802
- }
12346
+ group: true,
12347
+ render: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(props) })
10803
12348
  });
10804
12349
 
10805
12350
  //#endregion
@@ -13044,9 +14589,11 @@ exports.CopilotChatSuggestionView = CopilotChatSuggestionView;
13044
14589
  exports.CopilotChatToggleButton = CopilotChatToggleButton;
13045
14590
  exports.CopilotChatToggleButtonCloseIcon = DefaultCloseIcon;
13046
14591
  exports.CopilotChatToggleButtonOpenIcon = DefaultOpenIcon;
14592
+ exports.CopilotChatToolCallGroup = CopilotChatToolCallGroup;
13047
14593
  exports.CopilotChatToolCallsView = CopilotChatToolCallsView;
13048
14594
  exports.CopilotChatUserMessage = CopilotChatUserMessage_default;
13049
14595
  exports.CopilotChatView = CopilotChatView_default;
14596
+ exports.CopilotChatWorkSummary = CopilotChatWorkSummary;
13050
14597
  exports.CopilotKit = CopilotKit;
13051
14598
  Object.defineProperty(exports, 'CopilotKitCore', {
13052
14599
  enumerable: true,
@@ -13275,12 +14822,6 @@ Object.defineProperty(exports, 'IntelligenceAgent', {
13275
14822
  });
13276
14823
  exports.IntelligenceIndicator = IntelligenceIndicator;
13277
14824
  exports.IntelligenceIndicatorView = IntelligenceIndicatorView;
13278
- Object.defineProperty(exports, 'IntelligenceReplayFailedError', {
13279
- enumerable: true,
13280
- get: function () {
13281
- return _copilotkit_core.IntelligenceReplayFailedError;
13282
- }
13283
- });
13284
14825
  Object.defineProperty(exports, 'InterruptSchema', {
13285
14826
  enumerable: true,
13286
14827
  get: function () {