@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
@@ -47,6 +47,8 @@ let zod = require("zod");
47
47
  let _copilotkit_mcp_apps_renderer_activity = require("@copilotkit/mcp-apps-renderer/activity");
48
48
  let _copilotkit_a2ui_renderer = require("@copilotkit/a2ui-renderer");
49
49
  let zod_to_json_schema = require("zod-to-json-schema");
50
+ let _radix_ui_react_popover = require("@radix-ui/react-popover");
51
+ _radix_ui_react_popover = __toESM(_radix_ui_react_popover);
50
52
  let react_dom = require("react-dom");
51
53
  let _tanstack_react_virtual = require("@tanstack/react-virtual");
52
54
  let use_stick_to_bottom = require("use-stick-to-bottom");
@@ -104,6 +106,8 @@ const CopilotChatDefaultLabels = {
104
106
  chatInputToolbarFinishTranscribeButtonLabel: "Finish",
105
107
  chatInputToolbarAddButtonLabel: "Add attachments",
106
108
  chatInputToolbarToolsButtonLabel: "Tools",
109
+ chatInputToolbarSendButtonLabel: "Send message",
110
+ chatInputToolbarStopButtonLabel: "Stop",
107
111
  assistantMessageToolbarCopyCodeLabel: "Copy",
108
112
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
109
113
  assistantMessageToolbarCopyMessageLabel: "Copy",
@@ -116,13 +120,18 @@ const CopilotChatDefaultLabels = {
116
120
  assistantMessageToolbarInspectorHideDescription: "Until you reload this page",
117
121
  assistantMessageToolbarThumbsUpLabel: "Good response",
118
122
  assistantMessageToolbarThumbsDownLabel: "Bad response",
123
+ assistantMessageFeedbackCommentPlaceholder: "Add a comment (optional)",
124
+ assistantMessageFeedbackCommentSubmitLabel: "Send comment",
119
125
  assistantMessageToolbarReadAloudLabel: "Read aloud",
120
126
  assistantMessageToolbarRegenerateLabel: "Regenerate",
121
127
  userMessageToolbarCopyMessageLabel: "Copy",
122
128
  userMessageToolbarEditMessageLabel: "Edit",
129
+ userMessageToolbarPreviousBranchLabel: "Previous branch",
130
+ userMessageToolbarNextBranchLabel: "Next branch",
123
131
  chatDisclaimerText: "AI can make mistakes. Please verify important information.",
124
132
  chatToggleOpenLabel: "Open chat",
125
133
  chatToggleCloseLabel: "Close chat",
134
+ chatScrollToBottomLabel: "Scroll to bottom",
126
135
  modalHeaderTitle: "CopilotKit Chat",
127
136
  welcomeMessageText: "How can I help you today?"
128
137
  };
@@ -384,9 +393,9 @@ ControlledModalOpenScope.displayName = "ControlledModalOpenScope";
384
393
 
385
394
  //#endregion
386
395
  //#region src/v2/lib/utils.ts
387
- const twMerge$10 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
396
+ const twMerge$12 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
388
397
  function cn(...inputs) {
389
- return twMerge$10((0, clsx.clsx)(inputs));
398
+ return twMerge$12((0, clsx.clsx)(inputs));
390
399
  }
391
400
 
392
401
  //#endregion
@@ -834,6 +843,11 @@ function renderSlot(slot, DefaultComponent, props) {
834
843
  //#region src/v2/components/chat/CopilotChatInput.tsx
835
844
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
836
845
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
846
+ /**
847
+ * The chat composer: a text area with the add-menu, voice and send/stop
848
+ * buttons, and a slash-command menu that lists `toolsMenu` items once the text
849
+ * starts with "/".
850
+ */
837
851
  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 }) {
838
852
  const isControlled = value !== void 0;
839
853
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
@@ -1053,10 +1067,12 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1053
1067
  send();
1054
1068
  };
1055
1069
  const BoundAudioRecorder = renderSlot(audioRecorder, CopilotChatAudioRecorder, { ref: audioRecorderRef });
1070
+ const showsStop = isProcessing && canStop;
1056
1071
  const BoundSendButton = renderSlot(sendButton, CopilotChatInput.SendButton, {
1057
1072
  onClick: handleSendButtonClick,
1058
1073
  disabled: isProcessing ? !canStop : !canSend,
1059
- children: isProcessing && canStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Square, { className: "cpk:size-[18px] cpk:fill-current" }) : void 0
1074
+ "aria-label": showsStop ? labels.chatInputToolbarStopButtonLabel : labels.chatInputToolbarSendButtonLabel,
1075
+ children: showsStop ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Square, { className: "cpk:size-[18px] cpk:fill-current" }) : void 0
1060
1076
  });
1061
1077
  const BoundStartTranscribeButton = renderSlot(startTranscribeButton, CopilotChatInput.StartTranscribeButton, { onClick: onStartTranscribe });
1062
1078
  const BoundCancelTranscribeButton = renderSlot(cancelTranscribeButton, CopilotChatInput.CancelTranscribeButton, { onClick: onCancelTranscribe });
@@ -1368,18 +1384,22 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1368
1384
  });
1369
1385
  }
1370
1386
  (function(_CopilotChatInput) {
1371
- _CopilotChatInput.SendButton = ({ className, children, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1372
- className: "cpk:mr-[10px]",
1373
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1374
- type: "button",
1375
- "data-testid": "copilot-send-button",
1376
- variant: "chatInputToolbarPrimary",
1377
- size: "chatInputToolbarIcon",
1378
- className,
1379
- ...props,
1380
- children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
1381
- })
1382
- });
1387
+ _CopilotChatInput.SendButton = ({ className, children, ...props }) => {
1388
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1389
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1390
+ className: "cpk:mr-[10px]",
1391
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1392
+ type: "button",
1393
+ "data-testid": "copilot-send-button",
1394
+ variant: "chatInputToolbarPrimary",
1395
+ size: "chatInputToolbarIcon",
1396
+ className,
1397
+ "aria-label": labels.chatInputToolbarSendButtonLabel,
1398
+ ...props,
1399
+ children: children ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[18px]" })
1400
+ })
1401
+ });
1402
+ };
1383
1403
  const ToolbarButton = _CopilotChatInput.ToolbarButton = ({ icon, labelKey, defaultClassName, className, ...props }) => {
1384
1404
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1385
1405
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
@@ -1389,6 +1409,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1389
1409
  variant: "chatInputToolbarSecondary",
1390
1410
  size: "chatInputToolbarIcon",
1391
1411
  className: (0, tailwind_merge.twMerge)(defaultClassName, className),
1412
+ "aria-label": labels[labelKey],
1392
1413
  ...props,
1393
1414
  children: icon
1394
1415
  })
@@ -1459,6 +1480,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1459
1480
  size: "chatInputToolbarIcon",
1460
1481
  className: (0, tailwind_merge.twMerge)("cpk:ml-1", className),
1461
1482
  disabled: isDisabled,
1483
+ "aria-label": labels.chatInputToolbarAddButtonLabel,
1462
1484
  ...props,
1463
1485
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "cpk:size-[20px]" })
1464
1486
  });
@@ -1608,6 +1630,32 @@ function warnAboutUnrenderedToolCalls(toolNames, registered, alreadyWarned) {
1608
1630
  }
1609
1631
  }
1610
1632
  /**
1633
+ * The tool-call renderers registered on the provider, kept current.
1634
+ * @internal
1635
+ */
1636
+ function useToolCallRenderers() {
1637
+ const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
1638
+ return (0, react.useSyncExternalStore)((callback) => copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
1639
+ }
1640
+ /**
1641
+ * Which tool names have a renderer of their own, whether a wildcard (`"*"`)
1642
+ * renderer is registered, and which of them opted into grouping.
1643
+ * @internal
1644
+ */
1645
+ function useToolCallRendererNames() {
1646
+ const renderers = useToolCallRenderers();
1647
+ return (0, react.useMemo)(() => {
1648
+ const named = renderers.filter((rc) => rc.name !== "*");
1649
+ const wildcards = renderers.filter((rc) => rc.name === "*");
1650
+ return {
1651
+ named: new Set(named.map((rc) => rc.name)),
1652
+ groupedNames: new Set(named.filter((rc) => rc.group).map((rc) => rc.name)),
1653
+ wildcard: wildcards.length > 0,
1654
+ wildcardGroups: wildcards.some((rc) => rc.group)
1655
+ };
1656
+ }, [renderers]);
1657
+ }
1658
+ /**
1611
1659
  * Hook that returns a function to render tool calls based on the render functions
1612
1660
  * defined in CopilotKitProvider.
1613
1661
  *
@@ -1618,9 +1666,7 @@ function useRenderToolCall() {
1618
1666
  const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
1619
1667
  const unrenderedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
1620
1668
  const warnedToolNames = (0, react.useRef)(/* @__PURE__ */ new Set());
1621
- const renderToolCalls = (0, react.useSyncExternalStore)((callback) => {
1622
- return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
1623
- }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
1669
+ const renderToolCalls = useToolCallRenderers();
1624
1670
  const renderToolCall = (0, react.useCallback)(({ toolCall, toolMessage }) => {
1625
1671
  const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
1626
1672
  const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
@@ -1655,7 +1701,7 @@ function useRenderToolCall() {
1655
1701
 
1656
1702
  //#endregion
1657
1703
  //#region package.json
1658
- var version = "1.79.2-canary.1792172552";
1704
+ var version = "1.80.0";
1659
1705
 
1660
1706
  //#endregion
1661
1707
  //#region src/v2/components/CopilotKitInspector.tsx
@@ -1891,7 +1937,7 @@ function InlineFeatureWarning({ featureName }) {
1891
1937
  * Fetches resource content on-demand via proxied MCP requests. The React shell
1892
1938
  * owns the iframe; `bindMcpApp` owns the protocol.
1893
1939
  */
1894
- const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, message, agent }) {
1940
+ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, message, agent, allowSameOriginFallback }) {
1895
1941
  const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
1896
1942
  const messageId = message?.id;
1897
1943
  const containerRef = (0, react.useRef)(null);
@@ -1941,6 +1987,7 @@ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, mess
1941
1987
  getAgent: () => agentRef.current,
1942
1988
  host: copilotkit,
1943
1989
  messageId,
1990
+ options: { allowSameOriginFallback },
1944
1991
  hooks: {
1945
1992
  onResource: (resource) => {
1946
1993
  if (!mounted) return;
@@ -1982,6 +2029,7 @@ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, mess
1982
2029
  agent,
1983
2030
  copilotkit,
1984
2031
  messageId,
2032
+ allowSameOriginFallback,
1985
2033
  content.resourceUri,
1986
2034
  content.serverHash,
1987
2035
  content.serverId
@@ -3196,7 +3244,8 @@ function defineToolCallRenderer(def) {
3196
3244
  name: def.name,
3197
3245
  args: argsSchema,
3198
3246
  render: def.render,
3199
- ...def.agentId ? { agentId: def.agentId } : {}
3247
+ ...def.agentId ? { agentId: def.agentId } : {},
3248
+ ...def.group ? { group: true } : {}
3200
3249
  };
3201
3250
  }
3202
3251
 
@@ -3412,6 +3461,11 @@ const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follo
3412
3461
  - Use CSS Grid or Flexbox for layout. Ensure the UI looks good at any width.
3413
3462
  - Minimal transitions (150ms) for hover/focus states only. No decorative animations.
3414
3463
  - Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
3464
+ /** The MCP Apps renderer with `mcpApps.allowSameOriginFallback` applied. */
3465
+ const SameOriginMCPAppsActivityRenderer = (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MCPAppsActivityRenderer, {
3466
+ ...props,
3467
+ allowSameOriginFallback: true
3468
+ });
3415
3469
  const DEFAULT_LEARNING = Object.freeze({});
3416
3470
  function toLearningConfig(learning) {
3417
3471
  if (learning === true) return DEFAULT_LEARNING;
@@ -3428,7 +3482,8 @@ function useStableArrayProp(prop, warningMessage, isMeaningfulChange) {
3428
3482
  }, [value, warningMessage]);
3429
3483
  return value;
3430
3484
  }
3431
- 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 }) => {
3485
+ /** Provide a shared core instance and synchronize runtime, tool, and UI configuration. */
3486
+ 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 }) => {
3432
3487
  const learningConfig = (0, react.useMemo)(() => toLearningConfig(learning), [learning]);
3433
3488
  const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
3434
3489
  const [inspectorVisible, setInspectorVisible] = (0, react.useState)(false);
@@ -3485,11 +3540,12 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
3485
3540
  const core = copilotkitRef.current;
3486
3541
  return (0, _copilotkit_a2ui_renderer.filterCatalog)(rawCatalog, (name) => core ? core.isCatalogComponentEnabled(name) : true);
3487
3542
  }, [rawCatalog, catalogToggleVersion]);
3543
+ const mcpAppsAllowSameOrigin = mcpApps?.allowSameOriginFallback === true;
3488
3544
  const builtInActivityRenderers = (0, react.useMemo)(() => {
3489
3545
  const renderers = [{
3490
3546
  activityType: _copilotkit_mcp_apps_renderer_activity.MCPAppsActivityType,
3491
3547
  content: _copilotkit_mcp_apps_renderer_activity.MCPAppsActivityContentSchema,
3492
- render: MCPAppsActivityRenderer
3548
+ render: mcpAppsAllowSameOrigin ? SameOriginMCPAppsActivityRenderer : MCPAppsActivityRenderer
3493
3549
  }];
3494
3550
  if (openGenUIActive) renderers.push({
3495
3551
  activityType: OpenGenerativeUIActivityType,
@@ -3507,7 +3563,8 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
3507
3563
  a2uiActive,
3508
3564
  openGenUIActive,
3509
3565
  a2ui,
3510
- filteredCatalog
3566
+ filteredCatalog,
3567
+ mcpAppsAllowSameOrigin
3511
3568
  ]);
3512
3569
  const allActivityRenderers = (0, react.useMemo)(() => {
3513
3570
  return [...renderActivityMessagesList, ...builtInActivityRenderers];
@@ -3615,6 +3672,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = EMPTY
3615
3672
  description: _copilotkit_shared.GENERATE_SANDBOXED_UI_DESCRIPTION,
3616
3673
  parameters: GenerateSandboxedUiArgsSchema,
3617
3674
  handler: async () => "UI generated",
3675
+ reconnectBehavior: "resume-pending",
3618
3676
  followUp: true,
3619
3677
  render: OpenGenerativeUIToolRenderer
3620
3678
  }];
@@ -4346,7 +4404,8 @@ function useRenderTool(config, deps) {
4346
4404
  ...props,
4347
4405
  parameters: props.args
4348
4406
  }),
4349
- ...config.agentId ? { agentId: config.agentId } : {}
4407
+ ...config.agentId ? { agentId: config.agentId } : {},
4408
+ group: config.group
4350
4409
  }) : defineToolCallRenderer({
4351
4410
  name: config.name,
4352
4411
  args: config.parameters,
@@ -4364,16 +4423,395 @@ function useRenderTool(config, deps) {
4364
4423
  parameters: props.args
4365
4424
  });
4366
4425
  },
4367
- ...config.agentId ? { agentId: config.agentId } : {}
4426
+ ...config.agentId ? { agentId: config.agentId } : {},
4427
+ group: config.group
4368
4428
  });
4369
4429
  copilotkit.addHookRenderToolCall(renderer);
4370
4430
  }, [
4371
4431
  config.name,
4432
+ config.group,
4372
4433
  copilotkit,
4373
4434
  depsVersion
4374
4435
  ]);
4375
4436
  }
4376
4437
 
4438
+ //#endregion
4439
+ //#region src/v2/components/chat/CopilotChatToolCallGroup.tsx
4440
+ /**
4441
+ * Set by a group around the calls it shows, so CopilotKit's built-in tool card
4442
+ * draws itself as a compact row that fits the group instead of a card.
4443
+ * @internal
4444
+ */
4445
+ const ToolCallGroupContext = (0, react.createContext)(null);
4446
+ /**
4447
+ * A call without a result is running only while the group is active: once
4448
+ * the run has moved on or ended, its result is never coming.
4449
+ */
4450
+ const entryStatus = ({ toolMessage }, active) => toolMessage ? toolMessage.error ? "failed" : "done" : active ? "running" : "stopped";
4451
+ /** @internal */
4452
+ const statusLabels = {
4453
+ running: "Running",
4454
+ done: "Done",
4455
+ failed: "Failed",
4456
+ stopped: "Stopped"
4457
+ };
4458
+ /** The first non-empty string argument: usually a command, path or query. */
4459
+ function argumentPreview(parameters) {
4460
+ if (!parameters || typeof parameters !== "object") return void 0;
4461
+ return Object.values(parameters).find((v) => typeof v === "string" && v.trim().length > 0)?.replace(/\s+/g, " ");
4462
+ }
4463
+ const entryPreview = (entry) => argumentPreview((0, _copilotkit_shared.partialJSONParse)(entry.toolCall.function.arguments));
4464
+ /** "run_command ×2, read_file" */
4465
+ function toolNames(calls) {
4466
+ const counts = /* @__PURE__ */ new Map();
4467
+ for (const { toolCall } of calls) {
4468
+ const name = toolCall.function.name;
4469
+ counts.set(name, (counts.get(name) ?? 0) + 1);
4470
+ }
4471
+ return [...counts].map(([name, count]) => count > 1 ? `${name} ×${count}` : name).join(", ");
4472
+ }
4473
+ const pluralize = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
4474
+ /**
4475
+ * Height transition between closed and its content's height. Closed content
4476
+ * stays mounted (so its state survives) but is inert.
4477
+ * @internal
4478
+ */
4479
+ function Collapsible({ open, id, className, children }) {
4480
+ const ref = (0, react.useRef)(null);
4481
+ (0, react.useLayoutEffect)(() => {
4482
+ ref.current?.toggleAttribute("inert", !open);
4483
+ }, [open]);
4484
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4485
+ ref,
4486
+ id,
4487
+ 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),
4488
+ style: { gridTemplateRows: open ? "1fr" : "0fr" },
4489
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4490
+ className: "cpk:min-h-0 cpk:overflow-hidden",
4491
+ children
4492
+ })
4493
+ });
4494
+ }
4495
+ function StatusIcon({ status, className }) {
4496
+ const Icon = {
4497
+ running: lucide_react.Loader2,
4498
+ done: lucide_react.Check,
4499
+ failed: lucide_react.X,
4500
+ stopped: lucide_react.Circle
4501
+ }[status];
4502
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4503
+ "aria-hidden": "true",
4504
+ 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)
4505
+ });
4506
+ }
4507
+ function Chevron({ open, className }) {
4508
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
4509
+ "aria-hidden": "true",
4510
+ className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", open && "cpk:rotate-90", className)
4511
+ });
4512
+ }
4513
+ function FailedCount({ calls }) {
4514
+ const failed = calls.filter((call) => call.toolMessage?.error).length;
4515
+ if (!failed) return null;
4516
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4517
+ className: "cpk:shrink-0 cpk:text-destructive",
4518
+ children: [failed, " failed"]
4519
+ });
4520
+ }
4521
+ /**
4522
+ * Guarded JSON.stringify for the payload blocks. A circular reference would
4523
+ * otherwise crash the entire React tree on render.
4524
+ */
4525
+ function stringifyPayload(value) {
4526
+ if (typeof value === "string") return value;
4527
+ try {
4528
+ return JSON.stringify(value, null, 2);
4529
+ } catch (err) {
4530
+ console.warn("[CopilotKit] Failed to JSON.stringify tool-call payload; falling back to String():", err);
4531
+ try {
4532
+ return String(value);
4533
+ } catch {
4534
+ return "[unserializable]";
4535
+ }
4536
+ }
4537
+ }
4538
+ function Payload({ label, value }) {
4539
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4540
+ className: "cpk:text-[11px] cpk:font-medium cpk:uppercase cpk:tracking-wide cpk:text-muted-foreground",
4541
+ children: label
4542
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
4543
+ 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",
4544
+ children: stringifyPayload(value)
4545
+ })] });
4546
+ }
4547
+ /**
4548
+ * A tool call's arguments and, once it has one, its result.
4549
+ * @internal
4550
+ */
4551
+ function ToolCallPayloads({ parameters, result, className }) {
4552
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4553
+ className: (0, tailwind_merge.twMerge)("cpk:grid cpk:gap-3", className),
4554
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Payload, {
4555
+ label: "Arguments",
4556
+ value: parameters ?? {}
4557
+ }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Payload, {
4558
+ label: "Result",
4559
+ value: result
4560
+ })]
4561
+ });
4562
+ }
4563
+ /**
4564
+ * The built-in tool card drawn as a row inside a group: status, name and main
4565
+ * argument, opening to the payloads.
4566
+ * @internal
4567
+ */
4568
+ function ToolCallRow({ name, parameters, result, status, appearance, className, ...props }) {
4569
+ const [open, setOpen] = (0, react.useState)(false);
4570
+ const detailsId = (0, react.useId)();
4571
+ const preview = argumentPreview(parameters);
4572
+ const line = appearance === "line";
4573
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4574
+ "data-copilotkit": true,
4575
+ className: (0, tailwind_merge.twMerge)(!line && "cpk:border-t cpk:border-border", className),
4576
+ ...props,
4577
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4578
+ type: "button",
4579
+ "aria-expanded": open,
4580
+ "aria-controls": detailsId,
4581
+ onClick: () => setOpen((value) => !value),
4582
+ 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"),
4583
+ children: [
4584
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status }),
4585
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4586
+ "data-testid": "copilot-tool-render-name",
4587
+ className: (0, tailwind_merge.twMerge)("cpk:shrink-0 cpk:font-mono cpk:text-[13px] cpk:font-medium cpk:text-foreground", status === "running" && "cpk-shimmer"),
4588
+ children: name
4589
+ }),
4590
+ preview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4591
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-[13px] cpk:text-muted-foreground",
4592
+ children: preview
4593
+ }),
4594
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4595
+ "data-testid": "copilot-tool-render-status",
4596
+ className: "cpk:sr-only",
4597
+ children: statusLabels[status]
4598
+ }),
4599
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
4600
+ open,
4601
+ 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")
4602
+ })
4603
+ ]
4604
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
4605
+ open,
4606
+ id: detailsId,
4607
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallPayloads, {
4608
+ parameters,
4609
+ result,
4610
+ className: line ? "cpk:mt-1 cpk:mb-2 cpk:ml-5.5" : "cpk:px-3.5 cpk:pt-1 cpk:pb-3"
4611
+ })
4612
+ })]
4613
+ });
4614
+ }
4615
+ /**
4616
+ * Tool calls made in a row, collapsed into one summary. While a call runs the
4617
+ * summary names it ("Running read_file src/app.ts"); once they're all done it
4618
+ * reads "Used 3 tools" and opens to the calls.
4619
+ *
4620
+ * Each call is drawn by its registered renderer. CopilotKit's built-in card
4621
+ * (`useDefaultRenderTool`) draws a compact row inside the card and line
4622
+ * appearances; a custom wildcard renderer draws whatever it draws.
4623
+ */
4624
+ function CopilotChatToolCallGroup({ calls, renderCall, appearance = "card", isActive = false, className, ...props }) {
4625
+ const [open, setOpen] = (0, react.useState)(false);
4626
+ const bodyId = (0, react.useId)();
4627
+ const latest = calls[calls.length - 1];
4628
+ if (!latest) return null;
4629
+ const current = isActive ? calls.find((call) => !call.toolMessage) ?? latest : void 0;
4630
+ const preview = current ? entryPreview(current) : void 0;
4631
+ const done = calls.filter((call) => call.toolMessage).length;
4632
+ const statuses = calls.map((call) => entryStatus(call, isActive));
4633
+ const failedIds = new Set(calls.filter((_, index) => statuses[index] === "failed").map((call) => call.toolCall.id));
4634
+ const status = current ? "running" : failedIds.size > 0 ? "failed" : statuses.includes("stopped") ? "stopped" : "done";
4635
+ const rows = (rowAppearance) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
4636
+ value: {
4637
+ appearance: rowAppearance,
4638
+ failedIds,
4639
+ active: isActive
4640
+ },
4641
+ children: calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
4642
+ });
4643
+ const groupProps = {
4644
+ "data-copilotkit": true,
4645
+ "data-testid": "copilot-tool-call-group",
4646
+ "data-appearance": appearance,
4647
+ "data-state": status,
4648
+ ...props
4649
+ };
4650
+ const runningLabel = current && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4651
+ className: "cpk:shrink-0 cpk:font-medium cpk-shimmer",
4652
+ children: ["Running ", current.toolCall.function.name]
4653
+ }), preview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4654
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:opacity-70",
4655
+ children: preview
4656
+ })] });
4657
+ if (appearance === "line") {
4658
+ if (calls.length === 1 && !current) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4659
+ ...groupProps,
4660
+ className: (0, tailwind_merge.twMerge)("cpk:my-2", className),
4661
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
4662
+ value: {
4663
+ appearance: "line",
4664
+ failedIds,
4665
+ active: isActive
4666
+ },
4667
+ children: renderCall(latest)
4668
+ })
4669
+ });
4670
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4671
+ ...groupProps,
4672
+ className: (0, tailwind_merge.twMerge)("cpk:my-3", className),
4673
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4674
+ type: "button",
4675
+ "aria-expanded": open,
4676
+ "aria-controls": bodyId,
4677
+ onClick: () => setOpen((value) => !value),
4678
+ 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",
4679
+ children: [runningLabel || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4680
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4681
+ className: "cpk:shrink-0 cpk:font-medium",
4682
+ children: ["Used ", pluralize(calls.length, "tool")]
4683
+ }),
4684
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4685
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:opacity-70",
4686
+ children: toolNames(calls)
4687
+ }),
4688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls })
4689
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
4690
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
4691
+ open,
4692
+ id: bodyId,
4693
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4694
+ className: "cpk:mt-1 cpk:ml-1 cpk:border-l-2 cpk:border-border cpk:py-0.5 cpk:pl-3",
4695
+ children: rows("line")
4696
+ })
4697
+ })]
4698
+ });
4699
+ }
4700
+ if (appearance === "stack") {
4701
+ if (calls.length === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4702
+ ...groupProps,
4703
+ className: (0, tailwind_merge.twMerge)("cpk:py-1", className),
4704
+ children: renderCall(latest)
4705
+ });
4706
+ const behind = open ? 0 : Math.min(calls.length - 1, 2);
4707
+ const layer = "cpk:rounded-xl cpk:border cpk:border-border cpk:bg-card cpk:text-card-foreground";
4708
+ const latestStatus = entryStatus(latest, isActive);
4709
+ const latestPreview = entryPreview(latest);
4710
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4711
+ ...groupProps,
4712
+ className: (0, tailwind_merge.twMerge)("cpk:my-3", className),
4713
+ style: {
4714
+ paddingBottom: behind * 6,
4715
+ ...props.style
4716
+ },
4717
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4718
+ className: "cpk:relative cpk:isolate",
4719
+ children: [Array.from({ length: behind }, (_, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4720
+ "aria-hidden": "true",
4721
+ className: (0, tailwind_merge.twMerge)(layer, "cpk:absolute cpk:inset-0"),
4722
+ style: {
4723
+ transform: `translateY(${(index + 1) * 6}px) scaleX(${1 - (index + 1) * .045})`,
4724
+ zIndex: -1 - index,
4725
+ opacity: 1 - (index + 1) * .28
4726
+ }
4727
+ }, index)), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4728
+ type: "button",
4729
+ "aria-expanded": open,
4730
+ "aria-controls": open ? bodyId : void 0,
4731
+ onClick: () => setOpen((value) => !value),
4732
+ 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")),
4733
+ children: open ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4734
+ pluralize(calls.length, "tool call"),
4735
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }),
4736
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
4737
+ open: true,
4738
+ className: "cpk:-rotate-90"
4739
+ }),
4740
+ "Collapse"
4741
+ ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4742
+ 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",
4743
+ children: [
4744
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status: latestStatus }),
4745
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4746
+ className: (0, tailwind_merge.twMerge)("cpk:shrink-0 cpk:font-mono cpk:font-medium cpk:text-foreground", latestStatus === "running" && "cpk-shimmer"),
4747
+ children: latest.toolCall.function.name
4748
+ }),
4749
+ latestPreview && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4750
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-muted-foreground",
4751
+ children: latestPreview
4752
+ })
4753
+ ]
4754
+ }, latest.toolCall.id), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4755
+ className: "cpk:ml-2 cpk:inline-flex cpk:shrink-0 cpk:items-center cpk:gap-2 cpk:text-xs cpk:text-muted-foreground",
4756
+ children: [
4757
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }),
4758
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4759
+ className: "cpk:tabular-nums",
4760
+ children: pluralize(calls.length, "call")
4761
+ }),
4762
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, {
4763
+ open: false,
4764
+ className: "cpk:rotate-90"
4765
+ })
4766
+ ]
4767
+ })] })
4768
+ })]
4769
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4770
+ id: bodyId,
4771
+ className: "cpk:[&>*]:animate-[cpk-stack-fan_300ms_cubic-bezier(0.16,1,0.3,1)_both] cpk:motion-reduce:[&>*]:animate-none",
4772
+ children: calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
4773
+ })]
4774
+ });
4775
+ }
4776
+ if (calls.length === 1) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4777
+ ...groupProps,
4778
+ className: (0, tailwind_merge.twMerge)("cpk:py-1", className),
4779
+ children: renderCall(latest)
4780
+ });
4781
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4782
+ ...groupProps,
4783
+ 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),
4784
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
4785
+ type: "button",
4786
+ "aria-expanded": open,
4787
+ "aria-controls": bodyId,
4788
+ onClick: () => setOpen((value) => !value),
4789
+ 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",
4790
+ children: [
4791
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(StatusIcon, { status }),
4792
+ runningLabel || /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4793
+ className: "cpk:shrink-0 cpk:font-medium cpk:text-foreground",
4794
+ children: ["Used ", pluralize(calls.length, "tool")]
4795
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4796
+ className: "cpk:min-w-0 cpk:truncate cpk:font-mono cpk:text-xs cpk:text-muted-foreground",
4797
+ children: toolNames(calls)
4798
+ })] }),
4799
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
4800
+ 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",
4801
+ children: [status === "failed" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FailedCount, { calls }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
4802
+ className: "cpk:tabular-nums",
4803
+ children: status === "running" ? `${done} done` : statusLabels[status]
4804
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Chevron, { open })]
4805
+ })
4806
+ ]
4807
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
4808
+ open,
4809
+ id: bodyId,
4810
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: rows("card") })
4811
+ })]
4812
+ });
4813
+ }
4814
+
4377
4815
  //#endregion
4378
4816
  //#region src/v2/hooks/use-default-render-tool.tsx
4379
4817
  /**
@@ -4457,7 +4895,8 @@ function useDefaultRenderTool(config, deps) {
4457
4895
  const userRender = config?.render;
4458
4896
  useRenderTool({
4459
4897
  name: "*",
4460
- render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) })
4898
+ render: userRender ? (raw) => userRender(adaptRendererProps(raw)) : (raw) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(raw) }),
4899
+ group: userRender ? config?.group === true : true
4461
4900
  }, deps);
4462
4901
  }
4463
4902
  /**
@@ -4478,9 +4917,23 @@ function safeStringifyForPre(value) {
4478
4917
  }
4479
4918
  }
4480
4919
  function DefaultToolCallRenderer({ name, toolCallId, parameters, status, result }) {
4920
+ const group = (0, react.useContext)(ToolCallGroupContext);
4481
4921
  const [isExpanded, setIsExpanded] = (0, react.useState)(false);
4482
4922
  const isActive = status === "inProgress" || status === "executing";
4483
4923
  const isComplete = status === "complete";
4924
+ if (group) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallRow, {
4925
+ "data-testid": "copilot-tool-render",
4926
+ "data-tool-name": name,
4927
+ "data-tool-call-id": toolCallId,
4928
+ "data-status": status,
4929
+ "data-args": safeStringifyForAttr(parameters),
4930
+ "data-result": safeStringifyForAttr(result),
4931
+ name,
4932
+ parameters,
4933
+ result,
4934
+ status: isComplete ? group.failedIds.has(toolCallId) ? "failed" : "done" : group.active ? "running" : "stopped",
4935
+ appearance: group.appearance
4936
+ });
4484
4937
  const statusLabel = isActive ? "Running" : isComplete ? "Done" : status;
4485
4938
  const dotClassName = isActive ? "cpk:bg-amber-500" : isComplete ? "cpk:bg-emerald-500" : "cpk:bg-zinc-400";
4486
4939
  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";
@@ -6177,38 +6630,227 @@ function useLearningContainersInCurrentThread({ learningContainers }) {
6177
6630
  });
6178
6631
  }
6179
6632
 
6633
+ //#endregion
6634
+ //#region src/v2/components/chat/tool-call-display-context.ts
6635
+ /** @internal */
6636
+ const ToolCallDisplayContext = (0, react.createContext)(null);
6637
+
6638
+ //#endregion
6639
+ //#region src/v2/components/chat/tool-call-layout.ts
6640
+ function contentLength(content) {
6641
+ return typeof content === "string" ? content.length : JSON.stringify(content ?? "").length;
6642
+ }
6643
+ const entrySignature = ({ toolCall, toolMessage }) => [
6644
+ toolCall.id,
6645
+ toolCall.function.name,
6646
+ toolCall.function.arguments.length,
6647
+ toolMessage ? `${contentLength(toolMessage.content)}${toolMessage.error ? "!" : ""}` : "-"
6648
+ ].join(":");
6649
+ function signatureOf(segments) {
6650
+ return segments.map((segment) => segment.kind === "call" ? `c(${entrySignature(segment.call)})` : `g${segment.trailing ? "*" : ""}(${segment.calls.map(entrySignature).join(",")})`).join("|");
6651
+ }
6652
+ /** Index of tool results by the call they answer. */
6653
+ function getToolResults(messages) {
6654
+ const results = /* @__PURE__ */ new Map();
6655
+ for (const message of messages) if (message.role === "tool") results.set(message.toolCallId, message);
6656
+ return results;
6657
+ }
6658
+ /**
6659
+ * Lays out the tool calls of every assistant message in `rendered`.
6660
+ *
6661
+ * Calls drawn by a renderer that opted into grouping join the group that's
6662
+ * open; anything else that shows between them closes it: assistant text, a
6663
+ * user, reasoning or activity message, or a call drawn by any other renderer. A group belongs to
6664
+ * the message where it starts, so later messages whose calls joined it render
6665
+ * no tool calls themselves.
6666
+ *
6667
+ * @param rendered - The messages as the chat renders them, in order.
6668
+ * @param all - Every message, for finding tool results (a transform may hide them).
6669
+ * @param renderers - The registered renderers.
6670
+ */
6671
+ function getToolCallLayouts(rendered, all, renderers) {
6672
+ const results = getToolResults(all);
6673
+ const layouts = /* @__PURE__ */ new Map();
6674
+ let open;
6675
+ for (const message of rendered) {
6676
+ if (message.role === "tool") continue;
6677
+ if (message.role !== "assistant") {
6678
+ open = void 0;
6679
+ continue;
6680
+ }
6681
+ if (typeof message.content === "string" && message.content.trim()) open = void 0;
6682
+ const segments = [];
6683
+ layouts.set(message.id, segments);
6684
+ const toolCalls = /* @__PURE__ */ new Map();
6685
+ for (const toolCall of message.toolCalls ?? []) toolCalls.set(toolCall.id, toolCall);
6686
+ for (const toolCall of toolCalls.values()) {
6687
+ const entry = {
6688
+ toolCall,
6689
+ toolMessage: results.get(toolCall.id)
6690
+ };
6691
+ const name = toolCall.function.name;
6692
+ const named = renderers.named.has(name);
6693
+ const rendered = named || renderers.wildcard;
6694
+ const groups = named ? renderers.groupedNames.has(name) : renderers.wildcardGroups;
6695
+ if (!rendered || !groups) {
6696
+ if (rendered) open = void 0;
6697
+ segments.push({
6698
+ kind: "call",
6699
+ id: toolCall.id,
6700
+ call: entry,
6701
+ rendered
6702
+ });
6703
+ } else if (open) open.calls.push(entry);
6704
+ else {
6705
+ open = {
6706
+ kind: "group",
6707
+ id: toolCall.id,
6708
+ calls: [entry],
6709
+ trailing: false
6710
+ };
6711
+ segments.push(open);
6712
+ }
6713
+ }
6714
+ }
6715
+ if (open) open.trailing = true;
6716
+ const layout = /* @__PURE__ */ new Map();
6717
+ for (const [id, segments] of layouts) layout.set(id, {
6718
+ segments,
6719
+ signature: signatureOf(segments)
6720
+ });
6721
+ return layout;
6722
+ }
6723
+ /**
6724
+ * Finds the working phase of every reply in `rendered`: its messages up to the
6725
+ * last one that calls a tool. Text in there was narration; what follows is the
6726
+ * reply itself. Replies that called no tools are omitted, except the latest,
6727
+ * which may be about to.
6728
+ */
6729
+ function getTurnWork(rendered, layouts) {
6730
+ const turns = /* @__PURE__ */ new Map();
6731
+ let start = 0;
6732
+ while (start < rendered.length) {
6733
+ let end = start;
6734
+ while (end < rendered.length && rendered[end].role !== "user") end++;
6735
+ const key = start > 0 ? rendered[start - 1].id : "start";
6736
+ const visible = rendered.slice(start, end).filter((message) => message.role !== "tool");
6737
+ const isLatest = end === rendered.length;
6738
+ const calls = [];
6739
+ for (const message of visible) for (const segment of layouts.get(message.id)?.segments ?? []) if (segment.kind === "group") calls.push(...segment.calls);
6740
+ else if (segment.rendered) calls.push(segment.call);
6741
+ const shown = new Set(calls.map((entry) => entry.toolCall.id));
6742
+ let lastWork = -1;
6743
+ visible.forEach((message, index) => {
6744
+ if (message.role === "assistant" && message.toolCalls?.some((call) => shown.has(call.id))) lastWork = index;
6745
+ });
6746
+ const first = visible[0];
6747
+ if (first && (calls.length > 0 || isLatest)) {
6748
+ let lastAssistant = -1;
6749
+ visible.forEach((message, index) => {
6750
+ if (message.role === "assistant") lastAssistant = index;
6751
+ });
6752
+ const replyText = visible[lastAssistant]?.content;
6753
+ const endsWithWork = lastAssistant >= 0 && lastAssistant <= lastWork && typeof replyText === "string" && replyText.trim().length > 0;
6754
+ turns.set(key, {
6755
+ key,
6756
+ firstMessageId: first.id,
6757
+ workMessageIds: new Set(visible.slice(0, lastWork + 1).map((message) => message.id)),
6758
+ replyTextMessageId: endsWithWork ? visible[lastAssistant].id : void 0,
6759
+ calls,
6760
+ isLatest
6761
+ });
6762
+ }
6763
+ start = end + 1;
6764
+ }
6765
+ return turns;
6766
+ }
6767
+
6180
6768
  //#endregion
6181
6769
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
6182
- function CopilotChatToolCallsView({ message, messages = [] }) {
6770
+ const appearances = {
6771
+ grouped: "card",
6772
+ line: "line",
6773
+ stacked: "stack"
6774
+ };
6775
+ function CopilotChatToolCallsView({ message, messages = [], display: displayProp, group }) {
6183
6776
  const renderToolCall = useRenderToolCall();
6777
+ const rendererNames = useToolCallRendererNames();
6184
6778
  const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
6185
6779
  const subscribe = (0, react.useCallback)((onChange) => copilotkit.ɵsubscribeToLearningConfigured(onChange), [copilotkit]);
6186
6780
  const getSnapshot = () => copilotkit.ɵlearningConfigured;
6187
6781
  const learningConfigured = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
6782
+ const context = (0, react.useContext)(ToolCallDisplayContext);
6783
+ const display = displayProp ?? context?.display ?? "grouped";
6784
+ const viewLayout = context?.layouts.get(message.id);
6785
+ const layout = (0, react.useMemo)(() => {
6786
+ if (display === "separate") return void 0;
6787
+ return viewLayout ?? getToolCallLayouts([message], messages, rendererNames).get(message.id);
6788
+ }, [
6789
+ display,
6790
+ viewLayout,
6791
+ message,
6792
+ messages,
6793
+ rendererNames
6794
+ ]);
6188
6795
  if (!message.toolCalls || message.toolCalls.length === 0) return null;
6189
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: message.toolCalls.map((toolCall) => {
6796
+ const renderCall = (entry) => {
6190
6797
  const rendered = renderToolCall({
6191
- toolCall,
6192
- toolMessage: messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id)
6798
+ toolCall: entry.toolCall,
6799
+ toolMessage: entry.toolMessage
6193
6800
  });
6194
- if (rendered === null) return null;
6195
- if (!learningConfigured) return rendered;
6801
+ if (rendered === null || !learningConfigured) return rendered;
6196
6802
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6197
- "data-tool-call-id": toolCall.id,
6803
+ "data-tool-call-id": entry.toolCall.id,
6198
6804
  style: { display: "contents" },
6199
6805
  children: rendered
6200
- }, toolCall.id);
6806
+ });
6807
+ };
6808
+ if (!layout) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: message.toolCalls.map((toolCall) => {
6809
+ const toolMessage = messages.find((m) => m.role === "tool" && m.toolCallId === toolCall.id);
6810
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall({
6811
+ toolCall,
6812
+ toolMessage
6813
+ }) }, toolCall.id);
6814
+ }) });
6815
+ const workOpen = context?.workOpen.get(message.id);
6816
+ const groupActive = (trailing) => trailing && (context ? context.isRunning : true);
6817
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: layout.segments.map((segment) => {
6818
+ if (segment.kind === "call") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(segment.call) }, segment.id);
6819
+ if (display === "turn") {
6820
+ const failedIds = new Set(segment.calls.filter((call) => call.toolMessage?.error).map((call) => call.toolCall.id));
6821
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
6822
+ open: workOpen ?? true,
6823
+ className: "cpk:-mx-2",
6824
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6825
+ className: "cpk:px-2 cpk:py-1.5",
6826
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallGroupContext.Provider, {
6827
+ value: {
6828
+ appearance: "line",
6829
+ failedIds,
6830
+ active: groupActive(segment.trailing)
6831
+ },
6832
+ children: segment.calls.map((call) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderCall(call) }, call.toolCall.id))
6833
+ })
6834
+ })
6835
+ }, segment.id);
6836
+ }
6837
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.default.Fragment, { children: renderSlot(group, CopilotChatToolCallGroup, {
6838
+ calls: segment.calls,
6839
+ renderCall,
6840
+ appearance: appearances[display] ?? "card",
6841
+ isActive: groupActive(segment.trailing)
6842
+ }) }, segment.id);
6201
6843
  }) });
6202
6844
  }
6203
6845
 
6204
6846
  //#endregion
6205
6847
  //#region src/v2/components/chat/CopilotChatInspectorButton.tsx
6206
6848
  let shortcutsHidden = false;
6207
- const listeners = /* @__PURE__ */ new Set();
6849
+ const listeners$1 = /* @__PURE__ */ new Set();
6208
6850
  const subscribe = (listener) => {
6209
- listeners.add(listener);
6851
+ listeners$1.add(listener);
6210
6852
  return () => {
6211
- listeners.delete(listener);
6853
+ listeners$1.delete(listener);
6212
6854
  };
6213
6855
  };
6214
6856
  const getSnapshot = () => shortcutsHidden;
@@ -6218,7 +6860,7 @@ function useInspectorShortcutsHidden() {
6218
6860
  }
6219
6861
  function hideShortcuts() {
6220
6862
  shortcutsHidden = true;
6221
- listeners.forEach((listener) => listener());
6863
+ listeners$1.forEach((listener) => listener());
6222
6864
  }
6223
6865
  function InspectorKiteIcon({ className, ...props }) {
6224
6866
  const id = (0, react.useId)();
@@ -6484,9 +7126,321 @@ function CopilotChatInspectorButton({ title, className, onClick, onPointerDown,
6484
7126
  });
6485
7127
  }
6486
7128
 
7129
+ //#endregion
7130
+ //#region src/v2/components/ui/popover.tsx
7131
+ function Popover({ ...props }) {
7132
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Root, {
7133
+ "data-slot": "popover",
7134
+ ...props
7135
+ });
7136
+ }
7137
+ function PopoverAnchor({ ...props }) {
7138
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_radix_ui_react_popover.Anchor, {
7139
+ "data-slot": "popover-anchor",
7140
+ ...props
7141
+ });
7142
+ }
7143
+ /**
7144
+ * Popover content in a portal, so it floats over the page instead of taking
7145
+ * room in the layout, with the CopilotKit style scope on it.
7146
+ */
7147
+ 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, {
7148
+ ref,
7149
+ "data-copilotkit": true,
7150
+ "data-copilotkit-popover": true,
7151
+ "data-slot": "popover-content",
7152
+ align,
7153
+ sideOffset,
7154
+ 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),
7155
+ ...props
7156
+ }) }));
7157
+ PopoverContent.displayName = "PopoverContent";
7158
+
7159
+ //#endregion
7160
+ //#region src/v2/lib/message-feedback.ts
7161
+ /** Trajectory event name for a rating of one assistant message. */
7162
+ const MESSAGE_FEEDBACK_EVENT_NAME = "message.feedback";
7163
+ /** Longest comment kept, in characters; the rest is cut so the event stays small. */
7164
+ const MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH = 1e3;
7165
+ /** Longest excerpt of the rated response sent with the rating, in characters. */
7166
+ const RESPONSE_EXCERPT_LENGTH = 500;
7167
+ const ratings = /* @__PURE__ */ new Map();
7168
+ const listeners = /* @__PURE__ */ new Map();
7169
+ const threadKey = (agentId, threadId) => JSON.stringify([agentId, threadId]);
7170
+ const messageKey = (agentId, threadId, messageId) => JSON.stringify([
7171
+ agentId,
7172
+ threadId,
7173
+ messageId
7174
+ ]);
7175
+ /** Cuts `text` to `max` characters without splitting a surrogate pair. */
7176
+ const clip = (text, max) => {
7177
+ const characters = Array.from(text);
7178
+ return characters.length > max ? characters.slice(0, max).join("") : text;
7179
+ };
7180
+ /** The comment as kept and sent: trimmed and capped, or none. */
7181
+ const normalizeComment = (comment) => {
7182
+ if (typeof comment !== "string") return void 0;
7183
+ const text = clip(comment.trim(), MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH);
7184
+ return text === "" ? void 0 : text;
7185
+ };
7186
+ /** The rating this page gave one message, if any. */
7187
+ function getMessageFeedback(agentId, threadId, messageId) {
7188
+ return ratings.get(messageKey(agentId, threadId, messageId));
7189
+ }
7190
+ /** Calls `listener` whenever a rating in this Thread changes. */
7191
+ function subscribeToMessageFeedback(agentId, threadId, listener) {
7192
+ const key = threadKey(agentId, threadId);
7193
+ let set = listeners.get(key);
7194
+ if (set === void 0) {
7195
+ set = /* @__PURE__ */ new Set();
7196
+ listeners.set(key, set);
7197
+ }
7198
+ set.add(listener);
7199
+ return () => {
7200
+ set.delete(listener);
7201
+ if (set.size === 0) listeners.delete(key);
7202
+ };
7203
+ }
7204
+ /**
7205
+ * Sets (or, with `null`, clears) this page's rating of one message and sends
7206
+ * it to the active Trajectory. Every change is a new event; the newest event
7207
+ * for a `feedbackId` is the current one. Like any Trajectory event, a change
7208
+ * made while no Trajectory captures, for example during a reconnect, is not
7209
+ * sent; the page still shows it.
7210
+ */
7211
+ function setMessageFeedback(params) {
7212
+ const { copilotkit, agentId, threadId, message } = params;
7213
+ const comment = normalizeComment(params.feedback?.comment);
7214
+ const feedback = params.feedback && {
7215
+ rating: params.feedback.rating,
7216
+ ...comment ? { comment } : {}
7217
+ };
7218
+ const key = messageKey(agentId, threadId, message.id);
7219
+ if (feedback) ratings.set(key, feedback);
7220
+ else ratings.delete(key);
7221
+ for (const listener of listeners.get(threadKey(agentId, threadId)) ?? []) listener();
7222
+ const runId = copilotkit.getRunIdForMessage(agentId, threadId, message.id);
7223
+ copilotkit.emitTrajectoryEvent(MESSAGE_FEEDBACK_EVENT_NAME, {
7224
+ feedbackId: `${threadId}:${message.id}`,
7225
+ threadId,
7226
+ messageId: message.id,
7227
+ agentId,
7228
+ ...runId ? { runId } : {},
7229
+ rating: feedback?.rating ?? null,
7230
+ ...feedback?.comment ? { comment: feedback.comment } : {},
7231
+ ...copilotkit.ɵlearningCapturesAgentText ? { response: clip(message.content ?? "", RESPONSE_EXCERPT_LENGTH) } : {}
7232
+ });
7233
+ }
7234
+
7235
+ //#endregion
7236
+ //#region src/v2/components/chat/message-feedback.tsx
7237
+ /**
7238
+ * Set by the message view while Learning is on for this chat. Assistant
7239
+ * messages without it show thumbs only when the app passes its own handlers
7240
+ * or thumb slots.
7241
+ */
7242
+ const MessageFeedbackScopeContext = (0, react.createContext)(null);
7243
+ const capturedCores = /* @__PURE__ */ new WeakSet();
7244
+ const rememberCapture = (copilotkit) => {
7245
+ if (capturedCores.has(copilotkit)) return;
7246
+ capturedCores.add(copilotkit);
7247
+ const unsubscribe = copilotkit.ɵsubscribeToLearningConfigured(() => {
7248
+ if (copilotkit.ɵlearningConfigured) return;
7249
+ capturedCores.delete(copilotkit);
7250
+ unsubscribe();
7251
+ });
7252
+ };
7253
+ /**
7254
+ * Whether the thumbs record feedback for Learning. True once a Trajectory of
7255
+ * this Core has started and as long as Learning stays configured, also while
7256
+ * capture reconnects or is stopped, so the buttons do not flicker. Ratings
7257
+ * given meanwhile still show but, like any Trajectory event, are not
7258
+ * sent. A capture that never starts (for example without a license) leaves it
7259
+ * false.
7260
+ */
7261
+ function useLearningFeedbackEnabled(copilotkit) {
7262
+ const configured = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => copilotkit.ɵsubscribeToLearningConfigured(onChange), [copilotkit]), () => copilotkit.ɵlearningConfigured, () => false);
7263
+ const capturing = (0, react.useSyncExternalStore)((0, react.useCallback)((onChange) => copilotkit.subscribe({ onTrajectoryChanged: onChange }).unsubscribe, [copilotkit]), () => copilotkit.trajectoryId !== null, () => false);
7264
+ (0, react.useEffect)(() => {
7265
+ if (capturing) rememberCapture(copilotkit);
7266
+ }, [capturing, copilotkit]);
7267
+ return configured && (capturing || capturedCores.has(copilotkit));
7268
+ }
7269
+ const noSubscription = () => () => {};
7270
+ /** Feedback controls for `message`, or `null` when this chat collects none. */
7271
+ function useMessageFeedback(message) {
7272
+ const scope = (0, react.useContext)(MessageFeedbackScopeContext);
7273
+ const subscribe = (0, react.useCallback)((onChange) => scope ? subscribeToMessageFeedback(scope.agentId, scope.threadId, onChange) : noSubscription(), [scope]);
7274
+ const getSnapshot = () => scope ? getMessageFeedback(scope.agentId, scope.threadId, message.id) : void 0;
7275
+ const feedback = (0, react.useSyncExternalStore)(subscribe, getSnapshot, getSnapshot);
7276
+ if (!scope) return null;
7277
+ const save = (next) => setMessageFeedback({
7278
+ ...scope,
7279
+ message,
7280
+ feedback: next
7281
+ });
7282
+ return {
7283
+ feedback,
7284
+ rate: (rating) => {
7285
+ if (feedback?.rating !== rating) save({ rating });
7286
+ },
7287
+ clear: () => {
7288
+ if (feedback) save(null);
7289
+ },
7290
+ comment: (comment) => {
7291
+ if (feedback) save({
7292
+ rating: feedback.rating,
7293
+ comment
7294
+ });
7295
+ }
7296
+ };
7297
+ }
7298
+ /**
7299
+ * How long the pointer may be away from the rating and its comment before the
7300
+ * comment closes: enough to cross the gap between them, short enough that
7301
+ * passing over a rating does not leave it open.
7302
+ */
7303
+ const FEEDBACK_COMMENT_CLOSE_DELAY_MS = 150;
7304
+ /**
7305
+ * Drives the comment popover of a rated message.
7306
+ *
7307
+ * It opens when a rating is given and whenever the pointer or keyboard focus
7308
+ * reaches the chosen thumb, with the saved comment in the field. It closes by
7309
+ * itself shortly after the pointer leaves the thumb and the popover, unless
7310
+ * focus is in the field: then it stays until focus moves away, Enter, or
7311
+ * Escape. Closing saves a changed comment; Escape, a click on a thumb, and
7312
+ * removing the rating discard it.
7313
+ */
7314
+ function useFeedbackComment(controls) {
7315
+ const [open, setOpen] = (0, react.useState)(false);
7316
+ const [draft, setDraft] = (0, react.useState)("");
7317
+ const [focusRequest, setFocusRequest] = (0, react.useState)(0);
7318
+ const inputRef = (0, react.useRef)(null);
7319
+ const timer = (0, react.useRef)(void 0);
7320
+ const editing = (0, react.useRef)(false);
7321
+ const focusWaiting = (0, react.useRef)(false);
7322
+ const pointerOver = (0, react.useRef)(false);
7323
+ const returning = (0, react.useRef)(false);
7324
+ const latest = (0, react.useRef)({
7325
+ controls,
7326
+ draft,
7327
+ open
7328
+ });
7329
+ latest.current = {
7330
+ ...latest.current,
7331
+ controls,
7332
+ draft,
7333
+ open
7334
+ };
7335
+ const takeFocusRequest = (0, react.useCallback)(() => {
7336
+ const waiting = focusWaiting.current;
7337
+ focusWaiting.current = false;
7338
+ return waiting;
7339
+ }, []);
7340
+ const cancelClose = (0, react.useCallback)(() => {
7341
+ if (timer.current === void 0) return;
7342
+ clearTimeout(timer.current);
7343
+ timer.current = void 0;
7344
+ }, []);
7345
+ (0, react.useEffect)(() => cancelClose, [cancelClose]);
7346
+ const savedComment = (0, react.useCallback)(() => latest.current.controls?.feedback?.comment ?? "", []);
7347
+ (0, react.useEffect)(() => () => {
7348
+ const { open: wasOpen, controls: current, draft: text } = latest.current;
7349
+ if (wasOpen && current?.feedback && text.trim() !== savedComment()) current.comment(text);
7350
+ }, []);
7351
+ const close = (0, react.useCallback)(({ save = true } = {}) => {
7352
+ cancelClose();
7353
+ focusWaiting.current = false;
7354
+ pointerOver.current = false;
7355
+ if (!latest.current.open) return;
7356
+ const current = latest.current.controls;
7357
+ if (save && current?.feedback && latest.current.draft.trim() !== savedComment()) current.comment(latest.current.draft);
7358
+ editing.current = false;
7359
+ latest.current = {
7360
+ ...latest.current,
7361
+ open: false
7362
+ };
7363
+ setOpen(false);
7364
+ }, [cancelClose, savedComment]);
7365
+ const show = ({ comment, focus }) => {
7366
+ cancelClose();
7367
+ latest.current = {
7368
+ ...latest.current,
7369
+ open: true,
7370
+ draft: comment
7371
+ };
7372
+ setDraft(comment);
7373
+ if (focus) {
7374
+ focusWaiting.current = true;
7375
+ setFocusRequest((request) => request + 1);
7376
+ }
7377
+ setOpen(true);
7378
+ };
7379
+ return {
7380
+ open,
7381
+ draft,
7382
+ setDraft,
7383
+ focusRequest,
7384
+ takeFocusRequest,
7385
+ inputRef,
7386
+ show,
7387
+ enter: (via = "focus") => {
7388
+ cancelClose();
7389
+ if (via === "pointer") pointerOver.current = true;
7390
+ if (returning.current) return;
7391
+ if (!latest.current.open && latest.current.controls?.feedback) show({
7392
+ comment: savedComment(),
7393
+ focus: false
7394
+ });
7395
+ },
7396
+ leave: (via = "focus") => {
7397
+ if (via === "pointer") pointerOver.current = false;
7398
+ else if (pointerOver.current) return;
7399
+ cancelClose();
7400
+ timer.current = setTimeout(() => {
7401
+ timer.current = void 0;
7402
+ if (!editing.current) close();
7403
+ }, FEEDBACK_COMMENT_CLOSE_DELAY_MS);
7404
+ },
7405
+ setEditing: (value) => {
7406
+ editing.current = value;
7407
+ },
7408
+ close,
7409
+ returnFocus: (target) => {
7410
+ returning.current = true;
7411
+ try {
7412
+ target?.focus();
7413
+ } finally {
7414
+ returning.current = false;
7415
+ }
7416
+ }
7417
+ };
7418
+ }
7419
+
6487
7420
  //#endregion
6488
7421
  //#region src/v2/components/chat/CopilotChatAssistantMessage.tsx
6489
7422
  /**
7423
+ * In the `"turn"` tool-call display, text in a reply's working phase was
7424
+ * narration: it fades to muted once a call follows it, and folds away with the
7425
+ * rest of the work when the reply is done. The wrapper is there for every
7426
+ * message in that display, so text that turns into narration keeps its node
7427
+ * (and its place) rather than remounting.
7428
+ */
7429
+ function WorkText({ messageId, children }) {
7430
+ const context = (0, react.useContext)(ToolCallDisplayContext);
7431
+ if (context?.display !== "turn") return children;
7432
+ const open = context.workTextOpen.get(messageId);
7433
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
7434
+ open: open ?? true,
7435
+ className: "cpk:-mx-2",
7436
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7437
+ className: "cpk:px-2",
7438
+ "data-work-text": open === void 0 ? "reply" : "narration",
7439
+ children
7440
+ })
7441
+ });
7442
+ }
7443
+ /**
6490
7444
  * Binds the Inspector action to the active chat without making the full
6491
7445
  * assistant message subscribe to chat configuration changes.
6492
7446
  */
@@ -6501,31 +7455,216 @@ function BoundInspectorButton({ inspectorButton, messageId, openInspector, ...pr
6501
7455
  })
6502
7456
  });
6503
7457
  }
6504
- 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 }) {
7458
+ /** Whether focus came from the keyboard; a pointer click also focuses buttons. */
7459
+ function isFocusVisible(element) {
7460
+ try {
7461
+ return element.matches(":focus-visible");
7462
+ } catch {
7463
+ return false;
7464
+ }
7465
+ }
7466
+ const HIDDEN_TOO = {
7467
+ checkVisibilityCSS: true,
7468
+ visibilityProperty: true
7469
+ };
7470
+ const FOCUSABLE = [
7471
+ "a[href]",
7472
+ "button:not([disabled])",
7473
+ "input:not([disabled])",
7474
+ "select:not([disabled])",
7475
+ "textarea:not([disabled])",
7476
+ "[tabindex]"
7477
+ ].map((selector) => `${selector}:not([tabindex="-1"]):not([data-radix-focus-guard])`).join(", ");
7478
+ /** Approximately the element Tab reaches after `element`, outside `skip`. */
7479
+ function nextFocusable(element, skip) {
7480
+ const all = Array.from(element.ownerDocument.querySelectorAll(FOCUSABLE)).filter((candidate) => candidate === element || !skip?.contains(candidate) && !candidate.closest("[hidden], [inert]"));
7481
+ const index = all.indexOf(element);
7482
+ return index === -1 ? null : all[index + 1] ?? null;
7483
+ }
7484
+ /**
7485
+ * The comment popover of a rated message, pointing at the chosen thumb. Kept
7486
+ * apart so only it, not the whole message, follows chat configuration.
7487
+ *
7488
+ * The popover lives in a portal, so it keeps keyboard order by hand: Shift+Tab
7489
+ * from its start returns to the thumb, Tab from its end moves on to the
7490
+ * control after the thumb, and Enter or Escape inside it returns to the thumb.
7491
+ */
7492
+ function FeedbackCommentPopover({ feedbackComment, comment, savedComment, rating, anchor, messageId }) {
7493
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
7494
+ const contentRef = (0, react.useRef)(null);
7495
+ const focusInside = () => contentRef.current?.contains(document.activeElement) ?? false;
7496
+ const { focusRequest, takeFocusRequest, inputRef } = comment;
7497
+ const focusField = (0, react.useCallback)(() => {
7498
+ if (inputRef.current && takeFocusRequest()) inputRef.current.focus();
7499
+ }, [inputRef, takeFocusRequest]);
7500
+ (0, react.useEffect)(() => focusField(), [focusRequest, focusField]);
7501
+ /** Whether `target` is a thumb of this message, whose click decides itself. */
7502
+ const isOwnThumb = (target) => {
7503
+ return (target instanceof Element ? target.closest("[data-copilotkit-rating]") : null)?.closest("[data-message-id]")?.getAttribute("data-message-id") === messageId;
7504
+ };
7505
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Popover, {
7506
+ open: comment.open,
7507
+ onOpenChange: (open) => {
7508
+ if (!open) comment.close();
7509
+ },
7510
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverAnchor, { virtualRef: anchor }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PopoverContent, {
7511
+ ref: contentRef,
7512
+ side: "bottom",
7513
+ collisionPadding: 8,
7514
+ hideWhenDetached: true,
7515
+ "aria-label": labels.assistantMessageFeedbackCommentPlaceholder,
7516
+ "data-copilotkit-ignore": true,
7517
+ onOpenAutoFocus: (event) => {
7518
+ event.preventDefault();
7519
+ focusField();
7520
+ },
7521
+ onCloseAutoFocus: (event) => event.preventDefault(),
7522
+ onInteractOutside: (event) => {
7523
+ if (isOwnThumb(event.target)) event.preventDefault();
7524
+ },
7525
+ onEscapeKeyDown: (event) => {
7526
+ const returnFocus = focusInside();
7527
+ if (returnFocus) event.stopPropagation();
7528
+ comment.close({ save: false });
7529
+ if (returnFocus) comment.returnFocus(anchor.current);
7530
+ },
7531
+ onKeyDown: (event) => {
7532
+ if (event.key !== "Tab") return;
7533
+ const focusables = Array.from(contentRef.current?.querySelectorAll(FOCUSABLE) ?? []);
7534
+ if (!(event.shiftKey ? document.activeElement === focusables[0] : document.activeElement === focusables[focusables.length - 1]) || !anchor.current?.isConnected) return;
7535
+ event.preventDefault();
7536
+ const target = event.shiftKey ? anchor.current : nextFocusable(anchor.current, contentRef.current) ?? anchor.current;
7537
+ comment.close();
7538
+ if (target === anchor.current) comment.returnFocus(target);
7539
+ else target.focus();
7540
+ },
7541
+ onPointerEnter: () => comment.enter("pointer"),
7542
+ onPointerLeave: () => comment.leave("pointer"),
7543
+ className: "cpk:z-[1300]",
7544
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react.Fragment, { children: renderSlot(feedbackComment, CopilotChatAssistantMessage.FeedbackComment, {
7545
+ value: comment.draft,
7546
+ savedComment,
7547
+ inputRef: comment.inputRef,
7548
+ onChange: comment.setDraft,
7549
+ onSubmit: () => {
7550
+ comment.close();
7551
+ comment.returnFocus(anchor.current);
7552
+ },
7553
+ onEditingChange: comment.setEditing,
7554
+ onLeave: (next) => {
7555
+ if (next && (contentRef.current?.contains(next) || isOwnThumb(next))) return;
7556
+ comment.leave();
7557
+ }
7558
+ }) }, rating)
7559
+ })]
7560
+ });
7561
+ }
7562
+ 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 }) {
6505
7563
  useKatexStyles();
6506
7564
  const { isInspectorEnabled, openInspector } = useCopilotKitInspector();
6507
7565
  const shortcutsHidden = useInspectorShortcutsHidden();
6508
7566
  const showInspectorShortcut = isInspectorEnabled && !shortcutsHidden;
7567
+ const feedback = useMessageFeedback(message);
7568
+ const comment = useFeedbackComment(feedback);
7569
+ const rating = feedback?.feedback?.rating;
7570
+ const root = (0, react.useRef)(null);
7571
+ const commentAnchor = (0, react.useRef)(null);
7572
+ const commentRating = (0, react.useRef)(rating);
7573
+ (0, react.useLayoutEffect)(() => {
7574
+ if (comment.open && !commentAnchor.current?.isConnected && rating) commentAnchor.current = root.current?.querySelector(`[data-copilotkit-rating="${rating}"]`) ?? root.current?.querySelector("button[aria-pressed=\"true\"]") ?? null;
7575
+ });
7576
+ /**
7577
+ * Thumbs are toggles: a new rating is saved and opens its comment, and
7578
+ * clicking the chosen thumb again removes the rating. A keyboard click
7579
+ * (`detail` 0) moves focus into the comment, as there is no pointer.
7580
+ * Custom thumb slots may call `onClick` without an event.
7581
+ *
7582
+ * @returns Whether the app's handler should run: true for a new rating and
7583
+ * whenever this chat does not record ratings, false when one is removed.
7584
+ */
7585
+ const rate = (value, event) => {
7586
+ if (!feedback) return true;
7587
+ if (rating === value) {
7588
+ comment.close({ save: false });
7589
+ feedback.clear();
7590
+ return false;
7591
+ }
7592
+ feedback.rate(value);
7593
+ commentRating.current = value;
7594
+ commentAnchor.current = event?.currentTarget ?? null;
7595
+ comment.show({
7596
+ comment: "",
7597
+ focus: event?.detail === 0
7598
+ });
7599
+ return true;
7600
+ };
7601
+ /** Props that let the chosen thumb open and point at its comment. */
7602
+ const thumbFeedbackProps = (value) => {
7603
+ if (!feedback) return {};
7604
+ const thumb = { "data-copilotkit-rating": value };
7605
+ if (rating !== value) return {
7606
+ ...thumb,
7607
+ "aria-pressed": false
7608
+ };
7609
+ const enter = (target, via) => {
7610
+ if (!comment.open) commentRating.current = value;
7611
+ commentAnchor.current = target;
7612
+ comment.enter(via);
7613
+ };
7614
+ return {
7615
+ ...thumb,
7616
+ "aria-pressed": true,
7617
+ "aria-haspopup": "dialog",
7618
+ "aria-expanded": comment.open,
7619
+ onPointerEnter: (event) => {
7620
+ if (event.pointerType !== "touch") enter(event.currentTarget, "pointer");
7621
+ },
7622
+ onPointerLeave: (event) => {
7623
+ if (event.pointerType !== "touch") comment.leave("pointer");
7624
+ },
7625
+ onFocus: (event) => {
7626
+ if (isFocusVisible(event.currentTarget)) enter(event.currentTarget, "focus");
7627
+ },
7628
+ onBlur: () => comment.leave(),
7629
+ onKeyDown: (event) => {
7630
+ const input = comment.inputRef.current;
7631
+ if (event.key === "Tab" && !event.shiftKey && comment.open && input && input.checkVisibility?.(HIDDEN_TOO) !== false) {
7632
+ event.preventDefault();
7633
+ input.focus();
7634
+ }
7635
+ }
7636
+ };
7637
+ };
6509
7638
  const boundMarkdownRenderer = renderSlot(markdownRenderer, CopilotChatAssistantMessage.MarkdownRenderer, { content: message.content || "" });
6510
7639
  const boundCopyButton = renderSlot(copyButton, CopilotChatAssistantMessage.CopyButton, { onClick: async () => {
6511
7640
  if (message.content) return await (0, _copilotkit_shared.copyToClipboard)(message.content);
6512
7641
  return false;
6513
7642
  } });
6514
- const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp ? () => onThumbsUp(message) : void 0 });
7643
+ const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, {
7644
+ onClick: onThumbsUp || feedback ? (event) => {
7645
+ if (rate("up", event)) onThumbsUp?.(message);
7646
+ } : void 0,
7647
+ ...thumbFeedbackProps("up")
7648
+ });
6515
7649
  const boundInspectorButton = showInspectorShortcut ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BoundInspectorButton, {
6516
7650
  inspectorButton,
6517
7651
  messageId: message.id,
6518
7652
  openInspector
6519
7653
  }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
6520
- const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown ? () => onThumbsDown(message) : void 0 });
7654
+ const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, {
7655
+ onClick: onThumbsDown || feedback ? (event) => {
7656
+ if (rate("down", event)) onThumbsDown?.(message);
7657
+ } : void 0,
7658
+ ...thumbFeedbackProps("down")
7659
+ });
6521
7660
  const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud ? () => onReadAloud(message) : void 0 });
6522
7661
  const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate ? () => onRegenerate(message) : void 0 });
6523
7662
  const boundToolbar = renderSlot(toolbar, CopilotChatAssistantMessage.Toolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6524
7663
  className: "cpk:flex cpk:w-full cpk:items-center cpk:gap-1",
6525
7664
  children: [
6526
7665
  boundCopyButton,
6527
- (onThumbsUp || thumbsUpButton) && boundThumbsUpButton,
6528
- (onThumbsDown || thumbsDownButton) && boundThumbsDownButton,
7666
+ (onThumbsUp || thumbsUpButton || feedback) && boundThumbsUpButton,
7667
+ (onThumbsDown || thumbsDownButton || feedback) && boundThumbsDownButton,
6529
7668
  (onReadAloud || readAloudButton) && boundReadAloudButton,
6530
7669
  (onRegenerate || regenerateButton) && boundRegenerateButton,
6531
7670
  additionalToolbarItems,
@@ -6539,11 +7678,31 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
6539
7678
  const hasContent = !!(message.content && message.content.trim().length > 0);
6540
7679
  const isLatestAssistantMessage = message.role === "assistant" && (isLatest ?? messages?.[messages.length - 1]?.id === message.id);
6541
7680
  const shouldShowToolbar = toolbarVisible && (hasContent || showInspectorShortcut) && !(isRunning && isLatestAssistantMessage);
6542
- if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7681
+ const { open: commentOpen, close: closeComment } = comment;
7682
+ (0, react.useEffect)(() => {
7683
+ if (!commentOpen) return;
7684
+ if (rating !== commentRating.current) closeComment({ save: false });
7685
+ else if (!shouldShowToolbar) closeComment();
7686
+ }, [
7687
+ commentOpen,
7688
+ shouldShowToolbar,
7689
+ rating,
7690
+ closeComment
7691
+ ]);
7692
+ const feedbackComment = feedback?.feedback && shouldShowToolbar && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FeedbackCommentPopover, {
7693
+ feedbackComment: feedbackCommentSlot,
7694
+ comment,
7695
+ savedComment: feedback.feedback.comment ?? "",
7696
+ rating: feedback.feedback.rating,
7697
+ anchor: commentAnchor,
7698
+ messageId: message.id
7699
+ });
7700
+ if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7701
+ ref: root,
6543
7702
  "data-copilotkit": true,
6544
7703
  "data-message-id": message.id,
6545
7704
  style: { display: "contents" },
6546
- children: children({
7705
+ children: [children({
6547
7706
  markdownRenderer: boundMarkdownRenderer,
6548
7707
  toolbar: boundToolbar,
6549
7708
  toolCallsView: boundToolCallsView,
@@ -6562,21 +7721,26 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
6562
7721
  onRegenerate,
6563
7722
  additionalToolbarItems,
6564
7723
  toolbarVisible: shouldShowToolbar
6565
- })
7724
+ }), feedbackComment]
6566
7725
  });
6567
7726
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7727
+ ref: root,
6568
7728
  "data-copilotkit": true,
6569
7729
  "data-testid": "copilot-assistant-message",
6570
7730
  className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitAssistantMessage", className),
6571
7731
  ...props,
6572
7732
  "data-message-id": message.id,
6573
7733
  children: [
6574
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6575
- className: "cpk:prose cpk:max-w-full cpk:break-words cpk:dark:prose-invert",
6576
- children: boundMarkdownRenderer
7734
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(WorkText, {
7735
+ messageId: message.id,
7736
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7737
+ className: "cpk:prose cpk:max-w-full cpk:break-words cpk:dark:prose-invert",
7738
+ children: boundMarkdownRenderer
7739
+ })
6577
7740
  }),
6578
7741
  boundToolCallsView,
6579
- shouldShowToolbar && boundToolbar
7742
+ shouldShowToolbar && boundToolbar,
7743
+ feedbackComment
6580
7744
  ]
6581
7745
  });
6582
7746
  }
@@ -6592,20 +7756,27 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
6592
7756
  ...props
6593
7757
  });
6594
7758
  const ToolbarButton = _CopilotChatAssistantMessage.ToolbarButton = ({ title, tooltip, tooltipClassName, children, ...props }) => {
6595
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
6596
- asChild: true,
6597
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
6598
- type: "button",
6599
- variant: "assistantMessageToolbarButton",
6600
- "aria-label": title,
6601
- ...props,
6602
- children
6603
- })
6604
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
6605
- side: "bottom",
6606
- className: tooltipClassName,
6607
- children: tooltip ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
6608
- })] });
7759
+ const [tooltipOpen, setTooltipOpen] = (0, react.useState)(false);
7760
+ const popupOpen = props["aria-expanded"] === true;
7761
+ if (popupOpen && tooltipOpen) setTooltipOpen(false);
7762
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, {
7763
+ open: tooltipOpen && !popupOpen,
7764
+ onOpenChange: (open) => setTooltipOpen(open && !popupOpen),
7765
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
7766
+ asChild: true,
7767
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7768
+ type: "button",
7769
+ variant: "assistantMessageToolbarButton",
7770
+ "aria-label": title,
7771
+ ...props,
7772
+ children
7773
+ })
7774
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
7775
+ side: "bottom",
7776
+ className: tooltipClassName,
7777
+ children: tooltip ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: title })
7778
+ })]
7779
+ });
6609
7780
  };
6610
7781
  _CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
6611
7782
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
@@ -6644,7 +7815,10 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
6644
7815
  "data-testid": "copilot-thumbs-up-button",
6645
7816
  title: title || labels.assistantMessageToolbarThumbsUpLabel,
6646
7817
  ...props,
6647
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, { className: "cpk:size-[18px]" })
7818
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsUp, {
7819
+ className: "cpk:size-[18px]",
7820
+ fill: props["aria-pressed"] === true ? "currentColor" : "none"
7821
+ })
6648
7822
  });
6649
7823
  };
6650
7824
  _CopilotChatAssistantMessage.ThumbsDownButton = ({ title, ...props }) => {
@@ -6653,7 +7827,45 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, isLatest, o
6653
7827
  "data-testid": "copilot-thumbs-down-button",
6654
7828
  title: title || labels.assistantMessageToolbarThumbsDownLabel,
6655
7829
  ...props,
6656
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, { className: "cpk:size-[18px]" })
7830
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ThumbsDown, {
7831
+ className: "cpk:size-[18px]",
7832
+ fill: props["aria-pressed"] === true ? "currentColor" : "none"
7833
+ })
7834
+ });
7835
+ };
7836
+ _CopilotChatAssistantMessage.FeedbackComment = ({ value, savedComment, inputRef, onChange, onSubmit, onEditingChange, onLeave, className }) => {
7837
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
7838
+ const changed = value.trim() !== savedComment;
7839
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", {
7840
+ "data-testid": "copilot-feedback-comment",
7841
+ 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),
7842
+ onSubmit: (event) => {
7843
+ event.preventDefault();
7844
+ onSubmit();
7845
+ },
7846
+ onFocus: () => onEditingChange?.(true),
7847
+ onBlur: (event) => {
7848
+ const next = event.relatedTarget instanceof Element ? event.relatedTarget : null;
7849
+ if (event.currentTarget.contains(next)) return;
7850
+ if (typeof document !== "undefined" && !document.hasFocus()) return;
7851
+ onEditingChange?.(false);
7852
+ onLeave?.(next);
7853
+ },
7854
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
7855
+ ref: inputRef,
7856
+ value,
7857
+ maxLength: MESSAGE_FEEDBACK_MAX_COMMENT_LENGTH,
7858
+ onChange: (event) => onChange(event.target.value),
7859
+ placeholder: labels.assistantMessageFeedbackCommentPlaceholder,
7860
+ "aria-label": labels.assistantMessageFeedbackCommentPlaceholder,
7861
+ 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"
7862
+ }), changed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
7863
+ type: "submit",
7864
+ variant: "assistantMessageToolbarButton",
7865
+ "aria-label": labels.assistantMessageFeedbackCommentSubmitLabel,
7866
+ title: labels.assistantMessageFeedbackCommentSubmitLabel,
7867
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ArrowUp, { className: "cpk:size-[16px]" })
7868
+ })]
6657
7869
  });
6658
7870
  };
6659
7871
  _CopilotChatAssistantMessage.ReadAloudButton = ({ title, ...props }) => {
@@ -6681,6 +7893,7 @@ CopilotChatAssistantMessage.CopyButton.displayName = "CopilotChatAssistantMessag
6681
7893
  CopilotChatAssistantMessage.InspectorButton.displayName = "CopilotChatAssistantMessage.InspectorButton";
6682
7894
  CopilotChatAssistantMessage.ThumbsUpButton.displayName = "CopilotChatAssistantMessage.ThumbsUpButton";
6683
7895
  CopilotChatAssistantMessage.ThumbsDownButton.displayName = "CopilotChatAssistantMessage.ThumbsDownButton";
7896
+ CopilotChatAssistantMessage.FeedbackComment.displayName = "CopilotChatAssistantMessage.FeedbackComment";
6684
7897
  CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantMessage.ReadAloudButton";
6685
7898
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
6686
7899
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
@@ -6986,6 +8199,7 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
6986
8199
  });
6987
8200
  };
6988
8201
  _CopilotChatUserMessage.BranchNavigation = ({ className, currentBranch = 0, numberOfBranches = 1, onSwitchToBranch, message, ...props }) => {
8202
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
6989
8203
  if (!numberOfBranches || numberOfBranches <= 1 || !onSwitchToBranch) return null;
6990
8204
  const canGoPrev = currentBranch > 0;
6991
8205
  const canGoNext = currentBranch < numberOfBranches - 1;
@@ -7004,6 +8218,7 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
7004
8218
  }),
7005
8219
  disabled: !canGoPrev,
7006
8220
  className: "cpk:h-6 cpk:w-6 cpk:p-0",
8221
+ "aria-label": labels.userMessageToolbarPreviousBranchLabel,
7007
8222
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronLeft, { className: "cpk:size-[20px]" })
7008
8223
  }),
7009
8224
  /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
@@ -7024,6 +8239,7 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
7024
8239
  }),
7025
8240
  disabled: !canGoNext,
7026
8241
  className: "cpk:h-6 cpk:w-6 cpk:p-0",
8242
+ "aria-label": labels.userMessageToolbarNextBranchLabel,
7027
8243
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, { className: "cpk:size-[20px]" })
7028
8244
  })
7029
8245
  ]
@@ -7044,7 +8260,7 @@ var CopilotChatUserMessage_default = CopilotChatUserMessage;
7044
8260
  /**
7045
8261
  * Formats an elapsed duration (in seconds) to a human-readable string.
7046
8262
  */
7047
- function formatDuration(seconds) {
8263
+ function formatDuration$1(seconds) {
7048
8264
  if (seconds < 1) return "a few seconds";
7049
8265
  if (seconds < 60) return `${Math.round(seconds)} seconds`;
7050
8266
  const mins = Math.floor(seconds / 60);
@@ -7082,7 +8298,7 @@ function CopilotChatReasoningMessage({ message, messages, isRunning, isLatest: i
7082
8298
  userToggledRef.current = true;
7083
8299
  setIsOpen((prev) => !prev);
7084
8300
  } : void 0;
7085
- const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration(elapsed)}`;
8301
+ const label = isStreaming ? "Thinking…" : `Thought for ${formatDuration$1(elapsed)}`;
7086
8302
  const boundHeader = renderSlot(header, CopilotChatReasoningMessage.Header, {
7087
8303
  isOpen,
7088
8304
  label,
@@ -7304,6 +8520,71 @@ const ScrollElementContext = react.default.createContext(null);
7304
8520
  */
7305
8521
  const ScrollPinnedContext = react.default.createContext(false);
7306
8522
 
8523
+ //#endregion
8524
+ //#region src/v2/components/chat/CopilotChatWorkSummary.tsx
8525
+ /** "45s", "1m 5s", "2m". Used for the live timer and the final duration. */
8526
+ function formatDuration(ms) {
8527
+ const seconds = Math.max(0, Math.floor(ms / 1e3));
8528
+ if (seconds < 60) return `${seconds}s`;
8529
+ const minutes = Math.floor(seconds / 60);
8530
+ const rest = seconds % 60;
8531
+ return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
8532
+ }
8533
+ /** Re-renders every second while `active`, returning the current time. */
8534
+ function useNow(active) {
8535
+ const [now, setNow] = (0, react.useState)(() => Date.now());
8536
+ (0, react.useEffect)(() => {
8537
+ if (!active) return;
8538
+ setNow(Date.now());
8539
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
8540
+ return () => clearInterval(timer);
8541
+ }, [active]);
8542
+ return now;
8543
+ }
8544
+ /**
8545
+ * The line at the top of a reply in the `"turn"` tool-call display. While the
8546
+ * agent works it reads "Working 4s"; once the reply is done it reads "Worked
8547
+ * for 9s · 7 tools" and toggles the work it folded away.
8548
+ */
8549
+ function CopilotChatWorkSummary({ isWorking, isOpen, calls, elapsedMs, workingSince, className, ...props }) {
8550
+ const now = useNow(isWorking);
8551
+ const failed = calls.filter((call) => call.toolMessage?.error).length;
8552
+ const liveMs = elapsedMs !== void 0 && workingSince !== void 0 ? elapsedMs + Math.max(0, now - workingSince) : void 0;
8553
+ const duration = elapsedMs !== void 0 ? formatDuration(Math.max(1e3, elapsedMs)) : void 0;
8554
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
8555
+ type: "button",
8556
+ "data-copilotkit": true,
8557
+ "data-testid": "copilot-work-summary",
8558
+ "data-state": isWorking ? "working" : "done",
8559
+ "aria-expanded": isOpen,
8560
+ 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),
8561
+ ...props,
8562
+ children: [isWorking ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8563
+ className: "cpk:font-medium cpk-shimmer",
8564
+ children: "Working"
8565
+ }), liveMs !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8566
+ className: "cpk:text-xs cpk:tabular-nums cpk:opacity-70",
8567
+ children: formatDuration(liveMs)
8568
+ })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
8569
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8570
+ className: "cpk:font-medium",
8571
+ children: duration ? `Worked for ${duration}` : `Used ${pluralize(calls.length, "tool")}`
8572
+ }),
8573
+ duration && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8574
+ className: "cpk:text-xs cpk:opacity-70",
8575
+ children: pluralize(calls.length, "tool")
8576
+ }),
8577
+ failed > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8578
+ className: "cpk:text-destructive",
8579
+ children: [failed, " failed"]
8580
+ })
8581
+ ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronRight, {
8582
+ "aria-hidden": "true",
8583
+ className: (0, tailwind_merge.twMerge)("cpk:size-3.5 cpk:shrink-0 cpk:transition-transform cpk:duration-200", isOpen && "cpk:rotate-90")
8584
+ })]
8585
+ });
8586
+ }
8587
+
7307
8588
  //#endregion
7308
8589
  //#region src/v2/components/intelligence-indicator/IntelligenceIndicatorView.tsx
7309
8590
  /**
@@ -7790,7 +9071,7 @@ function messageGroup(group) {
7790
9071
  }
7791
9072
  const GROUP_ROW_KEY_PREFIX = "copilotkit-group:";
7792
9073
  const VIRTUALIZE_THRESHOLD = 50;
7793
- function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, isRunning = false, transformMessages, groupMessages, children, className, ...props }) {
9074
+ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, intelligenceIndicator, workSummary, isRunning = false, transformMessages, groupMessages, messageFeedback = true, toolCallDisplay = "grouped", children, className, ...props }) {
7794
9075
  const isPinnedToBottom = (0, react.useContext)(ScrollPinnedContext);
7795
9076
  const renderCustomMessage = useRenderCustomMessages();
7796
9077
  const { renderActivityMessage } = useRenderActivityMessage();
@@ -7831,6 +9112,100 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7831
9112
  transformedMessages
7832
9113
  ]);
7833
9114
  const latestRenderedId = renderedMessages[renderedMessages.length - 1]?.id;
9115
+ const rendererNames = useToolCallRendererNames();
9116
+ const toolCallLayouts = (0, react.useMemo)(() => toolCallDisplay === "separate" ? /* @__PURE__ */ new Map() : getToolCallLayouts(renderedMessages, messages, rendererNames), [
9117
+ toolCallDisplay,
9118
+ renderedMessages,
9119
+ messages,
9120
+ rendererNames
9121
+ ]);
9122
+ const turnWork = (0, react.useMemo)(() => toolCallDisplay === "turn" ? getTurnWork(renderedMessages, toolCallLayouts) : void 0, [
9123
+ toolCallDisplay,
9124
+ renderedMessages,
9125
+ toolCallLayouts
9126
+ ]);
9127
+ const turnByFirstMessage = (0, react.useMemo)(() => {
9128
+ const byFirst = /* @__PURE__ */ new Map();
9129
+ for (const turn of turnWork?.values() ?? []) byFirst.set(turn.firstMessageId, turn);
9130
+ return byFirst;
9131
+ }, [turnWork]);
9132
+ let latestTurnKey = "start";
9133
+ for (let i = renderedMessages.length - 1; i >= 0; i--) if (renderedMessages[i].role === "user") {
9134
+ latestTurnKey = renderedMessages[i].id;
9135
+ break;
9136
+ }
9137
+ const isWorkingOn = (turnKey) => isRunning && turnKey === latestTurnKey;
9138
+ const [toggledTurns, setToggledTurns] = (0, react.useState)(() => /* @__PURE__ */ new Map());
9139
+ const isWorkOpen = (turnKey) => toggledTurns.get(turnKey) ?? isWorkingOn(turnKey);
9140
+ const workTimesRef = (0, react.useRef)(/* @__PURE__ */ new Map());
9141
+ if (toolCallDisplay === "turn") {
9142
+ const now = Date.now();
9143
+ const workTimes = workTimesRef.current;
9144
+ if (isRunning) {
9145
+ const times = workTimes.get(latestTurnKey);
9146
+ if (times) times.workingSince ??= now;
9147
+ else if (renderedMessages[renderedMessages.length - 1]?.role === "user") workTimes.set(latestTurnKey, {
9148
+ elapsedMs: 0,
9149
+ workingSince: now
9150
+ });
9151
+ } else for (const times of workTimes.values()) {
9152
+ if (times.workingSince === void 0) continue;
9153
+ times.elapsedMs += now - times.workingSince;
9154
+ times.workingSince = void 0;
9155
+ }
9156
+ }
9157
+ const { workOpen, workTextOpen } = (0, react.useMemo)(() => {
9158
+ const open = /* @__PURE__ */ new Map();
9159
+ const textOpen = /* @__PURE__ */ new Map();
9160
+ for (const turn of turnWork?.values() ?? []) {
9161
+ const turnOpen = toggledTurns.get(turn.key) ?? (isRunning && turn.key === latestTurnKey);
9162
+ for (const id of turn.workMessageIds) {
9163
+ open.set(id, turnOpen);
9164
+ if (id !== turn.replyTextMessageId) textOpen.set(id, turnOpen);
9165
+ }
9166
+ }
9167
+ return {
9168
+ workOpen: open,
9169
+ workTextOpen: textOpen
9170
+ };
9171
+ }, [
9172
+ turnWork,
9173
+ toggledTurns,
9174
+ isRunning,
9175
+ latestTurnKey
9176
+ ]);
9177
+ const toolCallDisplayContext = (0, react.useMemo)(() => ({
9178
+ display: toolCallDisplay,
9179
+ isRunning,
9180
+ layouts: toolCallLayouts,
9181
+ workOpen,
9182
+ workTextOpen
9183
+ }), [
9184
+ toolCallDisplay,
9185
+ isRunning,
9186
+ toolCallLayouts,
9187
+ workOpen,
9188
+ workTextOpen
9189
+ ]);
9190
+ const renderWorkSummary = (turnKey, calls) => {
9191
+ const working = isWorkingOn(turnKey);
9192
+ const open = isWorkOpen(turnKey);
9193
+ const times = workTimesRef.current.get(turnKey);
9194
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
9195
+ open: working || calls.length > 0,
9196
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9197
+ className: "cpk:pt-1 cpk:pb-2",
9198
+ children: renderSlot(workSummary, CopilotChatWorkSummary, {
9199
+ isWorking: working,
9200
+ isOpen: open,
9201
+ calls,
9202
+ elapsedMs: times?.elapsedMs,
9203
+ workingSince: times?.workingSince,
9204
+ onClick: () => setToggledTurns((toggled) => new Map(toggled).set(turnKey, !open))
9205
+ })
9206
+ })
9207
+ }, `work-summary-${turnKey}`);
9208
+ };
7834
9209
  const transformDuplicateId = (0, react.useMemo)(() => {
7835
9210
  if (process.env.NODE_ENV === "production" || !transformMessages && !groupMessages) return;
7836
9211
  const seen = /* @__PURE__ */ new Set();
@@ -7868,8 +9243,6 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7868
9243
  (0, _copilotkit_shared.commitRowKeyStore)(rowKeyStore, renderedMessages);
7869
9244
  }, [rowKeyStore, renderedMessages]);
7870
9245
  const stableGroupKey = (key) => rowRenderKeys.get(key) ?? key;
7871
- const rowKey = (row) => row.type === "group" ? `${GROUP_ROW_KEY_PREFIX}${stableGroupKey(row.key)}` : rowRenderKeys.get(row.message.id) ?? row.message.id;
7872
- const rowKeys = (0, react.useMemo)(() => rows.map(rowKey), [rows, rowRenderKeys]);
7873
9246
  const groupStateRef = (0, react.useRef)(/* @__PURE__ */ new Map());
7874
9247
  const groupSettersRef = (0, react.useRef)(/* @__PURE__ */ new Map());
7875
9248
  const groupStateThreadRef = (0, react.useRef)(config?.threadId);
@@ -7898,6 +9271,18 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7898
9271
  const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
7899
9272
  const agentId = config?.agentId;
7900
9273
  const threadId = config?.threadId;
9274
+ const learningFeedbackEnabled = useLearningFeedbackEnabled(copilotkit);
9275
+ const feedbackScope = (0, react.useMemo)(() => messageFeedback && learningFeedbackEnabled && agentId !== void 0 && threadId !== void 0 ? {
9276
+ copilotkit,
9277
+ agentId,
9278
+ threadId
9279
+ } : null, [
9280
+ messageFeedback,
9281
+ learningFeedbackEnabled,
9282
+ copilotkit,
9283
+ agentId,
9284
+ threadId
9285
+ ]);
7901
9286
  const assistantSlotPropsWithFeedback = (0, react.useMemo)(() => {
7902
9287
  const onThumbsUp = assistantSlotProps?.onThumbsUp;
7903
9288
  const onThumbsDown = assistantSlotProps?.onThumbsDown;
@@ -7943,8 +9328,6 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7943
9328
  total: 0,
7944
9329
  sizes: /* @__PURE__ */ new Map()
7945
9330
  });
7946
- const rowKeysRef = react.default.useRef(rowKeys);
7947
- rowKeysRef.current = rowKeys;
7948
9331
  const firstMessageId = deduplicatedMessages[0]?.id;
7949
9332
  const measuredThreadRef = react.default.useRef(firstMessageId);
7950
9333
  if (measuredThreadRef.current !== firstMessageId) {
@@ -7954,16 +9337,6 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7954
9337
  sizes: /* @__PURE__ */ new Map()
7955
9338
  };
7956
9339
  }
7957
- const measuredKeysRef = react.default.useRef(rowKeys);
7958
- if (measuredKeysRef.current !== rowKeys) {
7959
- measuredKeysRef.current = rowKeys;
7960
- const live = new Set(rowKeys);
7961
- const measured = measuredRef.current;
7962
- for (const [key, height] of measured.sizes) if (!live.has(key)) {
7963
- measured.sizes.delete(key);
7964
- measured.total -= height;
7965
- }
7966
- }
7967
9340
  const estimateRowSize = react.default.useCallback(() => {
7968
9341
  const { total, sizes } = measuredRef.current;
7969
9342
  return sizes.size > 0 ? Math.max(1, Math.round(total / sizes.size)) : 100;
@@ -7971,15 +9344,13 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7971
9344
  const measureRowElement = react.default.useCallback((el) => {
7972
9345
  const height = el?.getBoundingClientRect().height ?? 0;
7973
9346
  const index = Number(el?.dataset?.index);
7974
- const key = Number.isInteger(index) ? rowKeysRef.current[index] : void 0;
7975
- if (height > 0 && key !== void 0) {
9347
+ if (height > 0 && Number.isInteger(index)) {
7976
9348
  const { total, sizes } = measuredRef.current;
7977
- measuredRef.current.total = total - (sizes.get(key) ?? 0) + height;
7978
- sizes.set(key, height);
9349
+ measuredRef.current.total = total - (sizes.get(index) ?? 0) + height;
9350
+ sizes.set(index, height);
7979
9351
  }
7980
- return height;
9352
+ return height > 0 ? height : measuredRef.current.sizes.get(index) ?? 100;
7981
9353
  }, []);
7982
- const getRowKey = react.default.useCallback((index) => rowKeys[index] ?? index, [rowKeys]);
7983
9354
  const isPinnedToBottomRef = react.default.useRef(isPinnedToBottom);
7984
9355
  const shouldAdjustScrollOnResize = react.default.useCallback((item, _delta, instance) => {
7985
9356
  if (isPinnedToBottomRef.current) return false;
@@ -7988,9 +9359,8 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
7988
9359
  }, []);
7989
9360
  const virtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
7990
9361
  count: shouldVirtualize ? rows.length : 0,
7991
- getScrollElement: () => scrollElement,
9362
+ getScrollElement: () => shouldVirtualize ? scrollElement : null,
7992
9363
  estimateSize: estimateRowSize,
7993
- getItemKey: getRowKey,
7994
9364
  overscan: 5,
7995
9365
  measureElement: measureRowElement,
7996
9366
  initialRect: {
@@ -8000,8 +9370,11 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
8000
9370
  });
8001
9371
  isPinnedToBottomRef.current = isPinnedToBottom;
8002
9372
  virtualizer.shouldAdjustScrollPositionOnItemSizeChange = shouldAdjustScrollOnResize;
9373
+ const lastEndAlignedFirstMessageRef = react.default.useRef(void 0);
8003
9374
  (0, react.useLayoutEffect)(() => {
8004
9375
  if (!shouldVirtualize || !rows.length) return;
9376
+ if (lastEndAlignedFirstMessageRef.current === firstMessageId) return;
9377
+ lastEndAlignedFirstMessageRef.current = firstMessageId;
8005
9378
  virtualizer.scrollToIndex(rows.length - 1, { align: "end" });
8006
9379
  }, [shouldVirtualize, firstMessageId]);
8007
9380
  const intelligenceTurnAnchors = (0, react.useMemo)(() => getIntelligenceTurnAnchors(deduplicatedMessages, renderedMessages), [deduplicatedMessages, renderedMessages]);
@@ -8009,6 +9382,8 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
8009
9382
  const elements = [];
8010
9383
  const stateSnapshot = renderCustomMessage ? getStateSnapshotForMessage(message.id) : void 0;
8011
9384
  const rowKey = rowRenderKeys.get(message.id) ?? message.id;
9385
+ const turn = turnByFirstMessage.get(message.id);
9386
+ if (turn) elements.push(renderWorkSummary(turn.key, turn.calls));
8012
9387
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
8013
9388
  message,
8014
9389
  position: "before",
@@ -8032,14 +9407,20 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
8032
9407
  message,
8033
9408
  renderActivityMessage
8034
9409
  }, rowKey));
8035
- else if (message.role === "reasoning") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
8036
- message,
8037
- messages,
8038
- isRunning,
8039
- isLatest: message.id === latestRenderedId,
8040
- ReasoningMessageComponent: ReasoningComponent,
8041
- slotProps: reasoningSlotProps
8042
- }, rowKey));
9410
+ else if (message.role === "reasoning") {
9411
+ const reasoning = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
9412
+ message,
9413
+ messages,
9414
+ isRunning,
9415
+ isLatest: message.id === latestRenderedId,
9416
+ ReasoningMessageComponent: ReasoningComponent,
9417
+ slotProps: reasoningSlotProps
9418
+ }, rowKey);
9419
+ elements.push(toolCallDisplay === "turn" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Collapsible, {
9420
+ open: workOpen.get(message.id) ?? true,
9421
+ children: reasoning
9422
+ }, rowKey) : reasoning);
9423
+ }
8043
9424
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
8044
9425
  message,
8045
9426
  position: "after",
@@ -8054,6 +9435,7 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
8054
9435
  }, `intelligence-${intelligenceTurnId}`));
8055
9436
  return elements.filter(Boolean);
8056
9437
  };
9438
+ const rowKey = (row) => row.type === "group" ? `${GROUP_ROW_KEY_PREFIX}${stableGroupKey(row.key)}` : rowRenderKeys.get(row.message.id) ?? row.message.id;
8057
9439
  const renderRow = (row) => {
8058
9440
  if (row.type === "message") return renderMessageBlock(row.message);
8059
9441
  const Wrapper = row.wrapper;
@@ -8066,52 +9448,157 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
8066
9448
  children: row.messages.flatMap(renderMessageBlock)
8067
9449
  }, rowKey(row))];
8068
9450
  };
9451
+ const rowKeysRef = (0, react.useRef)([]);
9452
+ rowKeysRef.current = rows.map(rowKey);
9453
+ const virtualModeRef = (0, react.useRef)(shouldVirtualize);
9454
+ virtualModeRef.current = shouldVirtualize;
9455
+ (0, react.useEffect)(() => {
9456
+ const element = scrollElementFromCtx;
9457
+ if (!element) return;
9458
+ let anchor = null;
9459
+ let suspended = false;
9460
+ let frame = 0;
9461
+ const visibleRows = () => Array.from(element.querySelectorAll("[data-scroll-anchor]"));
9462
+ const remember = () => {
9463
+ if (suspended || element.clientHeight === 0 || !virtualModeRef.current) return;
9464
+ if (isPinnedToBottomRef.current) {
9465
+ anchor = null;
9466
+ return;
9467
+ }
9468
+ const viewport = element.getBoundingClientRect();
9469
+ const row = visibleRows().find((row) => {
9470
+ const bounds = row.getBoundingClientRect();
9471
+ return bounds.bottom > viewport.top && bounds.top < viewport.bottom;
9472
+ });
9473
+ if (row) anchor = {
9474
+ key: row.dataset.scrollAnchor,
9475
+ offset: row.getBoundingClientRect().top - viewport.top
9476
+ };
9477
+ };
9478
+ const observer = new ResizeObserver(() => {
9479
+ if (element.clientHeight === 0) {
9480
+ suspended = true;
9481
+ cancelAnimationFrame(frame);
9482
+ return;
9483
+ }
9484
+ if (!suspended || !anchor) {
9485
+ suspended = false;
9486
+ remember();
9487
+ return;
9488
+ }
9489
+ let attempts = 0;
9490
+ let stableFrames = 0;
9491
+ const restore = () => {
9492
+ if (!anchor || element.clientHeight === 0) return;
9493
+ const index = rowKeysRef.current.indexOf(anchor.key);
9494
+ if (index < 0) {
9495
+ anchor = null;
9496
+ suspended = false;
9497
+ return;
9498
+ }
9499
+ const row = visibleRows().find((row) => row.dataset.scrollAnchor === anchor.key);
9500
+ if (row) {
9501
+ const delta = row.getBoundingClientRect().top - element.getBoundingClientRect().top - anchor.offset;
9502
+ element.scrollTop += delta;
9503
+ stableFrames = Math.abs(delta) < 1 ? stableFrames + 1 : 0;
9504
+ } else {
9505
+ const offset = virtualizer.getOffsetForIndex(index, "start")?.[0];
9506
+ if (offset !== void 0) virtualizer.scrollToOffset(offset - anchor.offset);
9507
+ stableFrames = 0;
9508
+ }
9509
+ if (++attempts < 60 && stableFrames < 3) frame = requestAnimationFrame(restore);
9510
+ else {
9511
+ suspended = false;
9512
+ remember();
9513
+ }
9514
+ };
9515
+ frame = requestAnimationFrame(restore);
9516
+ });
9517
+ const interrupt = () => {
9518
+ cancelAnimationFrame(frame);
9519
+ anchor = null;
9520
+ suspended = false;
9521
+ };
9522
+ element.addEventListener("scroll", remember, { passive: true });
9523
+ element.addEventListener("wheel", interrupt, { passive: true });
9524
+ element.addEventListener("touchstart", interrupt, { passive: true });
9525
+ element.addEventListener("keydown", interrupt);
9526
+ observer.observe(element);
9527
+ remember();
9528
+ return () => {
9529
+ cancelAnimationFrame(frame);
9530
+ observer.disconnect();
9531
+ element.removeEventListener("scroll", remember);
9532
+ element.removeEventListener("wheel", interrupt);
9533
+ element.removeEventListener("touchstart", interrupt);
9534
+ element.removeEventListener("keydown", interrupt);
9535
+ };
9536
+ }, [
9537
+ scrollElementFromCtx,
9538
+ virtualizer,
9539
+ firstMessageId
9540
+ ]);
8069
9541
  const messageElements = shouldVirtualize ? [] : rows.flatMap(renderRow);
8070
- if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8071
- "data-copilotkit": true,
8072
- style: { display: "contents" },
8073
- children: children({
8074
- messageElements,
8075
- messages,
8076
- isRunning,
8077
- interruptElement
9542
+ if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFeedbackScopeContext.Provider, {
9543
+ value: feedbackScope,
9544
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallDisplayContext.Provider, {
9545
+ value: toolCallDisplayContext,
9546
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9547
+ "data-copilotkit": true,
9548
+ style: { display: "contents" },
9549
+ children: children({
9550
+ messageElements,
9551
+ messages,
9552
+ isRunning,
9553
+ interruptElement
9554
+ })
9555
+ })
8078
9556
  })
8079
9557
  });
8080
9558
  const lastMessage = renderedMessages[renderedMessages.length - 1];
8081
- const showCursor = isRunning && lastMessage?.role !== "reasoning";
8082
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8083
- "data-copilotkit": true,
8084
- "data-testid": "copilot-message-list",
8085
- className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
8086
- ...props,
8087
- children: [
8088
- shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8089
- style: {
8090
- height: virtualizer.getTotalSize(),
8091
- position: "relative"
8092
- },
8093
- children: virtualizer.getVirtualItems().map((virtualItem) => {
8094
- const row = rows[virtualItem.index];
8095
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8096
- "data-index": virtualItem.index,
8097
- ref: virtualizer.measureElement,
9559
+ const showCursor = isRunning && lastMessage?.role !== "reasoning" && toolCallDisplay !== "turn";
9560
+ const pendingWorkSummary = toolCallDisplay === "turn" && isRunning && lastMessage?.role === "user";
9561
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MessageFeedbackScopeContext.Provider, {
9562
+ value: feedbackScope,
9563
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolCallDisplayContext.Provider, {
9564
+ value: toolCallDisplayContext,
9565
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9566
+ "data-copilotkit": true,
9567
+ "data-testid": "copilot-message-list",
9568
+ className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
9569
+ ...props,
9570
+ children: [
9571
+ shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8098
9572
  style: {
8099
- position: "absolute",
8100
- top: 0,
8101
- left: 0,
8102
- width: "100%",
8103
- transform: `translateY(${virtualItem.start}px)`
9573
+ height: virtualizer.getTotalSize(),
9574
+ position: "relative"
8104
9575
  },
8105
- children: renderRow(row)
8106
- }, rowKey(row));
8107
- })
8108
- }) : messageElements,
8109
- interruptElement,
8110
- showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8111
- className: "cpk:mt-2",
8112
- children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
9576
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
9577
+ const row = rows[virtualItem.index];
9578
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9579
+ "data-index": virtualItem.index,
9580
+ "data-scroll-anchor": rowKey(row),
9581
+ ref: virtualizer.measureElement,
9582
+ style: {
9583
+ position: "absolute",
9584
+ top: 0,
9585
+ left: 0,
9586
+ width: "100%",
9587
+ transform: `translateY(${virtualItem.start}px)`
9588
+ },
9589
+ children: renderRow(row)
9590
+ }, rowKey(row));
9591
+ })
9592
+ }) : messageElements,
9593
+ interruptElement,
9594
+ pendingWorkSummary && renderWorkSummary(latestTurnKey, []),
9595
+ showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9596
+ className: "cpk:mt-2",
9597
+ children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
9598
+ })
9599
+ ]
8113
9600
  })
8114
- ]
9601
+ })
8115
9602
  });
8116
9603
  }
8117
9604
  CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
@@ -8496,6 +9983,16 @@ function DropOverlay() {
8496
9983
  });
8497
9984
  }
8498
9985
  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 }) {
9986
+ const configuredThreadId = useCopilotChatConfiguration()?.threadId;
9987
+ const [fallbackThreadId, setFallbackThreadId] = (0, react.useState)(messages[0]?.id);
9988
+ const previousMessageIds = (0, react.useRef)(/* @__PURE__ */ new Set());
9989
+ (0, react.useEffect)(() => {
9990
+ if (messages.length === 0) return;
9991
+ const overlaps = messages.some((message) => previousMessageIds.current.has(message.id));
9992
+ previousMessageIds.current = new Set(messages.map((message) => message.id));
9993
+ if (!overlaps) setFallbackThreadId(messages[0].id);
9994
+ }, [messages]);
9995
+ const scrollThreadId = configuredThreadId ?? fallbackThreadId;
8499
9996
  const [inputContainerEl, setInputContainerEl] = (0, react.useState)(null);
8500
9997
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
8501
9998
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -8562,6 +10059,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
8562
10059
  }) : null;
8563
10060
  const BoundScrollView = renderSlot(scrollView, CopilotChatView.ScrollView, {
8564
10061
  autoScroll,
10062
+ scrollThreadId,
8565
10063
  inputContainerHeight,
8566
10064
  isResizing,
8567
10065
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -8657,12 +10155,102 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
8657
10155
  });
8658
10156
  }
8659
10157
  (function(_CopilotChatView) {
8660
- const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
8661
- const { isAtBottom, scrollToBottom, scrollRef, state } = (0, use_stick_to_bottom.useStickToBottomContext)();
10158
+ const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing, scrollThreadId }) => {
10159
+ const { isAtBottom, scrollToBottom, stopScroll, scrollRef, state } = (0, use_stick_to_bottom.useStickToBottomContext)();
8662
10160
  const [scrollEl, setScrollEl] = (0, react.useState)(null);
10161
+ const readingOffsetRef = (0, react.useRef)(null);
10162
+ const pinIntentRef = (0, react.useRef)(false);
10163
+ const stateRef = (0, react.useRef)(state);
10164
+ stateRef.current = state;
8663
10165
  (0, react.useLayoutEffect)(() => {
8664
10166
  setScrollEl(scrollRef.current ?? null);
8665
10167
  }, []);
10168
+ (0, react.useEffect)(() => {
10169
+ if (!scrollEl) return;
10170
+ let hidden = false;
10171
+ let resumeBottom = false;
10172
+ let pendingFrame = 0;
10173
+ let resumeFrame = 0;
10174
+ let settleFrame = 0;
10175
+ readingOffsetRef.current = null;
10176
+ const rememberScroll = () => {
10177
+ if (hidden || scrollEl.clientHeight === 0) return;
10178
+ if (resumeBottom) {
10179
+ readingOffsetRef.current = null;
10180
+ return;
10181
+ }
10182
+ if (pinIntentRef.current) if (stateRef.current.escapedFromLock) pinIntentRef.current = false;
10183
+ else {
10184
+ readingOffsetRef.current = null;
10185
+ if (!stateRef.current.isNearBottom) return;
10186
+ pinIntentRef.current = false;
10187
+ }
10188
+ readingOffsetRef.current = !stateRef.current.isAtBottom && (stateRef.current.escapedFromLock || !stateRef.current.isNearBottom) ? scrollEl.scrollTop : null;
10189
+ };
10190
+ const onScroll = () => {
10191
+ rememberScroll();
10192
+ cancelAnimationFrame(pendingFrame);
10193
+ pendingFrame = requestAnimationFrame(rememberScroll);
10194
+ };
10195
+ const onWheel = (event) => {
10196
+ if (event.deltaY < 0) {
10197
+ pinIntentRef.current = false;
10198
+ resumeBottom = false;
10199
+ }
10200
+ };
10201
+ const resizeObserver = new ResizeObserver(() => {
10202
+ if (scrollEl.clientHeight === 0) {
10203
+ hidden = true;
10204
+ resumeBottom = pinIntentRef.current || readingOffsetRef.current === null;
10205
+ if (!resumeBottom) stopScroll();
10206
+ } else if (hidden) {
10207
+ hidden = false;
10208
+ if (resumeBottom) {
10209
+ stopScroll();
10210
+ resumeFrame = requestAnimationFrame(() => {
10211
+ settleFrame = requestAnimationFrame(() => {
10212
+ if (scrollEl.clientHeight > 0 && resumeBottom) {
10213
+ resumeBottom = false;
10214
+ pinIntentRef.current = true;
10215
+ scrollToBottom({ animation: "instant" });
10216
+ }
10217
+ });
10218
+ });
10219
+ } else if (readingOffsetRef.current !== null) {
10220
+ stopScroll();
10221
+ scrollEl.scrollTop = readingOffsetRef.current;
10222
+ }
10223
+ }
10224
+ });
10225
+ rememberScroll();
10226
+ scrollEl.addEventListener("scroll", onScroll);
10227
+ scrollEl.addEventListener("wheel", onWheel, { passive: true });
10228
+ resizeObserver.observe(scrollEl);
10229
+ return () => {
10230
+ scrollEl.removeEventListener("scroll", onScroll);
10231
+ scrollEl.removeEventListener("wheel", onWheel);
10232
+ cancelAnimationFrame(pendingFrame);
10233
+ cancelAnimationFrame(resumeFrame);
10234
+ cancelAnimationFrame(settleFrame);
10235
+ resizeObserver.disconnect();
10236
+ };
10237
+ }, [
10238
+ scrollEl,
10239
+ scrollToBottom,
10240
+ stopScroll,
10241
+ scrollThreadId
10242
+ ]);
10243
+ const startBottomPin = (0, react.useCallback)((instant = false) => {
10244
+ readingOffsetRef.current = null;
10245
+ pinIntentRef.current = true;
10246
+ scrollToBottom(instant ? { animation: "instant" } : void 0);
10247
+ }, [scrollToBottom]);
10248
+ const previousScrollThreadId = (0, react.useRef)(scrollThreadId);
10249
+ (0, react.useEffect)(() => {
10250
+ if (previousScrollThreadId.current === scrollThreadId) return;
10251
+ previousScrollThreadId.current = scrollThreadId;
10252
+ startBottomPin(true);
10253
+ }, [scrollThreadId, startBottomPin]);
8666
10254
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
8667
10255
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
8668
10256
  value: scrollEl,
@@ -8684,7 +10272,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
8684
10272
  !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8685
10273
  className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
8686
10274
  style: { bottom: `${inputContainerHeight + SCROLL_BUTTON_OFFSET}px` },
8687
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
10275
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => startBottomPin() })
8688
10276
  })
8689
10277
  ] })
8690
10278
  })
@@ -8732,7 +10320,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
8732
10320
  })
8733
10321
  });
8734
10322
  };
8735
- _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
10323
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollThreadId, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
8736
10324
  const mode = normalizeAutoScroll(autoScroll);
8737
10325
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
8738
10326
  const scrollRef = (0, react.useRef)(null);
@@ -8821,6 +10409,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
8821
10409
  initial: "smooth",
8822
10410
  ...props,
8823
10411
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollContent, {
10412
+ scrollThreadId,
8824
10413
  scrollToBottomButton,
8825
10414
  feather,
8826
10415
  inputContainerHeight,
@@ -8829,14 +10418,18 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
8829
10418
  })
8830
10419
  });
8831
10420
  };
8832
- _CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
8833
- "data-testid": "copilot-scroll-to-bottom",
8834
- variant: "outline",
8835
- size: "sm",
8836
- 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),
8837
- ...props,
8838
- 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" })
8839
- });
10421
+ _CopilotChatView.ScrollToBottomButton = ({ className, ...props }) => {
10422
+ const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
10423
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
10424
+ "data-testid": "copilot-scroll-to-bottom",
10425
+ variant: "outline",
10426
+ size: "sm",
10427
+ 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),
10428
+ "aria-label": labels.chatScrollToBottomLabel,
10429
+ ...props,
10430
+ 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" })
10431
+ });
10432
+ };
8840
10433
  _CopilotChatView.Feather = ({ className, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8841
10434
  className,
8842
10435
  ...props
@@ -10219,6 +11812,7 @@ function CopilotPopupViewInternal({ header, toggleButton, width, height, clickOu
10219
11812
  if (containerRef.current?.contains(target)) return;
10220
11813
  const toggleButton = document.querySelector("[data-slot='chat-toggle-button']");
10221
11814
  if (toggleButton && toggleButton.contains(target)) return;
11815
+ if (target instanceof Element && target.closest("[data-copilotkit-popover]")) return;
10222
11816
  setModalOpen?.(false);
10223
11817
  };
10224
11818
  document.addEventListener("pointerdown", handlePointerDown);
@@ -10729,62 +12323,14 @@ CopilotThreadsDrawer.displayName = "CopilotThreadsDrawer";
10729
12323
 
10730
12324
  //#endregion
10731
12325
  //#region src/v2/components/WildcardToolCallRender.tsx
12326
+ /**
12327
+ * Catch-all (`"*"`) tool-call renderer that shows CopilotKit's default
12328
+ * tool-call card for any tool without a dedicated renderer.
12329
+ */
10732
12330
  const WildcardToolCallRender = defineToolCallRenderer({
10733
12331
  name: "*",
10734
- render: ({ args, result, name, status }) => {
10735
- const [isExpanded, setIsExpanded] = (0, react.useState)(false);
10736
- const statusString = String(status);
10737
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10738
- className: "cpk:mt-2 cpk:pb-2",
10739
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10740
- 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",
10741
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10742
- className: "cpk:flex cpk:items-center cpk:justify-between cpk:gap-3 cpk:cursor-pointer",
10743
- onClick: () => setIsExpanded(!isExpanded),
10744
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10745
- className: "cpk:flex cpk:items-center cpk:gap-2 cpk:min-w-0",
10746
- children: [
10747
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
10748
- className: `cpk:h-4 cpk:w-4 cpk:text-zinc-500 cpk:dark:text-zinc-400 cpk:transition-transform ${isExpanded ? "cpk:rotate-90" : ""}`,
10749
- fill: "none",
10750
- viewBox: "0 0 24 24",
10751
- strokeWidth: 2,
10752
- stroke: "currentColor",
10753
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
10754
- strokeLinecap: "round",
10755
- strokeLinejoin: "round",
10756
- d: "M8.25 4.5l7.5 7.5-7.5 7.5"
10757
- })
10758
- }),
10759
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "cpk:inline-block cpk:h-2 cpk:w-2 cpk:rounded-full cpk:bg-blue-500" }),
10760
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10761
- className: "cpk:truncate cpk:text-sm cpk:font-medium cpk:text-zinc-900 cpk:dark:text-zinc-100",
10762
- children: name
10763
- })
10764
- ]
10765
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
10766
- 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"}`,
10767
- children: String(status)
10768
- })]
10769
- }), isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
10770
- className: "cpk:mt-3 cpk:grid cpk:gap-4",
10771
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10772
- className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
10773
- children: "Arguments"
10774
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
10775
- 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",
10776
- children: JSON.stringify(args ?? {}, null, 2)
10777
- })] }), result !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
10778
- className: "cpk:text-xs cpk:uppercase cpk:tracking-wide cpk:text-zinc-500 cpk:dark:text-zinc-400",
10779
- children: "Result"
10780
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
10781
- 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",
10782
- children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
10783
- })] })]
10784
- })]
10785
- })
10786
- });
10787
- }
12332
+ group: true,
12333
+ render: (props) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultToolCallRenderer, { ...adaptRendererProps(props) })
10788
12334
  });
10789
12335
 
10790
12336
  //#endregion
@@ -13063,6 +14609,12 @@ Object.defineProperty(exports, 'CopilotChatToggleButton', {
13063
14609
  return CopilotChatToggleButton;
13064
14610
  }
13065
14611
  });
14612
+ Object.defineProperty(exports, 'CopilotChatToolCallGroup', {
14613
+ enumerable: true,
14614
+ get: function () {
14615
+ return CopilotChatToolCallGroup;
14616
+ }
14617
+ });
13066
14618
  Object.defineProperty(exports, 'CopilotChatToolCallsView', {
13067
14619
  enumerable: true,
13068
14620
  get: function () {
@@ -13081,6 +14633,12 @@ Object.defineProperty(exports, 'CopilotChatView_default', {
13081
14633
  return CopilotChatView_default;
13082
14634
  }
13083
14635
  });
14636
+ Object.defineProperty(exports, 'CopilotChatWorkSummary', {
14637
+ enumerable: true,
14638
+ get: function () {
14639
+ return CopilotChatWorkSummary;
14640
+ }
14641
+ });
13084
14642
  Object.defineProperty(exports, 'CopilotContext', {
13085
14643
  enumerable: true,
13086
14644
  get: function () {
@@ -13483,4 +15041,4 @@ Object.defineProperty(exports, 'useToast', {
13483
15041
  return useToast;
13484
15042
  }
13485
15043
  });
13486
- //# sourceMappingURL=copilotkit-ConxFSB2.cjs.map
15044
+ //# sourceMappingURL=copilotkit-DKICi3nC.cjs.map