@copilotkit/react-core 1.55.0-next.9 → 1.55.1-next.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 (81) hide show
  1. package/CHANGELOG.md +46 -6
  2. package/dist/{copilotkit-DeOzjPsb.mjs → copilotkit-BY5S1-0P.mjs} +2402 -552
  3. package/dist/copilotkit-BY5S1-0P.mjs.map +1 -0
  4. package/dist/{copilotkit-BqcyhQjT.d.mts → copilotkit-BuhSUZHb.d.mts} +228 -17
  5. package/dist/copilotkit-BuhSUZHb.d.mts.map +1 -0
  6. package/dist/{copilotkit-BDNjFNmk.cjs → copilotkit-Bz5-ImDl.cjs} +2421 -541
  7. package/dist/copilotkit-Bz5-ImDl.cjs.map +1 -0
  8. package/dist/{copilotkit-l-IBF4Xp.d.cts → copilotkit-dwDWYpya.d.cts} +228 -17
  9. package/dist/copilotkit-dwDWYpya.d.cts.map +1 -0
  10. package/dist/index.cjs +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.mts +1 -1
  13. package/dist/index.mjs +1 -1
  14. package/dist/index.umd.js +1400 -238
  15. package/dist/index.umd.js.map +1 -1
  16. package/dist/v2/index.cjs +13 -1
  17. package/dist/v2/index.css +1 -1
  18. package/dist/v2/index.d.cts +3 -3
  19. package/dist/v2/index.d.mts +3 -3
  20. package/dist/v2/index.mjs +3 -2
  21. package/dist/v2/index.umd.js +2442 -552
  22. package/dist/v2/index.umd.js.map +1 -1
  23. package/package.json +62 -54
  24. package/scripts/scope-preflight.mjs +1 -2
  25. package/src/components/CopilotListeners.tsx +41 -8
  26. package/src/components/copilot-provider/copilotkit-props.tsx +4 -2
  27. package/src/components/toast/toast-provider.tsx +269 -194
  28. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +86 -22
  29. package/src/v2/__tests__/utils/test-helpers.tsx +67 -0
  30. package/src/v2/a2ui/A2UICatalogContext.tsx +79 -0
  31. package/src/v2/a2ui/A2UIMessageRenderer.tsx +125 -37
  32. package/src/v2/a2ui/A2UIToolCallRenderer.tsx +290 -0
  33. package/src/v2/components/CopilotKitInspector.tsx +2 -0
  34. package/src/v2/components/OpenGenerativeUIRenderer.tsx +598 -0
  35. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.tsx +665 -0
  36. package/src/v2/components/chat/CopilotChat.tsx +193 -50
  37. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +17 -2
  38. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +481 -0
  39. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +139 -0
  40. package/src/v2/components/chat/CopilotChatInput.tsx +146 -77
  41. package/src/v2/components/chat/CopilotChatMessageView.tsx +253 -149
  42. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +1 -0
  43. package/src/v2/components/chat/CopilotChatUserMessage.tsx +54 -0
  44. package/src/v2/components/chat/CopilotChatView.tsx +179 -66
  45. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +168 -0
  46. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +63 -2
  47. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +544 -1
  48. package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +268 -0
  49. package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.tsx +249 -0
  50. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +43 -2
  51. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +138 -0
  52. package/src/v2/components/chat/index.ts +9 -0
  53. package/src/v2/components/chat/scroll-element-context.ts +13 -0
  54. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +1003 -0
  55. package/src/v2/hooks/__tests__/use-attachments.test.tsx +169 -0
  56. package/src/v2/hooks/__tests__/use-threads.test.tsx +54 -0
  57. package/src/v2/hooks/index.ts +5 -0
  58. package/src/v2/hooks/use-agent.tsx +95 -10
  59. package/src/v2/hooks/use-attachments.tsx +269 -0
  60. package/src/v2/hooks/use-frontend-tool.tsx +5 -2
  61. package/src/v2/hooks/use-render-activity-message.tsx +9 -2
  62. package/src/v2/hooks/use-threads.tsx +35 -15
  63. package/src/v2/index.ts +5 -1
  64. package/src/v2/lib/__tests__/processPartialHtml.test.ts +112 -0
  65. package/src/v2/lib/__tests__/slots.test.ts +56 -0
  66. package/src/v2/lib/processPartialHtml.ts +45 -0
  67. package/src/v2/lib/slots.tsx +42 -1
  68. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +9 -3
  69. package/src/v2/providers/CopilotKitProvider.tsx +268 -32
  70. package/src/v2/providers/SandboxFunctionsContext.ts +10 -0
  71. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.tsx +198 -0
  72. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +71 -0
  73. package/src/v2/providers/index.ts +7 -0
  74. package/src/v2/styles/globals.css +2 -1
  75. package/src/v2/types/index.ts +1 -0
  76. package/src/v2/types/sandbox-function.ts +11 -0
  77. package/dist/copilotkit-BDNjFNmk.cjs.map +0 -1
  78. package/dist/copilotkit-BqcyhQjT.d.mts.map +0 -1
  79. package/dist/copilotkit-DeOzjPsb.mjs.map +0 -1
  80. package/dist/copilotkit-l-IBF4Xp.d.cts.map +0 -1
  81. package/src/v2/components/__tests__/license-warning-banner.test.tsx +0 -46
@@ -44,19 +44,120 @@ let streamdown = require("streamdown");
44
44
  let zod = require("zod");
45
45
  let _lit_labs_react = require("@lit-labs/react");
46
46
  let _copilotkit_a2ui_renderer = require("@copilotkit/a2ui-renderer");
47
- let use_stick_to_bottom = require("use-stick-to-bottom");
48
- let ts_deepmerge = require("ts-deepmerge");
47
+ let zod_to_json_schema = require("zod-to-json-schema");
48
+ let _tanstack_react_virtual = require("@tanstack/react-virtual");
49
49
  let react_dom = require("react-dom");
50
+ let use_stick_to_bottom = require("use-stick-to-bottom");
50
51
  let react_markdown = require("react-markdown");
51
52
  react_markdown = __toESM(react_markdown);
52
53
 
54
+ //#region src/v2/lib/slots.tsx
55
+ /**
56
+ * Shallow equality comparison for objects.
57
+ */
58
+ function shallowEqual(obj1, obj2) {
59
+ const keys1 = Object.keys(obj1);
60
+ const keys2 = Object.keys(obj2);
61
+ if (keys1.length !== keys2.length) return false;
62
+ for (const key of keys1) if (obj1[key] !== obj2[key]) return false;
63
+ return true;
64
+ }
65
+ /**
66
+ * Returns true only for plain JS objects (`{}`), excluding arrays, Dates,
67
+ * class instances, and other exotic objects that happen to have typeof "object".
68
+ */
69
+ function isPlainObject(obj) {
70
+ return obj !== null && typeof obj === "object" && Object.prototype.toString.call(obj) === "[object Object]";
71
+ }
72
+ /**
73
+ * Returns the same reference as long as the value is shallowly equal to the
74
+ * previous render's value.
75
+ *
76
+ * - Identical references bail out immediately (O(1)).
77
+ * - Plain objects ({}) are shallow-compared key-by-key.
78
+ * - Arrays, Dates, class instances, functions, and primitives are compared by
79
+ * reference only — shallowEqual is never called on non-plain objects, which
80
+ * avoids incorrect equality for e.g. [1,2] vs [1,2] (different arrays).
81
+ *
82
+ * Typical use: stabilize inline slot props so MemoizedSlotWrapper's shallow
83
+ * equality check isn't defeated by a new object reference on every render.
84
+ */
85
+ function useShallowStableRef(value) {
86
+ const ref = (0, react.useRef)(value);
87
+ if (ref.current === value) return ref.current;
88
+ if (isPlainObject(ref.current) && isPlainObject(value)) {
89
+ if (shallowEqual(ref.current, value)) return ref.current;
90
+ }
91
+ ref.current = value;
92
+ return ref.current;
93
+ }
94
+ /**
95
+ * Check if a value is a React component type (function, class, forwardRef, memo, etc.)
96
+ */
97
+ function isReactComponentType(value) {
98
+ if (typeof value === "function") return true;
99
+ if (value && typeof value === "object" && "$$typeof" in value && !react.default.isValidElement(value)) return true;
100
+ return false;
101
+ }
102
+ /**
103
+ * Internal function to render a slot value as a React element (non-memoized).
104
+ */
105
+ function renderSlotElement(slot, DefaultComponent, props) {
106
+ if (typeof slot === "string") {
107
+ const existingClassName = props.className;
108
+ return react.default.createElement(DefaultComponent, {
109
+ ...props,
110
+ className: (0, tailwind_merge.twMerge)(existingClassName, slot)
111
+ });
112
+ }
113
+ if (isReactComponentType(slot)) return react.default.createElement(slot, props);
114
+ if (slot && typeof slot === "object" && !react.default.isValidElement(slot)) return react.default.createElement(DefaultComponent, {
115
+ ...props,
116
+ ...slot
117
+ });
118
+ return react.default.createElement(DefaultComponent, props);
119
+ }
120
+ /**
121
+ * Internal memoized wrapper component for renderSlot.
122
+ * Uses forwardRef to support ref forwarding.
123
+ */
124
+ const MemoizedSlotWrapper = react.default.memo(react.default.forwardRef(function MemoizedSlotWrapper(props, ref) {
125
+ const { $slot, $component, ...rest } = props;
126
+ return renderSlotElement($slot, $component, ref !== null ? {
127
+ ...rest,
128
+ ref
129
+ } : rest);
130
+ }), (prev, next) => {
131
+ if (prev.$slot !== next.$slot) return false;
132
+ if (prev.$component !== next.$component) return false;
133
+ const { $slot: _ps, $component: _pc, ...prevRest } = prev;
134
+ const { $slot: _ns, $component: _nc, ...nextRest } = next;
135
+ return shallowEqual(prevRest, nextRest);
136
+ });
137
+ /**
138
+ * Renders a slot value as a memoized React element.
139
+ * Automatically prevents unnecessary re-renders using shallow prop comparison.
140
+ * Supports ref forwarding.
141
+ *
142
+ * @example
143
+ * renderSlot(customInput, CopilotChatInput, { onSubmit: handleSubmit })
144
+ */
145
+ function renderSlot(slot, DefaultComponent, props) {
146
+ return react.default.createElement(MemoizedSlotWrapper, {
147
+ ...props,
148
+ $slot: slot,
149
+ $component: DefaultComponent
150
+ });
151
+ }
152
+
153
+ //#endregion
53
154
  //#region src/v2/providers/CopilotChatConfigurationProvider.tsx
54
155
  const CopilotChatDefaultLabels = {
55
156
  chatInputPlaceholder: "Type a message...",
56
157
  chatInputToolbarStartTranscribeButtonLabel: "Transcribe",
57
158
  chatInputToolbarCancelTranscribeButtonLabel: "Cancel",
58
159
  chatInputToolbarFinishTranscribeButtonLabel: "Finish",
59
- chatInputToolbarAddButtonLabel: "Add photos or files",
160
+ chatInputToolbarAddButtonLabel: "Add attachments",
60
161
  chatInputToolbarToolsButtonLabel: "Tools",
61
162
  assistantMessageToolbarCopyCodeLabel: "Copy",
62
163
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
@@ -76,11 +177,12 @@ const CopilotChatDefaultLabels = {
76
177
  const CopilotChatConfiguration = (0, react.createContext)(null);
77
178
  const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, isModalDefaultOpen }) => {
78
179
  const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
180
+ const stableLabels = useShallowStableRef(labels);
79
181
  const mergedLabels = (0, react.useMemo)(() => ({
80
182
  ...CopilotChatDefaultLabels,
81
183
  ...parentConfig?.labels ?? {},
82
- ...labels ?? {}
83
- }), [labels, parentConfig?.labels]);
184
+ ...stableLabels ?? {}
185
+ }), [stableLabels, parentConfig?.labels]);
84
186
  const resolvedAgentId = agentId ?? parentConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
85
187
  const resolvedThreadId = (0, react.useMemo)(() => {
86
188
  if (threadId) return threadId;
@@ -129,9 +231,9 @@ const useCopilotChatConfiguration = () => {
129
231
 
130
232
  //#endregion
131
233
  //#region src/v2/lib/utils.ts
132
- const twMerge$8 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
234
+ const twMerge$7 = (0, tailwind_merge.extendTailwindMerge)({ prefix: "cpk" });
133
235
  function cn(...inputs) {
134
- return twMerge$8((0, clsx.clsx)(inputs));
236
+ return twMerge$7((0, clsx.clsx)(inputs));
135
237
  }
136
238
 
137
239
  //#endregion
@@ -503,82 +605,11 @@ const CopilotChatAudioRecorder = (0, react.forwardRef)((props, ref) => {
503
605
  });
504
606
  CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
505
607
 
506
- //#endregion
507
- //#region src/v2/lib/slots.tsx
508
- /**
509
- * Shallow equality comparison for objects.
510
- */
511
- function shallowEqual(obj1, obj2) {
512
- const keys1 = Object.keys(obj1);
513
- const keys2 = Object.keys(obj2);
514
- if (keys1.length !== keys2.length) return false;
515
- for (const key of keys1) if (obj1[key] !== obj2[key]) return false;
516
- return true;
517
- }
518
- /**
519
- * Check if a value is a React component type (function, class, forwardRef, memo, etc.)
520
- */
521
- function isReactComponentType(value) {
522
- if (typeof value === "function") return true;
523
- if (value && typeof value === "object" && "$$typeof" in value && !react.default.isValidElement(value)) return true;
524
- return false;
525
- }
526
- /**
527
- * Internal function to render a slot value as a React element (non-memoized).
528
- */
529
- function renderSlotElement(slot, DefaultComponent, props) {
530
- if (typeof slot === "string") {
531
- const existingClassName = props.className;
532
- return react.default.createElement(DefaultComponent, {
533
- ...props,
534
- className: (0, tailwind_merge.twMerge)(existingClassName, slot)
535
- });
536
- }
537
- if (isReactComponentType(slot)) return react.default.createElement(slot, props);
538
- if (slot && typeof slot === "object" && !react.default.isValidElement(slot)) return react.default.createElement(DefaultComponent, {
539
- ...props,
540
- ...slot
541
- });
542
- return react.default.createElement(DefaultComponent, props);
543
- }
544
- /**
545
- * Internal memoized wrapper component for renderSlot.
546
- * Uses forwardRef to support ref forwarding.
547
- */
548
- const MemoizedSlotWrapper = react.default.memo(react.default.forwardRef(function MemoizedSlotWrapper(props, ref) {
549
- const { $slot, $component, ...rest } = props;
550
- return renderSlotElement($slot, $component, ref !== null ? {
551
- ...rest,
552
- ref
553
- } : rest);
554
- }), (prev, next) => {
555
- if (prev.$slot !== next.$slot) return false;
556
- if (prev.$component !== next.$component) return false;
557
- const { $slot: _ps, $component: _pc, ...prevRest } = prev;
558
- const { $slot: _ns, $component: _nc, ...nextRest } = next;
559
- return shallowEqual(prevRest, nextRest);
560
- });
561
- /**
562
- * Renders a slot value as a memoized React element.
563
- * Automatically prevents unnecessary re-renders using shallow prop comparison.
564
- * Supports ref forwarding.
565
- *
566
- * @example
567
- * renderSlot(customInput, CopilotChatInput, { onSubmit: handleSubmit })
568
- */
569
- function renderSlot(slot, DefaultComponent, props) {
570
- return react.default.createElement(MemoizedSlotWrapper, {
571
- ...props,
572
- $slot: slot,
573
- $component: DefaultComponent
574
- });
575
- }
576
-
577
608
  //#endregion
578
609
  //#region src/v2/components/chat/CopilotChatInput.tsx
579
610
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
580
611
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
581
- function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = true, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
612
+ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
582
613
  const isControlled = value !== void 0;
583
614
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
584
615
  (0, react.useEffect)(() => {
@@ -607,6 +638,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
607
638
  paddingLeft: 0,
608
639
  paddingRight: 0
609
640
  });
641
+ const containerCacheRef = (0, react.useRef)(null);
610
642
  const commandItems = (0, react.useMemo)(() => {
611
643
  const entries = [];
612
644
  const seen = /* @__PURE__ */ new Set();
@@ -651,7 +683,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
651
683
  previousModalStateRef.current = config?.isModalOpen;
652
684
  return;
653
685
  }
654
- if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus();
686
+ if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus({ preventScroll: true });
655
687
  previousModalStateRef.current = config?.isModalOpen;
656
688
  }, [config?.isModalOpen, autoFocus]);
657
689
  (0, react.useEffect)(() => {
@@ -894,6 +926,25 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
894
926
  return nextLayout;
895
927
  });
896
928
  }, []);
929
+ const updateContainerCache = (0, react.useCallback)(() => {
930
+ const grid = gridRef.current;
931
+ const addContainer = addButtonContainerRef.current;
932
+ const actionsContainer = actionsContainerRef.current;
933
+ if (!grid || !addContainer || !actionsContainer) return null;
934
+ const gridStyles = window.getComputedStyle(grid);
935
+ const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0;
936
+ const paddingRight = parseFloat(gridStyles.paddingRight) || 0;
937
+ const columnGap = parseFloat(gridStyles.columnGap) || 0;
938
+ const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight;
939
+ if (gridAvailableWidth <= 0) return null;
940
+ const addWidth = addContainer.getBoundingClientRect().width;
941
+ const actionsWidth = actionsContainer.getBoundingClientRect().width;
942
+ const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0);
943
+ if (compactWidth <= 0) return null;
944
+ const result = { compactWidth };
945
+ containerCacheRef.current = result;
946
+ return result;
947
+ }, []);
897
948
  const evaluateLayout = (0, react.useCallback)(() => {
898
949
  if (mode !== "input") {
899
950
  updateLayout("compact");
@@ -919,31 +970,31 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
919
970
  const renderedMultiline = baseline > 0 ? scrollHeight > baseline + 1 : false;
920
971
  let shouldExpand = hasExplicitBreak || renderedMultiline;
921
972
  if (!shouldExpand) {
922
- const gridStyles = window.getComputedStyle(grid);
923
- const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0;
924
- const paddingRight = parseFloat(gridStyles.paddingRight) || 0;
925
- const columnGap = parseFloat(gridStyles.columnGap) || 0;
926
- const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight;
927
- if (gridAvailableWidth > 0) {
928
- const addWidth = addContainer.getBoundingClientRect().width;
929
- const actionsWidth = actionsContainer.getBoundingClientRect().width;
930
- const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0);
931
- const canvas = measurementCanvasRef.current ?? document.createElement("canvas");
932
- if (!measurementCanvasRef.current) measurementCanvasRef.current = canvas;
933
- const context = canvas.getContext("2d");
934
- if (context) {
973
+ const cache = containerCacheRef.current ?? updateContainerCache();
974
+ if (cache && cache.compactWidth > 0) {
975
+ const compactInnerWidth = Math.max(cache.compactWidth - (measurementsRef.current.paddingLeft || 0) - (measurementsRef.current.paddingRight || 0), 0);
976
+ if (compactInnerWidth > 0) {
935
977
  const textareaStyles = window.getComputedStyle(textarea);
936
- context.font = textareaStyles.font || `${textareaStyles.fontStyle} ${textareaStyles.fontVariant} ${textareaStyles.fontWeight} ${textareaStyles.fontSize}/${textareaStyles.lineHeight} ${textareaStyles.fontFamily}`;
937
- const compactInnerWidth = Math.max(compactWidth - (measurementsRef.current.paddingLeft || 0) - (measurementsRef.current.paddingRight || 0), 0);
938
- if (compactInnerWidth > 0) {
939
- const lines = resolvedValue.length > 0 ? resolvedValue.split("\n") : [""];
940
- let longestWidth = 0;
941
- for (const line of lines) {
942
- const metrics = context.measureText(line || " ");
943
- if (metrics.width > longestWidth) longestWidth = metrics.width;
944
- }
945
- if (longestWidth > compactInnerWidth) shouldExpand = true;
978
+ let font = textareaStyles.font;
979
+ if (!font) {
980
+ const { fontStyle, fontVariant, fontWeight, fontSize, lineHeight, fontFamily } = textareaStyles;
981
+ if (fontSize && fontFamily) font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
946
982
  }
983
+ if (font?.trim()) {
984
+ const canvas = measurementCanvasRef.current ?? document.createElement("canvas");
985
+ if (!measurementCanvasRef.current) measurementCanvasRef.current = canvas;
986
+ const context = canvas.getContext("2d");
987
+ if (context) {
988
+ context.font = font;
989
+ const lines = resolvedValue.length > 0 ? resolvedValue.split("\n") : [""];
990
+ let longestWidth = 0;
991
+ for (const line of lines) {
992
+ const metrics = context.measureText(line || " ");
993
+ if (metrics.width > longestWidth) longestWidth = metrics.width;
994
+ }
995
+ if (longestWidth > compactInnerWidth) shouldExpand = true;
996
+ } else if (process.env.NODE_ENV !== "production") console.warn("[CopilotChatInput] canvas.getContext('2d') returned null. Text-width-based expansion will be unavailable.");
997
+ } else if (process.env.NODE_ENV !== "production") console.warn("[CopilotChatInput] Could not resolve textarea font for layout measurement. Text-width-based expansion will be skipped until the next evaluation.");
947
998
  }
948
999
  }
949
1000
  }
@@ -953,6 +1004,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
953
1004
  ensureMeasurements,
954
1005
  mode,
955
1006
  resolvedValue,
1007
+ updateContainerCache,
956
1008
  updateLayout
957
1009
  ]);
958
1010
  (0, react.useLayoutEffect)(() => {
@@ -965,11 +1017,17 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
965
1017
  const addContainer = addButtonContainerRef.current;
966
1018
  const actionsContainer = actionsContainerRef.current;
967
1019
  if (!textarea || !grid || !addContainer || !actionsContainer) return;
968
- const scheduleEvaluation = () => {
1020
+ const containerTargets = new Set([
1021
+ grid,
1022
+ addContainer,
1023
+ actionsContainer
1024
+ ]);
1025
+ const scheduleEvaluation = (invalidateCache) => {
969
1026
  if (ignoreResizeRef.current) {
970
1027
  ignoreResizeRef.current = false;
971
1028
  return;
972
1029
  }
1030
+ if (invalidateCache) containerCacheRef.current = null;
973
1031
  if (typeof window === "undefined") {
974
1032
  evaluateLayout();
975
1033
  return;
@@ -980,8 +1038,13 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
980
1038
  evaluateLayout();
981
1039
  });
982
1040
  };
983
- const observer = new ResizeObserver(() => {
984
- scheduleEvaluation();
1041
+ const observer = new ResizeObserver((entries) => {
1042
+ let shouldInvalidate = false;
1043
+ for (const entry of entries) if (containerTargets.has(entry.target)) {
1044
+ shouldInvalidate = true;
1045
+ break;
1046
+ }
1047
+ scheduleEvaluation(shouldInvalidate);
985
1048
  });
986
1049
  observer.observe(grid);
987
1050
  observer.observe(addContainer);
@@ -1126,6 +1189,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1126
1189
  });
1127
1190
  _CopilotChatInput.AddMenuButton = ({ className, toolsMenu, onAddFile, disabled, ...props }) => {
1128
1191
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1192
+ const [mounted, setMounted] = (0, react.useState)(false);
1193
+ (0, react.useEffect)(() => setMounted(true), []);
1129
1194
  const menuItems = (0, react.useMemo)(() => {
1130
1195
  const items = [];
1131
1196
  if (onAddFile) items.push({
@@ -1156,26 +1221,28 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1156
1221
  }), []);
1157
1222
  const hasMenuItems = menuItems.length > 0;
1158
1223
  const isDisabled = disabled || !hasMenuItems;
1224
+ const button = /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1225
+ type: "button",
1226
+ "data-testid": "copilot-add-menu-button",
1227
+ variant: "chatInputToolbarSecondary",
1228
+ size: "chatInputToolbarIcon",
1229
+ className: (0, tailwind_merge.twMerge)("cpk:ml-1", className),
1230
+ disabled: isDisabled,
1231
+ ...props,
1232
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "cpk:size-[20px]" })
1233
+ });
1234
+ if (!mounted) return button;
1159
1235
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(DropdownMenu, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Tooltip, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipTrigger, {
1160
1236
  asChild: true,
1161
1237
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropdownMenuTrigger, {
1162
1238
  asChild: true,
1163
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
1164
- type: "button",
1165
- "data-testid": "copilot-add-menu-button",
1166
- variant: "chatInputToolbarSecondary",
1167
- size: "chatInputToolbarIcon",
1168
- className: (0, tailwind_merge.twMerge)("cpk:ml-1", className),
1169
- disabled: isDisabled,
1170
- ...props,
1171
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Plus, { className: "cpk:size-[20px]" })
1172
- })
1239
+ children: button
1173
1240
  })
1174
1241
  }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TooltipContent, {
1175
1242
  side: "bottom",
1176
1243
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", {
1177
1244
  className: "cpk:flex cpk:items-center cpk:gap-1 cpk:text-xs cpk:font-medium",
1178
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Add files and more" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1245
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Add attachments" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("code", {
1179
1246
  className: "cpk:rounded cpk:bg-[#4a4a4a] cpk:px-1 cpk:py-[1px] cpk:font-mono cpk:text-[11px] cpk:text-white cpk:dark:bg-[#e0e0e0] cpk:dark:text-black",
1180
1247
  children: "/"
1181
1248
  })]
@@ -1191,21 +1258,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1191
1258
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1192
1259
  (0, react.useImperativeHandle)(ref, () => internalTextareaRef.current);
1193
1260
  (0, react.useEffect)(() => {
1194
- const textarea = internalTextareaRef.current;
1195
- if (!textarea) return;
1196
- const handleFocus = () => {
1197
- setTimeout(() => {
1198
- textarea.scrollIntoView({
1199
- behavior: "smooth",
1200
- block: "nearest"
1201
- });
1202
- }, 300);
1203
- };
1204
- textarea.addEventListener("focus", handleFocus);
1205
- return () => textarea.removeEventListener("focus", handleFocus);
1206
- }, []);
1207
- (0, react.useEffect)(() => {
1208
- if (autoFocus) internalTextareaRef.current?.focus();
1261
+ if (autoFocus) internalTextareaRef.current?.focus({ preventScroll: true });
1209
1262
  }, [autoFocus]);
1210
1263
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", {
1211
1264
  ref: internalTextareaRef,
@@ -1912,8 +1965,411 @@ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, agen
1912
1965
  });
1913
1966
  };
1914
1967
 
1968
+ //#endregion
1969
+ //#region src/v2/providers/SandboxFunctionsContext.ts
1970
+ const SandboxFunctionsContext = (0, react.createContext)([]);
1971
+ function useSandboxFunctions() {
1972
+ return (0, react.useContext)(SandboxFunctionsContext);
1973
+ }
1974
+
1975
+ //#endregion
1976
+ //#region src/v2/lib/processPartialHtml.ts
1977
+ /**
1978
+ * Extracts all complete `<style>` blocks from the raw HTML.
1979
+ * Returns the concatenated style tags, suitable for injection into `<head>`.
1980
+ */
1981
+ function extractCompleteStyles(html) {
1982
+ const matches = html.match(/<style\b[^>]*>[\s\S]*?<\/style>/gi);
1983
+ return matches ? matches.join("") : "";
1984
+ }
1985
+ /**
1986
+ * Processes raw accumulated HTML for safe preview via innerHTML injection.
1987
+ * Pure function, no DOM dependencies.
1988
+ *
1989
+ * Pipeline (order matters):
1990
+ * 1. Strip incomplete tag at end
1991
+ * 2. Strip complete <style>, <script>, and <head> blocks
1992
+ * 3. Strip incomplete <style>/<script>/<head> blocks
1993
+ * 4. Strip incomplete HTML entities
1994
+ * 5. Extract body content (or use full string if no <body>)
1995
+ */
1996
+ function processPartialHtml(html) {
1997
+ let result = html;
1998
+ result = result.replace(/<[^>]*$/, "");
1999
+ result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
2000
+ result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*$/gi, "");
2001
+ result = result.replace(/&[a-zA-Z0-9#]*$/, "");
2002
+ const bodyMatch = result.match(/<body[^>]*>([\s\S]*)/i);
2003
+ if (bodyMatch) {
2004
+ result = bodyMatch[1];
2005
+ result = result.replace(/<\/body>[\s\S]*/i, "");
2006
+ }
2007
+ return result;
2008
+ }
2009
+
2010
+ //#endregion
2011
+ //#region src/v2/components/OpenGenerativeUIRenderer.tsx
2012
+ const OpenGenerativeUIActivityType = "open-generative-ui";
2013
+ const OpenGenerativeUIContentSchema = zod.z.object({
2014
+ initialHeight: zod.z.number().optional(),
2015
+ generating: zod.z.boolean().optional(),
2016
+ css: zod.z.string().optional(),
2017
+ cssComplete: zod.z.boolean().optional(),
2018
+ html: zod.z.array(zod.z.string()).optional(),
2019
+ htmlComplete: zod.z.boolean().optional(),
2020
+ jsFunctions: zod.z.string().optional(),
2021
+ jsFunctionsComplete: zod.z.boolean().optional(),
2022
+ jsExpressions: zod.z.array(zod.z.string()).optional(),
2023
+ jsExpressionsComplete: zod.z.boolean().optional()
2024
+ });
2025
+ /**
2026
+ * Schema for the generateSandboxedUi tool call arguments.
2027
+ * Used by the frontend tool renderer to display placeholder messages.
2028
+ */
2029
+ const GenerateSandboxedUiArgsSchema = zod.z.object({
2030
+ initialHeight: zod.z.number().optional(),
2031
+ placeholderMessages: zod.z.array(zod.z.string()).optional(),
2032
+ css: zod.z.string().optional(),
2033
+ html: zod.z.string().optional(),
2034
+ jsFunctions: zod.z.string().optional(),
2035
+ jsExpressions: zod.z.array(zod.z.string()).optional()
2036
+ });
2037
+ const THROTTLE_MS = 1e3;
2038
+ /**
2039
+ * Returns true when the inner component should re-render immediately
2040
+ * (no throttle delay).
2041
+ */
2042
+ function shouldFlushImmediately(prev, next) {
2043
+ if (next.cssComplete && (!prev || !prev.cssComplete)) return true;
2044
+ if (next.htmlComplete) return true;
2045
+ if (next.generating === false) return true;
2046
+ if (next.jsFunctions && (!prev || !prev.jsFunctions)) return true;
2047
+ if ((next.jsExpressions?.length ?? 0) > (prev?.jsExpressions?.length ?? 0)) return true;
2048
+ if (next.html?.length && (!prev || !prev.html?.length)) return true;
2049
+ return false;
2050
+ }
2051
+ /**
2052
+ * Outer wrapper — absorbs every parent re-render but only forwards
2053
+ * throttled content snapshots to the memoized inner component.
2054
+ */
2055
+ const OpenGenerativeUIActivityRenderer = function OpenGenerativeUIActivityRenderer({ content }) {
2056
+ const latestContentRef = (0, react.useRef)(content);
2057
+ latestContentRef.current = content;
2058
+ const [throttledContent, setThrottledContent] = (0, react.useState)(content);
2059
+ const throttledContentRef = (0, react.useRef)(throttledContent);
2060
+ const timerRef = (0, react.useRef)(null);
2061
+ if (throttledContentRef.current !== content) {
2062
+ if (shouldFlushImmediately(throttledContentRef.current, content)) {
2063
+ if (timerRef.current !== null) {
2064
+ clearTimeout(timerRef.current);
2065
+ timerRef.current = null;
2066
+ }
2067
+ throttledContentRef.current = content;
2068
+ setThrottledContent(content);
2069
+ }
2070
+ }
2071
+ const flush = (0, react.useCallback)(() => {
2072
+ timerRef.current = null;
2073
+ const latest = latestContentRef.current;
2074
+ throttledContentRef.current = latest;
2075
+ setThrottledContent(latest);
2076
+ }, []);
2077
+ (0, react.useEffect)(() => {
2078
+ if (throttledContentRef.current === content) return;
2079
+ if (timerRef.current === null) timerRef.current = setTimeout(flush, THROTTLE_MS);
2080
+ }, [content, flush]);
2081
+ (0, react.useEffect)(() => {
2082
+ return () => {
2083
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
2084
+ };
2085
+ }, []);
2086
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(OpenGenerativeUIActivityRendererInner, { content: throttledContent });
2087
+ };
2088
+ function ensureHead(html) {
2089
+ if (/<head[\s>]/i.test(html)) return html;
2090
+ return `<head></head>${html}`;
2091
+ }
2092
+ function injectCssIntoHtml(html, css) {
2093
+ const headCloseIdx = html.indexOf("</head>");
2094
+ if (headCloseIdx !== -1) return html.slice(0, headCloseIdx) + `<style>${css}</style>` + html.slice(headCloseIdx);
2095
+ return `<head><style>${css}</style></head>${html}`;
2096
+ }
2097
+ const OpenGenerativeUIActivityRendererInner = react.default.memo(function OpenGenerativeUIActivityRendererInner({ content }) {
2098
+ const initialHeight = content.initialHeight ?? 200;
2099
+ const [autoHeight, setAutoHeight] = (0, react.useState)(null);
2100
+ const sandboxFunctions = useSandboxFunctions();
2101
+ const localApi = (0, react.useMemo)(() => {
2102
+ const api = {};
2103
+ for (const fn of sandboxFunctions) api[fn.name] = fn.handler;
2104
+ return api;
2105
+ }, [sandboxFunctions]);
2106
+ const fullHtml = content.htmlComplete && content.html?.length ? content.html.join("") : void 0;
2107
+ const css = content.cssComplete ? content.css : void 0;
2108
+ const cssReady = !!content.cssComplete;
2109
+ const partialHtml = !content.htmlComplete && content.html?.length ? content.html.join("") : void 0;
2110
+ const previewBody = partialHtml ? processPartialHtml(partialHtml) : void 0;
2111
+ const previewStyles = partialHtml ? extractCompleteStyles(partialHtml) : "";
2112
+ const hasPreview = cssReady && !!previewBody?.trim();
2113
+ const hasVisibleSandbox = !!fullHtml || hasPreview;
2114
+ const containerRef = (0, react.useRef)(null);
2115
+ const sandboxRef = (0, react.useRef)(null);
2116
+ const previewSandboxRef = (0, react.useRef)(null);
2117
+ const previewReadyRef = (0, react.useRef)(false);
2118
+ const sandboxReadyRef = (0, react.useRef)(false);
2119
+ const executedIndexRef = (0, react.useRef)(0);
2120
+ const pendingQueueRef = (0, react.useRef)([]);
2121
+ const jsFunctionsInjectedRef = (0, react.useRef)(false);
2122
+ (0, react.useEffect)(() => {
2123
+ const container = containerRef.current;
2124
+ if (!container || fullHtml || !hasPreview || previewSandboxRef.current) return;
2125
+ let cancelled = false;
2126
+ import("@jetbrains/websandbox").then((mod) => {
2127
+ if (cancelled) return;
2128
+ const sandbox = (mod.default?.default ?? mod.default).create({}, {
2129
+ frameContainer: container,
2130
+ frameContent: "<head></head><body></body>",
2131
+ allowAdditionalAttributes: ""
2132
+ });
2133
+ previewSandboxRef.current = sandbox;
2134
+ sandbox.iframe.style.width = "100%";
2135
+ sandbox.iframe.style.height = "100%";
2136
+ sandbox.iframe.style.border = "none";
2137
+ sandbox.iframe.style.backgroundColor = "transparent";
2138
+ sandbox.promise.then(() => {
2139
+ if (cancelled) return;
2140
+ previewReadyRef.current = true;
2141
+ sandbox.run(`
2142
+ var s = document.createElement('style');
2143
+ s.textContent = 'html, body { overflow: hidden !important; }';
2144
+ document.head.appendChild(s);
2145
+ `);
2146
+ const headParts = [];
2147
+ if (css) headParts.push(`<style>${css}</style>`);
2148
+ if (previewStyles) headParts.push(previewStyles);
2149
+ if (headParts.length) sandbox.run(`document.head.innerHTML = ${JSON.stringify(headParts.join(""))}`);
2150
+ if (previewBody) sandbox.run(`document.body.innerHTML = ${JSON.stringify(previewBody)}`);
2151
+ });
2152
+ }).catch((err) => {
2153
+ console.error("[OpenGenerativeUI] Failed to load sandbox module:", err);
2154
+ });
2155
+ return () => {
2156
+ cancelled = true;
2157
+ };
2158
+ }, [hasPreview, fullHtml]);
2159
+ (0, react.useEffect)(() => {
2160
+ if (!previewSandboxRef.current || !previewReadyRef.current) return;
2161
+ const headParts = [];
2162
+ if (css) headParts.push(`<style>${css}</style>`);
2163
+ if (previewStyles) headParts.push(previewStyles);
2164
+ if (headParts.length) previewSandboxRef.current.run(`document.head.innerHTML = ${JSON.stringify(headParts.join(""))}`);
2165
+ if (!previewBody) return;
2166
+ previewSandboxRef.current.run(`document.body.innerHTML = ${JSON.stringify(previewBody)}`);
2167
+ }, [
2168
+ previewBody,
2169
+ previewStyles,
2170
+ css
2171
+ ]);
2172
+ (0, react.useEffect)(() => {
2173
+ const container = containerRef.current;
2174
+ if (!container || !fullHtml) return;
2175
+ if (previewSandboxRef.current) {
2176
+ previewSandboxRef.current.destroy();
2177
+ previewSandboxRef.current = null;
2178
+ previewReadyRef.current = false;
2179
+ }
2180
+ let cancelled = false;
2181
+ executedIndexRef.current = 0;
2182
+ jsFunctionsInjectedRef.current = false;
2183
+ sandboxReadyRef.current = false;
2184
+ pendingQueueRef.current = [];
2185
+ const htmlContent = css ? injectCssIntoHtml(fullHtml, css) : fullHtml;
2186
+ import("@jetbrains/websandbox").then((mod) => {
2187
+ if (cancelled) return;
2188
+ const sandbox = (mod.default?.default ?? mod.default).create(localApi, {
2189
+ frameContainer: container,
2190
+ frameContent: ensureHead(htmlContent),
2191
+ allowAdditionalAttributes: ""
2192
+ });
2193
+ sandboxRef.current = sandbox;
2194
+ sandbox.iframe.style.width = "100%";
2195
+ sandbox.iframe.style.height = "100%";
2196
+ sandbox.iframe.style.border = "none";
2197
+ sandbox.iframe.style.backgroundColor = "transparent";
2198
+ sandbox.promise.then(() => {
2199
+ if (cancelled) return;
2200
+ sandboxReadyRef.current = true;
2201
+ sandbox.run(`
2202
+ var s = document.createElement('style');
2203
+ s.textContent = 'html, body { overflow: hidden !important; }';
2204
+ document.head.appendChild(s);
2205
+ `);
2206
+ const queue = pendingQueueRef.current;
2207
+ pendingQueueRef.current = [];
2208
+ for (const code of queue) sandbox.run(code);
2209
+ });
2210
+ }).catch((err) => {
2211
+ console.error("[OpenGenerativeUI] Failed to load sandbox module:", err);
2212
+ });
2213
+ return () => {
2214
+ cancelled = true;
2215
+ if (previewSandboxRef.current) {
2216
+ previewSandboxRef.current.destroy();
2217
+ previewSandboxRef.current = null;
2218
+ previewReadyRef.current = false;
2219
+ }
2220
+ if (sandboxRef.current) {
2221
+ sandboxRef.current.destroy();
2222
+ sandboxRef.current = null;
2223
+ }
2224
+ sandboxReadyRef.current = false;
2225
+ setAutoHeight(null);
2226
+ };
2227
+ }, [
2228
+ fullHtml,
2229
+ css,
2230
+ localApi
2231
+ ]);
2232
+ (0, react.useEffect)(() => {
2233
+ if (!content.jsFunctions || jsFunctionsInjectedRef.current) return;
2234
+ jsFunctionsInjectedRef.current = true;
2235
+ const sandbox = sandboxRef.current;
2236
+ if (sandboxReadyRef.current && sandbox) sandbox.run(content.jsFunctions);
2237
+ else pendingQueueRef.current.push(content.jsFunctions);
2238
+ }, [content.jsFunctions]);
2239
+ (0, react.useEffect)(() => {
2240
+ const expressions = content.jsExpressions;
2241
+ if (!expressions || expressions.length === 0) return;
2242
+ const startIndex = executedIndexRef.current;
2243
+ if (startIndex >= expressions.length) return;
2244
+ const newExprs = expressions.slice(startIndex);
2245
+ executedIndexRef.current = expressions.length;
2246
+ const sandbox = sandboxRef.current;
2247
+ if (sandboxReadyRef.current && sandbox) (async () => {
2248
+ for (const expr of newExprs) await sandbox.run(expr);
2249
+ })();
2250
+ else pendingQueueRef.current.push(...newExprs);
2251
+ }, [content.jsExpressions?.length]);
2252
+ const generationDone = content.generating === false;
2253
+ (0, react.useEffect)(() => {
2254
+ const sandbox = sandboxRef.current;
2255
+ if (!generationDone || !sandbox) return;
2256
+ let handled = false;
2257
+ const onMessage = (e) => {
2258
+ if (handled) return;
2259
+ if (e.source === sandbox.iframe.contentWindow && e.data?.type === "__ck_resize") {
2260
+ handled = true;
2261
+ setAutoHeight(e.data.height);
2262
+ window.removeEventListener("message", onMessage);
2263
+ }
2264
+ };
2265
+ window.addEventListener("message", onMessage);
2266
+ const measureOnce = `
2267
+ (function() {
2268
+ var s = document.createElement('style');
2269
+ s.textContent = 'body { height: auto !important; min-height: 0 !important; }';
2270
+ document.head.appendChild(s);
2271
+ var h = document.body.scrollHeight;
2272
+ var cs = getComputedStyle(document.body);
2273
+ h += parseFloat(cs.marginTop) || 0;
2274
+ h += parseFloat(cs.marginBottom) || 0;
2275
+ s.remove();
2276
+ parent.postMessage({ type: "__ck_resize", height: Math.ceil(h) }, "*");
2277
+ })();
2278
+ `;
2279
+ if (sandboxReadyRef.current) sandbox.run(measureOnce);
2280
+ else pendingQueueRef.current.push(measureOnce);
2281
+ return () => {
2282
+ window.removeEventListener("message", onMessage);
2283
+ };
2284
+ }, [generationDone]);
2285
+ const height = autoHeight ?? initialHeight;
2286
+ const isGenerating = content.generating !== false;
2287
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2288
+ ref: containerRef,
2289
+ style: {
2290
+ position: "relative",
2291
+ width: "100%",
2292
+ height: `${height}px`,
2293
+ borderRadius: "8px",
2294
+ backgroundColor: hasVisibleSandbox ? "transparent" : "#f5f5f5",
2295
+ border: hasVisibleSandbox ? "none" : "1px solid #e0e0e0",
2296
+ display: hasVisibleSandbox ? "block" : "flex",
2297
+ alignItems: hasVisibleSandbox ? void 0 : "center",
2298
+ justifyContent: hasVisibleSandbox ? void 0 : "center",
2299
+ overflow: "hidden"
2300
+ },
2301
+ children: isGenerating && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2302
+ style: {
2303
+ position: "absolute",
2304
+ inset: 0,
2305
+ zIndex: 10,
2306
+ pointerEvents: "all",
2307
+ backgroundColor: "rgba(255, 255, 255, 0.5)",
2308
+ display: "flex",
2309
+ alignItems: "center",
2310
+ justifyContent: "center"
2311
+ },
2312
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("svg", {
2313
+ width: "48",
2314
+ height: "48",
2315
+ viewBox: "0 0 24 24",
2316
+ fill: "none",
2317
+ style: { animation: "ck-spin 1s linear infinite" },
2318
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("circle", {
2319
+ cx: "12",
2320
+ cy: "12",
2321
+ r: "10",
2322
+ stroke: "#e0e0e0",
2323
+ strokeWidth: "3"
2324
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
2325
+ d: "M12 2a10 10 0 0 1 10 10",
2326
+ stroke: "#999",
2327
+ strokeWidth: "3",
2328
+ strokeLinecap: "round"
2329
+ })]
2330
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `@keyframes ck-spin { to { transform: rotate(360deg) } }` })]
2331
+ })
2332
+ });
2333
+ }, (prev, next) => prev.content === next.content);
2334
+ /**
2335
+ * Frontend tool renderer for generateSandboxedUi.
2336
+ * Displays placeholder messages while the UI is being generated.
2337
+ */
2338
+ const OpenGenerativeUIToolRenderer = function OpenGenerativeUIToolRenderer(props) {
2339
+ const [visibleMessageIndex, setVisibleMessageIndex] = (0, react.useState)(0);
2340
+ const prevMessageCountRef = (0, react.useRef)(0);
2341
+ const messages = props.args.placeholderMessages;
2342
+ (0, react.useEffect)(() => {
2343
+ if (!messages || messages.length === 0) return;
2344
+ if (messages.length !== prevMessageCountRef.current) {
2345
+ prevMessageCountRef.current = messages.length;
2346
+ setVisibleMessageIndex(messages.length - 1);
2347
+ }
2348
+ if (props.status === _copilotkit_core.ToolCallStatus.Complete) return;
2349
+ const timer = setInterval(() => {
2350
+ setVisibleMessageIndex((i) => (i + 1) % messages.length);
2351
+ }, 5e3);
2352
+ return () => clearInterval(timer);
2353
+ }, [messages?.length, props.status]);
2354
+ if (props.status === _copilotkit_core.ToolCallStatus.Complete) return null;
2355
+ if (!messages || messages.length === 0) return null;
2356
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2357
+ style: {
2358
+ padding: "8px 12px",
2359
+ color: "#999",
2360
+ fontSize: "14px"
2361
+ },
2362
+ children: messages[visibleMessageIndex] ?? messages[0]
2363
+ });
2364
+ };
2365
+
1915
2366
  //#endregion
1916
2367
  //#region src/v2/a2ui/A2UIMessageRenderer.tsx
2368
+ /**
2369
+ * The container key used to wrap A2UI operations for explicit detection.
2370
+ * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
2371
+ */
2372
+ const A2UI_OPERATIONS_KEY = "a2ui_operations";
1917
2373
  let initialized = false;
1918
2374
  function ensureInitialized() {
1919
2375
  if (!initialized) {
@@ -1923,25 +2379,23 @@ function ensureInitialized() {
1923
2379
  }
1924
2380
  }
1925
2381
  function createA2UIMessageRenderer(options) {
1926
- const { theme } = options;
2382
+ const { theme, catalog, loadingComponent } = options;
1927
2383
  return {
1928
2384
  activityType: "a2ui-surface",
1929
2385
  content: zod.z.any(),
1930
2386
  render: ({ content, agent }) => {
1931
2387
  ensureInitialized();
1932
2388
  const [operations, setOperations] = (0, react.useState)([]);
1933
- const lastSignatureRef = (0, react.useRef)(null);
1934
2389
  const { copilotkit } = useCopilotKit();
2390
+ const lastContentRef = (0, react.useRef)(null);
1935
2391
  (0, react.useEffect)(() => {
1936
- if (!content || !Array.isArray(content.operations)) {
1937
- lastSignatureRef.current = null;
2392
+ if (content === lastContentRef.current) return;
2393
+ lastContentRef.current = content;
2394
+ const incoming = content?.[A2UI_OPERATIONS_KEY];
2395
+ if (!content || !Array.isArray(incoming)) {
1938
2396
  setOperations([]);
1939
2397
  return;
1940
2398
  }
1941
- const incoming = content.operations;
1942
- const signature = stringifyOperations(incoming);
1943
- if (signature && signature === lastSignatureRef.current) return;
1944
- lastSignatureRef.current = signature;
1945
2399
  setOperations(incoming);
1946
2400
  }, [content]);
1947
2401
  const groupedOperations = (0, react.useMemo)(() => {
@@ -1953,7 +2407,7 @@ function createA2UIMessageRenderer(options) {
1953
2407
  }
1954
2408
  return groups;
1955
2409
  }, [operations]);
1956
- if (!groupedOperations.size) return null;
2410
+ if (!groupedOperations.size) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(loadingComponent ?? DefaultA2UILoading, {});
1957
2411
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1958
2412
  className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
1959
2413
  children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReactSurfaceHost, {
@@ -1961,7 +2415,8 @@ function createA2UIMessageRenderer(options) {
1961
2415
  operations: ops,
1962
2416
  theme,
1963
2417
  agent,
1964
- copilotkit
2418
+ copilotkit,
2419
+ catalog
1965
2420
  }, surfaceId))
1966
2421
  });
1967
2422
  }
@@ -1971,14 +2426,14 @@ function createA2UIMessageRenderer(options) {
1971
2426
  * Renders a single A2UI surface using the React renderer.
1972
2427
  * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
1973
2428
  */
1974
- function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit }) {
2429
+ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog }) {
1975
2430
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1976
2431
  className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
1977
2432
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(_copilotkit_a2ui_renderer.A2UIProvider, {
1978
2433
  onAction: (0, react.useCallback)(async (message) => {
1979
2434
  if (!agent) return;
2435
+ message.userAction;
1980
2436
  try {
1981
- console.info("[A2UI] Action dispatched", message.userAction);
1982
2437
  copilotkit.setProperties({
1983
2438
  ...copilotkit.properties ?? {},
1984
2439
  a2uiAction: message
@@ -1992,46 +2447,500 @@ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit }) {
1992
2447
  }
1993
2448
  }, [agent, copilotkit]),
1994
2449
  theme,
2450
+ catalog,
1995
2451
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SurfaceMessageProcessor, {
1996
2452
  surfaceId,
1997
2453
  operations
1998
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
1999
- surfaceId,
2000
- className: "cpk:flex cpk:flex-1"
2001
- })]
2454
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UISurfaceOrError, { surfaceId })]
2002
2455
  })
2003
2456
  });
2004
2457
  }
2005
2458
  /**
2459
+ * Renders the A2UI surface, or an error message if processing failed.
2460
+ * Must be a child of A2UIProvider to access the error state.
2461
+ */
2462
+ function A2UISurfaceOrError({ surfaceId }) {
2463
+ const error = (0, _copilotkit_a2ui_renderer.useA2UIError)();
2464
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2465
+ className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
2466
+ children: ["A2UI render error: ", error]
2467
+ });
2468
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_copilotkit_a2ui_renderer.A2UIRenderer, {
2469
+ surfaceId,
2470
+ className: "cpk:flex cpk:flex-1"
2471
+ });
2472
+ }
2473
+ /**
2006
2474
  * Processes A2UI operations into the provider's message processor.
2007
2475
  * Must be a child of A2UIProvider to access the actions context.
2008
2476
  */
2009
2477
  function SurfaceMessageProcessor({ surfaceId, operations }) {
2010
- const { processMessages } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
2011
- const lastProcessedRef = (0, react.useRef)("");
2478
+ const { processMessages, getSurface } = (0, _copilotkit_a2ui_renderer.useA2UIActions)();
2479
+ const lastHashRef = (0, react.useRef)("");
2012
2480
  (0, react.useEffect)(() => {
2013
- const key = `${surfaceId}-${JSON.stringify(operations)}`;
2014
- if (key === lastProcessedRef.current) return;
2015
- lastProcessedRef.current = key;
2016
- processMessages(operations);
2481
+ const hash = JSON.stringify(operations);
2482
+ if (hash === lastHashRef.current) return;
2483
+ lastHashRef.current = hash;
2484
+ processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
2017
2485
  }, [
2018
2486
  processMessages,
2487
+ getSurface,
2019
2488
  surfaceId,
2020
2489
  operations
2021
2490
  ]);
2022
2491
  return null;
2023
2492
  }
2493
+ /**
2494
+ * Default loading component shown while an A2UI surface is generating.
2495
+ * Displays an animated shimmer skeleton.
2496
+ */
2497
+ function DefaultA2UILoading() {
2498
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2499
+ className: "cpk:flex cpk:flex-col cpk:gap-3 cpk:rounded-xl cpk:border cpk:border-gray-100 cpk:bg-gray-50/50 cpk:p-5",
2500
+ style: { minHeight: 120 },
2501
+ children: [
2502
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2503
+ className: "cpk:flex cpk:items-center cpk:gap-2",
2504
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2505
+ className: "cpk:h-3 cpk:w-3 cpk:rounded-full cpk:bg-gray-200",
2506
+ style: { animation: "cpk-a2ui-pulse 1.5s ease-in-out infinite" }
2507
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2508
+ className: "cpk:text-xs cpk:font-medium cpk:text-gray-400",
2509
+ children: "Generating UI..."
2510
+ })]
2511
+ }),
2512
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2513
+ className: "cpk:flex cpk:flex-col cpk:gap-2",
2514
+ children: [
2515
+ .8,
2516
+ .6,
2517
+ .4
2518
+ ].map((width, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2519
+ className: "cpk:h-3 cpk:rounded cpk:bg-gray-200/70",
2520
+ style: {
2521
+ width: `${width * 100}%`,
2522
+ animation: `cpk-a2ui-pulse 1.5s ease-in-out ${i * .15}s infinite`
2523
+ }
2524
+ }, i))
2525
+ }),
2526
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
2527
+ @keyframes cpk-a2ui-pulse {
2528
+ 0%, 100% { opacity: 0.4; }
2529
+ 50% { opacity: 1; }
2530
+ }
2531
+ ` })
2532
+ ]
2533
+ });
2534
+ }
2024
2535
  function getOperationSurfaceId(operation) {
2025
2536
  if (!operation || typeof operation !== "object") return null;
2026
2537
  if (typeof operation.surfaceId === "string") return operation.surfaceId;
2027
- return operation?.beginRendering?.surfaceId ?? operation?.surfaceUpdate?.surfaceId ?? operation?.dataModelUpdate?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
2538
+ return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
2028
2539
  }
2029
- function stringifyOperations(ops) {
2030
- try {
2031
- return JSON.stringify(ops);
2032
- } catch (error) {
2033
- return null;
2540
+
2541
+ //#endregion
2542
+ //#region src/v2/types/defineToolCallRenderer.ts
2543
+ function defineToolCallRenderer(def) {
2544
+ const argsSchema = def.name === "*" && !def.args ? zod.z.any() : def.args;
2545
+ return {
2546
+ name: def.name,
2547
+ args: argsSchema,
2548
+ render: def.render,
2549
+ ...def.agentId ? { agentId: def.agentId } : {}
2550
+ };
2551
+ }
2552
+
2553
+ //#endregion
2554
+ //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
2555
+ /**
2556
+ * Tool name used by the dynamic A2UI generation secondary LLM.
2557
+ * This renderer is auto-registered when A2UI is enabled.
2558
+ */
2559
+ const RENDER_A2UI_TOOL_NAME = "render_a2ui";
2560
+ /**
2561
+ * Built-in progress indicator for dynamic A2UI generation.
2562
+ * Shows a skeleton wireframe that progressively reveals as tokens stream in.
2563
+ *
2564
+ * Registered automatically when A2UI is enabled. Users can override by
2565
+ * providing their own `useRenderTool({ name: "render_a2ui", ... })`.
2566
+ */
2567
+ function A2UIProgressIndicator({ parameters }) {
2568
+ const lastRef = (0, react.useRef)({
2569
+ time: 0,
2570
+ tokens: 0
2571
+ });
2572
+ const now = Date.now();
2573
+ let { tokens } = lastRef.current;
2574
+ if (now - lastRef.current.time > 200) {
2575
+ const chars = JSON.stringify(parameters ?? {}).length;
2576
+ tokens = Math.round(chars / 4);
2577
+ lastRef.current = {
2578
+ time: now,
2579
+ tokens
2580
+ };
2034
2581
  }
2582
+ const phase = tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
2583
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2584
+ style: {
2585
+ margin: "12px 0",
2586
+ maxWidth: 320
2587
+ },
2588
+ children: [
2589
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2590
+ style: {
2591
+ position: "relative",
2592
+ overflow: "hidden",
2593
+ borderRadius: 12,
2594
+ border: "1px solid rgba(228,228,231,0.8)",
2595
+ backgroundColor: "#fff",
2596
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2597
+ padding: "16px 18px 14px"
2598
+ },
2599
+ children: [
2600
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2601
+ style: {
2602
+ display: "flex",
2603
+ alignItems: "center",
2604
+ gap: 8,
2605
+ marginBottom: 12
2606
+ },
2607
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2608
+ style: {
2609
+ display: "flex",
2610
+ gap: 4
2611
+ },
2612
+ children: [
2613
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2614
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2615
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {})
2616
+ ]
2617
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2618
+ w: 64,
2619
+ h: 6,
2620
+ bg: "#e4e4e7",
2621
+ opacity: phase >= 1 ? 1 : .4,
2622
+ transition: "opacity 0.5s"
2623
+ })]
2624
+ }),
2625
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2626
+ style: {
2627
+ display: "grid",
2628
+ gap: 7
2629
+ },
2630
+ children: [
2631
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2632
+ show: phase >= 0,
2633
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2634
+ w: 36,
2635
+ h: 7,
2636
+ bg: "rgba(147,197,253,0.7)",
2637
+ anim: 0
2638
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2639
+ w: 80,
2640
+ h: 7,
2641
+ bg: "rgba(219,234,254,0.8)",
2642
+ anim: .2
2643
+ })]
2644
+ }),
2645
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2646
+ show: phase >= 0,
2647
+ delay: .1,
2648
+ children: [
2649
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spacer, {}),
2650
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2651
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2652
+ w: 100,
2653
+ h: 7,
2654
+ bg: "rgba(24,24,27,0.2)",
2655
+ anim: .3
2656
+ })
2657
+ ]
2658
+ }),
2659
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2660
+ show: phase >= 1,
2661
+ delay: .15,
2662
+ children: [
2663
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spacer, {}),
2664
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2665
+ w: 48,
2666
+ h: 7,
2667
+ bg: "rgba(24,24,27,0.15)",
2668
+ anim: .1
2669
+ }),
2670
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2671
+ w: 40,
2672
+ h: 7,
2673
+ bg: "rgba(153,246,228,0.6)",
2674
+ anim: .5
2675
+ }),
2676
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2677
+ w: 56,
2678
+ h: 7,
2679
+ bg: "rgba(147,197,253,0.6)",
2680
+ anim: .3
2681
+ })
2682
+ ]
2683
+ }),
2684
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2685
+ show: phase >= 1,
2686
+ delay: .2,
2687
+ children: [
2688
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Spacer, {}),
2689
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2690
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2691
+ w: 60,
2692
+ h: 7,
2693
+ bg: "rgba(24,24,27,0.15)",
2694
+ anim: .4
2695
+ })
2696
+ ]
2697
+ }),
2698
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2699
+ show: phase >= 2,
2700
+ delay: .25,
2701
+ children: [
2702
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2703
+ w: 40,
2704
+ h: 7,
2705
+ bg: "rgba(153,246,228,0.5)",
2706
+ anim: .2
2707
+ }),
2708
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2709
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2710
+ w: 48,
2711
+ h: 7,
2712
+ bg: "rgba(24,24,27,0.15)",
2713
+ anim: .6
2714
+ }),
2715
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2716
+ w: 64,
2717
+ h: 7,
2718
+ bg: "rgba(147,197,253,0.5)",
2719
+ anim: .1
2720
+ })
2721
+ ]
2722
+ }),
2723
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2724
+ show: phase >= 2,
2725
+ delay: .3,
2726
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2727
+ w: 36,
2728
+ h: 7,
2729
+ bg: "rgba(147,197,253,0.6)",
2730
+ anim: .5
2731
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2732
+ w: 36,
2733
+ h: 7,
2734
+ bg: "rgba(24,24,27,0.12)",
2735
+ anim: .7
2736
+ })]
2737
+ }),
2738
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(Row, {
2739
+ show: phase >= 3,
2740
+ delay: .35,
2741
+ children: [
2742
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2743
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2744
+ w: 44,
2745
+ h: 7,
2746
+ bg: "rgba(24,24,27,0.18)",
2747
+ anim: .3
2748
+ }),
2749
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Dot, {}),
2750
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2751
+ w: 56,
2752
+ h: 7,
2753
+ bg: "rgba(153,246,228,0.5)",
2754
+ anim: .8
2755
+ }),
2756
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Bar, {
2757
+ w: 48,
2758
+ h: 7,
2759
+ bg: "rgba(147,197,253,0.5)",
2760
+ anim: .4
2761
+ })
2762
+ ]
2763
+ })
2764
+ ]
2765
+ }),
2766
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
2767
+ pointerEvents: "none",
2768
+ position: "absolute",
2769
+ inset: 0,
2770
+ background: "linear-gradient(105deg, transparent 0%, transparent 40%, rgba(255,255,255,0.6) 50%, transparent 60%, transparent 100%)",
2771
+ backgroundSize: "250% 100%",
2772
+ animation: "cpk-a2ui-sweep 3s ease-in-out infinite"
2773
+ } })
2774
+ ]
2775
+ }),
2776
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
2777
+ style: {
2778
+ display: "flex",
2779
+ alignItems: "center",
2780
+ justifyContent: "center",
2781
+ gap: 8,
2782
+ marginTop: 8
2783
+ },
2784
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
2785
+ style: {
2786
+ fontSize: 12,
2787
+ color: "#a1a1aa",
2788
+ letterSpacing: "0.025em"
2789
+ },
2790
+ children: "Building interface"
2791
+ }), tokens > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
2792
+ style: {
2793
+ fontSize: 11,
2794
+ color: "#d4d4d8",
2795
+ fontVariantNumeric: "tabular-nums"
2796
+ },
2797
+ children: [
2798
+ "~",
2799
+ tokens.toLocaleString(),
2800
+ " tokens"
2801
+ ]
2802
+ })]
2803
+ }),
2804
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("style", { children: `
2805
+ @keyframes cpk-a2ui-fade {
2806
+ 0%, 100% { opacity: 1; }
2807
+ 50% { opacity: 0.5; }
2808
+ }
2809
+ @keyframes cpk-a2ui-sweep {
2810
+ 0% { background-position: 250% 0; }
2811
+ 100% { background-position: -250% 0; }
2812
+ }
2813
+ ` })
2814
+ ]
2815
+ });
2816
+ }
2817
+ function Dot() {
2818
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
2819
+ width: 7,
2820
+ height: 7,
2821
+ borderRadius: "50%",
2822
+ backgroundColor: "#d4d4d8",
2823
+ flexShrink: 0
2824
+ } });
2825
+ }
2826
+ function Spacer() {
2827
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { width: 12 } });
2828
+ }
2829
+ function Bar({ w, h, bg, anim, opacity, transition }) {
2830
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
2831
+ width: w,
2832
+ height: h,
2833
+ borderRadius: 9999,
2834
+ backgroundColor: bg,
2835
+ ...anim !== void 0 ? { animation: `cpk-a2ui-fade 2.4s ease-in-out ${anim}s infinite` } : {},
2836
+ ...opacity !== void 0 ? { opacity } : {},
2837
+ ...transition ? { transition } : {}
2838
+ } });
2839
+ }
2840
+ function Row({ children, show, delay = 0 }) {
2841
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
2842
+ style: {
2843
+ display: "flex",
2844
+ alignItems: "center",
2845
+ gap: 6,
2846
+ opacity: show ? 1 : 0,
2847
+ transition: `opacity 0.4s ${delay}s`
2848
+ },
2849
+ children
2850
+ });
2851
+ }
2852
+ /**
2853
+ * Registers the built-in `render_a2ui` tool call renderer via the props-based
2854
+ * `setRenderToolCalls` mechanism (not `useRenderTool`).
2855
+ *
2856
+ * This ensures user-registered `useRenderTool({ name: "render_a2ui", ... })`
2857
+ * hooks automatically override the built-in, since the merge logic in
2858
+ * react-core.ts gives hook-based entries priority over prop-based entries.
2859
+ */
2860
+ function A2UIBuiltInToolCallRenderer() {
2861
+ const { copilotkit } = useCopilotKit();
2862
+ (0, react.useEffect)(() => {
2863
+ const renderer = defineToolCallRenderer({
2864
+ name: RENDER_A2UI_TOOL_NAME,
2865
+ args: zod.z.any(),
2866
+ render: ({ status, args: parameters }) => {
2867
+ if (status === "complete") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
2868
+ const params = parameters;
2869
+ const items = params?.items;
2870
+ if (Array.isArray(items) && items.length > 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
2871
+ const components = params?.components;
2872
+ if (Array.isArray(components) && components.length > 2) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {});
2873
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIProgressIndicator, { parameters });
2874
+ }
2875
+ });
2876
+ const existing = copilotkit._renderToolCalls ?? [];
2877
+ copilotkit.setRenderToolCalls([...existing.filter((rc) => rc.name !== RENDER_A2UI_TOOL_NAME), renderer]);
2878
+ }, [copilotkit]);
2879
+ return null;
2880
+ }
2881
+
2882
+ //#endregion
2883
+ //#region src/v2/hooks/use-agent-context.tsx
2884
+ function useAgentContext(context) {
2885
+ const { description, value } = context;
2886
+ const { copilotkit } = useCopilotKit();
2887
+ const stringValue = (0, react.useMemo)(() => {
2888
+ if (typeof value === "string") return value;
2889
+ return JSON.stringify(value);
2890
+ }, [value]);
2891
+ (0, react.useLayoutEffect)(() => {
2892
+ if (!copilotkit) return;
2893
+ const id = copilotkit.addContext({
2894
+ description,
2895
+ value: stringValue
2896
+ });
2897
+ return () => {
2898
+ copilotkit.removeContext(id);
2899
+ };
2900
+ }, [
2901
+ description,
2902
+ stringValue,
2903
+ copilotkit
2904
+ ]);
2905
+ }
2906
+
2907
+ //#endregion
2908
+ //#region src/v2/a2ui/A2UICatalogContext.tsx
2909
+ /**
2910
+ * Renders agent context describing the available A2UI catalog and custom components.
2911
+ * Only mount this component when A2UI is enabled.
2912
+ *
2913
+ * When `includeSchema` is true, the full component schemas (JSON Schema) are also
2914
+ * sent as context using the same description key as the A2UI middleware, so the
2915
+ * middleware can optionally overwrite it with a server-side schema.
2916
+ */
2917
+ function A2UICatalogContext({ catalog, includeSchema }) {
2918
+ useAgentContext({
2919
+ description: "A2UI catalog capabilities: available catalog IDs and custom component definitions the client can render.",
2920
+ value: (0, _copilotkit_a2ui_renderer.buildCatalogContextValue)(catalog)
2921
+ });
2922
+ const { copilotkit } = useCopilotKit();
2923
+ const schemaValue = (0, react.useMemo)(() => includeSchema !== false ? JSON.stringify((0, _copilotkit_a2ui_renderer.extractCatalogComponentSchemas)(catalog)) : null, [catalog, includeSchema]);
2924
+ (0, react.useLayoutEffect)(() => {
2925
+ if (!copilotkit || !schemaValue) return;
2926
+ const ids = [];
2927
+ ids.push(copilotkit.addContext({
2928
+ description: _copilotkit_a2ui_renderer.A2UI_SCHEMA_CONTEXT_DESCRIPTION,
2929
+ value: schemaValue
2930
+ }));
2931
+ ids.push(copilotkit.addContext({
2932
+ description: "A2UI generation guidelines — protocol rules, tool arguments, path rules, data model format, and form/two-way-binding instructions.",
2933
+ value: _copilotkit_shared.A2UI_DEFAULT_GENERATION_GUIDELINES
2934
+ }));
2935
+ ids.push(copilotkit.addContext({
2936
+ description: "A2UI design guidelines — visual design rules, component hierarchy tips, and action handler patterns.",
2937
+ value: _copilotkit_shared.A2UI_DEFAULT_DESIGN_GUIDELINES
2938
+ }));
2939
+ return () => {
2940
+ for (const id of ids) copilotkit.removeContext(id);
2941
+ };
2942
+ }, [copilotkit, schemaValue]);
2943
+ return null;
2035
2944
  }
2036
2945
 
2037
2946
  //#endregion
@@ -2134,6 +3043,17 @@ var CopilotKitCoreReact = class extends _copilotkit_core.CopilotKitCore {
2134
3043
  //#region src/v2/providers/CopilotKitProvider.tsx
2135
3044
  const HEADER_NAME = "X-CopilotCloud-Public-Api-Key";
2136
3045
  const COPILOT_CLOUD_CHAT_URL$1 = "https://api.cloud.copilotkit.ai/copilotkit/v1";
3046
+ const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follow these design principles inspired by shadcn/ui:
3047
+
3048
+ - Use a minimal, flat aesthetic. Avoid drop shadows and gradients — rely on subtle borders (1px solid, light gray like #e5e7eb) to define surfaces.
3049
+ - Neutral base palette: white backgrounds, zinc/slate gray text (#09090b for headings, #71717a for secondary text). One accent color for interactive elements.
3050
+ - Use system font stacks (system-ui, -apple-system, sans-serif) at readable sizes (14px body, 600 weight for headings). Tight line-heights.
3051
+ - Small, consistent border-radius (6–8px). Cards and containers use border, not shadow, for separation.
3052
+ - Buttons: solid fill for primary (dark bg, white text), outline for secondary (border + transparent bg). Subtle hover state (slight opacity or background shift).
3053
+ - Use CSS Grid or Flexbox for layout. Ensure the UI looks good at any width.
3054
+ - Minimal transitions (150ms) for hover/focus states only. No decorative animations.
3055
+ - Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
3056
+ const GENERATE_SANDBOXED_UI_DESCRIPTION = "Generate sandboxed UI. IMPORTANT: The generated code runs in a sandboxed iframe WITHOUT same-origin access. Do NOT use localStorage, sessionStorage, document.cookie, IndexedDB, or fetch/XMLHttpRequest to same-origin URLs. To communicate with the host application, use Websandbox.connection.remote.<functionName>(args) which returns a Promise.\n\nYou CAN use external libraries from CDNs by including <script> or <link> tags in the HTML <head> (e.g., Chart.js, D3, Three.js, x-data-spreadsheet, etc.). CDN resources load normally inside the sandbox.\n\nPARAMETER ORDER IS CRITICAL — generate parameters in exactly this order:\n1. initialHeight + placeholderMessages (shown to user while generating)\n2. css (all styles FIRST — the user sees a placeholder until CSS is complete)\n3. html (streams in live — the user watches the UI build as HTML is generated)\n4. jsFunctions (reusable helper functions)\n5. jsExpressions (applied one-by-one — the user sees each expression take effect)";
2137
3057
  const CopilotKitContext = (0, react.createContext)({
2138
3058
  copilotkit: null,
2139
3059
  executingToolCallIds: /* @__PURE__ */ new Set()
@@ -2149,9 +3069,11 @@ function useStableArrayProp(prop, warningMessage, isMeaningfulChange) {
2149
3069
  }, [value, warningMessage]);
2150
3070
  return value;
2151
3071
  }
2152
- const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, publicApiKey, publicLicenseKey, licenseToken, properties = {}, agents__unsafe_dev_only: agents = {}, selfManagedAgents = {}, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, showDevConsole = false, useSingleEndpoint = false, onError, a2ui }) => {
3072
+ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, publicApiKey, publicLicenseKey, licenseToken, properties = {}, agents__unsafe_dev_only: agents = {}, selfManagedAgents = {}, renderToolCalls, renderActivityMessages, renderCustomMessages, frontendTools, humanInTheLoop, openGenerativeUI, showDevConsole = false, useSingleEndpoint, onError, a2ui, defaultThrottleMs, inspectorDefaultAnchor }) => {
2153
3073
  const [shouldRenderInspector, setShouldRenderInspector] = (0, react.useState)(false);
2154
3074
  const [runtimeA2UIEnabled, setRuntimeA2UIEnabled] = (0, react.useState)(false);
3075
+ const [runtimeOpenGenUIEnabled, setRuntimeOpenGenUIEnabled] = (0, react.useState)(false);
3076
+ const openGenUIActive = runtimeOpenGenUIEnabled || !!openGenerativeUI;
2155
3077
  const [runtimeLicenseStatus, setRuntimeLicenseStatus] = (0, react.useState)(void 0);
2156
3078
  (0, react.useEffect)(() => {
2157
3079
  if (typeof window === "undefined") return;
@@ -2177,9 +3099,22 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2177
3099
  content: MCPAppsActivityContentSchema,
2178
3100
  render: MCPAppsActivityRenderer
2179
3101
  }];
2180
- if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({ theme: a2ui?.theme ?? _copilotkit_a2ui_renderer.viewerTheme }));
3102
+ if (openGenUIActive) renderers.push({
3103
+ activityType: OpenGenerativeUIActivityType,
3104
+ content: OpenGenerativeUIContentSchema,
3105
+ render: OpenGenerativeUIActivityRenderer
3106
+ });
3107
+ if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({
3108
+ theme: a2ui?.theme ?? _copilotkit_a2ui_renderer.viewerTheme,
3109
+ catalog: a2ui?.catalog,
3110
+ loadingComponent: a2ui?.loadingComponent
3111
+ }));
2181
3112
  return renderers;
2182
- }, [runtimeA2UIEnabled, a2ui]);
3113
+ }, [
3114
+ runtimeA2UIEnabled,
3115
+ openGenUIActive,
3116
+ a2ui
3117
+ ]);
2183
3118
  const allActivityRenderers = (0, react.useMemo)(() => {
2184
3119
  return [...renderActivityMessagesList, ...builtInActivityRenderers];
2185
3120
  }, [renderActivityMessagesList, builtInActivityRenderers]);
@@ -2205,6 +3140,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2205
3140
  const chatApiEndpoint = runtimeUrl ?? (resolvedPublicKey ? COPILOT_CLOUD_CHAT_URL$1 : void 0);
2206
3141
  const frontendToolsList = useStableArrayProp(frontendTools, "frontendTools must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.");
2207
3142
  const humanInTheLoopList = useStableArrayProp(humanInTheLoop, "humanInTheLoop must be a stable array. If you want to dynamically add or remove human-in-the-loop tools, use `useHumanInTheLoop` instead.");
3143
+ const sandboxFunctionsList = useStableArrayProp(openGenerativeUI?.sandboxFunctions, "openGenerativeUI.sandboxFunctions must be a stable array.");
2208
3144
  const processedHumanInTheLoopTools = (0, react.useMemo)(() => {
2209
3145
  const processedTools = [];
2210
3146
  const processedRenderToolCalls = [];
@@ -2235,15 +3171,31 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2235
3171
  renderToolCalls: processedRenderToolCalls
2236
3172
  };
2237
3173
  }, [humanInTheLoopList]);
3174
+ const builtInFrontendTools = (0, react.useMemo)(() => {
3175
+ if (!openGenUIActive) return [];
3176
+ return [{
3177
+ name: "generateSandboxedUi",
3178
+ description: GENERATE_SANDBOXED_UI_DESCRIPTION,
3179
+ parameters: GenerateSandboxedUiArgsSchema,
3180
+ handler: async () => "UI generated",
3181
+ followUp: true,
3182
+ render: OpenGenerativeUIToolRenderer
3183
+ }];
3184
+ }, [openGenUIActive]);
2238
3185
  const allTools = (0, react.useMemo)(() => {
2239
3186
  const tools = [];
2240
3187
  tools.push(...frontendToolsList);
3188
+ tools.push(...builtInFrontendTools);
2241
3189
  tools.push(...processedHumanInTheLoopTools.tools);
2242
3190
  return tools;
2243
- }, [frontendToolsList, processedHumanInTheLoopTools]);
3191
+ }, [
3192
+ frontendToolsList,
3193
+ builtInFrontendTools,
3194
+ processedHumanInTheLoopTools
3195
+ ]);
2244
3196
  const allRenderToolCalls = (0, react.useMemo)(() => {
2245
3197
  const combined = [...renderToolCallsList];
2246
- frontendToolsList.forEach((tool) => {
3198
+ [...frontendToolsList, ...builtInFrontendTools].forEach((tool) => {
2247
3199
  if (tool.render) {
2248
3200
  const args = tool.parameters || (tool.name === "*" ? zod.z.any() : void 0);
2249
3201
  if (args) combined.push({
@@ -2258,25 +3210,31 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2258
3210
  }, [
2259
3211
  renderToolCallsList,
2260
3212
  frontendToolsList,
3213
+ builtInFrontendTools,
2261
3214
  processedHumanInTheLoopTools
2262
3215
  ]);
2263
3216
  const copilotkitRef = (0, react.useRef)(null);
2264
- if (copilotkitRef.current === null) copilotkitRef.current = new CopilotKitCoreReact({
2265
- runtimeUrl: chatApiEndpoint,
2266
- runtimeTransport: useSingleEndpoint ? "single" : "rest",
2267
- headers: mergedHeaders,
2268
- credentials,
2269
- properties,
2270
- agents__unsafe_dev_only: mergedAgents,
2271
- tools: allTools,
2272
- renderToolCalls: allRenderToolCalls,
2273
- renderActivityMessages: allActivityRenderers,
2274
- renderCustomMessages: renderCustomMessagesList
2275
- });
3217
+ if (copilotkitRef.current === null) {
3218
+ copilotkitRef.current = new CopilotKitCoreReact({
3219
+ runtimeUrl: chatApiEndpoint,
3220
+ runtimeTransport: useSingleEndpoint === true ? "single" : useSingleEndpoint === false ? "rest" : "auto",
3221
+ headers: mergedHeaders,
3222
+ credentials,
3223
+ properties,
3224
+ agents__unsafe_dev_only: mergedAgents,
3225
+ tools: allTools,
3226
+ renderToolCalls: allRenderToolCalls,
3227
+ renderActivityMessages: allActivityRenderers,
3228
+ renderCustomMessages: renderCustomMessagesList
3229
+ });
3230
+ if (defaultThrottleMs !== void 0) copilotkitRef.current.setDefaultThrottleMs(defaultThrottleMs);
3231
+ }
2276
3232
  const copilotkit = copilotkitRef.current;
2277
3233
  (0, react.useEffect)(() => {
3234
+ setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
2278
3235
  const subscription = copilotkit.subscribe({ onRuntimeConnectionStatusChanged: () => {
2279
3236
  setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
3237
+ setRuntimeOpenGenUIEnabled(copilotkit.openGenerativeUIEnabled);
2280
3238
  setRuntimeLicenseStatus(copilotkit.licenseStatus);
2281
3239
  } });
2282
3240
  return () => {
@@ -2335,7 +3293,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2335
3293
  }, [copilotkit]);
2336
3294
  (0, react.useEffect)(() => {
2337
3295
  copilotkit.setRuntimeUrl(chatApiEndpoint);
2338
- copilotkit.setRuntimeTransport(useSingleEndpoint ? "single" : "rest");
3296
+ copilotkit.setRuntimeTransport(useSingleEndpoint === true ? "single" : useSingleEndpoint === false ? "rest" : "auto");
2339
3297
  copilotkit.setHeaders(mergedHeaders);
2340
3298
  copilotkit.setCredentials(credentials);
2341
3299
  copilotkit.setProperties(properties);
@@ -2369,23 +3327,75 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2369
3327
  (0, react.useEffect)(() => {
2370
3328
  didMountRef.current = true;
2371
3329
  }, []);
3330
+ (0, react.useEffect)(() => {
3331
+ if (defaultThrottleMs !== void 0 && (!Number.isFinite(defaultThrottleMs) || defaultThrottleMs < 0)) console.error(`CopilotKitProvider: defaultThrottleMs must be a non-negative finite number, got ${defaultThrottleMs}. useAgent hooks without an explicit throttleMs will fall back to unthrottled.`);
3332
+ copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3333
+ }, [copilotkit, defaultThrottleMs]);
3334
+ const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
3335
+ (0, react.useLayoutEffect)(() => {
3336
+ if (!copilotkit || !openGenUIActive) return;
3337
+ const id = copilotkit.addContext({
3338
+ description: "Design guidelines for the generateSandboxedUi tool. Follow these when building UI.",
3339
+ value: designSkill
3340
+ });
3341
+ return () => {
3342
+ copilotkit.removeContext(id);
3343
+ };
3344
+ }, [
3345
+ copilotkit,
3346
+ designSkill,
3347
+ openGenUIActive
3348
+ ]);
3349
+ const sandboxFunctionsDescriptors = (0, react.useMemo)(() => {
3350
+ if (sandboxFunctionsList.length === 0) return null;
3351
+ return JSON.stringify(sandboxFunctionsList.map((fn) => ({
3352
+ name: fn.name,
3353
+ description: fn.description,
3354
+ parameters: (0, _copilotkit_shared.schemaToJsonSchema)(fn.parameters, { zodToJsonSchema: zod_to_json_schema.zodToJsonSchema })
3355
+ })));
3356
+ }, [sandboxFunctionsList]);
3357
+ (0, react.useLayoutEffect)(() => {
3358
+ if (!copilotkit || !sandboxFunctionsDescriptors || !openGenUIActive) return;
3359
+ const id = copilotkit.addContext({
3360
+ description: "Sandbox functions available in generated sandboxed UI code. Call via: await Websandbox.connection.remote.<functionName>(args)",
3361
+ value: sandboxFunctionsDescriptors
3362
+ });
3363
+ return () => {
3364
+ copilotkit.removeContext(id);
3365
+ };
3366
+ }, [
3367
+ copilotkit,
3368
+ sandboxFunctionsDescriptors,
3369
+ openGenUIActive
3370
+ ]);
2372
3371
  const contextValue = (0, react.useMemo)(() => ({
2373
3372
  copilotkit,
2374
3373
  executingToolCallIds
2375
3374
  }), [copilotkit, executingToolCallIds]);
2376
3375
  const licenseContextValue = (0, react.useMemo)(() => (0, _copilotkit_shared.createLicenseContextValue)(null), []);
2377
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitContext.Provider, {
2378
- value: contextValue,
2379
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LicenseContext.Provider, {
2380
- value: licenseContextValue,
2381
- children: [
2382
- children,
2383
- shouldRenderInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitInspector, { core: copilotkit }) : null,
2384
- runtimeLicenseStatus === "none" && !resolvedPublicKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "no_license" }),
2385
- runtimeLicenseStatus === "expired" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "expired" }),
2386
- runtimeLicenseStatus === "invalid" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "invalid" }),
2387
- runtimeLicenseStatus === "expiring" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "expiring" })
2388
- ]
3376
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SandboxFunctionsContext.Provider, {
3377
+ value: sandboxFunctionsList,
3378
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitContext.Provider, {
3379
+ value: contextValue,
3380
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(LicenseContext.Provider, {
3381
+ value: licenseContextValue,
3382
+ children: [
3383
+ runtimeA2UIEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UIBuiltInToolCallRenderer, {}),
3384
+ runtimeA2UIEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(A2UICatalogContext, {
3385
+ catalog: a2ui?.catalog,
3386
+ includeSchema: a2ui?.includeSchema
3387
+ }),
3388
+ children,
3389
+ shouldRenderInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotKitInspector, {
3390
+ core: copilotkit,
3391
+ defaultAnchor: inspectorDefaultAnchor
3392
+ }) : null,
3393
+ runtimeLicenseStatus === "none" && !resolvedPublicKey && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "no_license" }),
3394
+ runtimeLicenseStatus === "expired" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "expired" }),
3395
+ runtimeLicenseStatus === "invalid" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "invalid" }),
3396
+ runtimeLicenseStatus === "expiring" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LicenseWarningBanner, { type: "expiring" })
3397
+ ]
3398
+ })
2389
3399
  })
2390
3400
  });
2391
3401
  };
@@ -2526,11 +3536,21 @@ function getOrCreateThreadClone(existing, threadId, headers) {
2526
3536
  byThread.set(threadId, clone);
2527
3537
  return clone;
2528
3538
  }
2529
- function useAgent({ agentId, threadId, updates } = {}) {
3539
+ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
2530
3540
  agentId ??= _copilotkit_shared.DEFAULT_AGENT_ID;
2531
3541
  const { copilotkit } = useCopilotKit();
3542
+ const providerThrottleMs = copilotkit.defaultThrottleMs;
2532
3543
  const chatConfig = useCopilotChatConfiguration();
2533
3544
  threadId ??= chatConfig?.threadId;
3545
+ const effectiveThrottleMs = (0, react.useMemo)(() => {
3546
+ const resolved = throttleMs ?? providerThrottleMs ?? 0;
3547
+ if (!Number.isFinite(resolved) || resolved < 0) {
3548
+ const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3549
+ console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3550
+ return 0;
3551
+ }
3552
+ return resolved;
3553
+ }, [throttleMs, providerThrottleMs]);
2534
3554
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
2535
3555
  const updateFlags = (0, react.useMemo)(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
2536
3556
  const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
@@ -2594,9 +3614,32 @@ function useAgent({ agentId, threadId, updates } = {}) {
2594
3614
  (0, react.useEffect)(() => {
2595
3615
  if (updateFlags.length === 0) return;
2596
3616
  const handlers = {};
2597
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = () => {
2598
- forceUpdate();
2599
- };
3617
+ let timerId = null;
3618
+ let active = true;
3619
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3620
+ const ms = effectiveThrottleMs;
3621
+ if (ms > 0) {
3622
+ let throttleActive = false;
3623
+ let pending = false;
3624
+ const throttledNotify = () => {
3625
+ if (!active) return;
3626
+ if (!throttleActive) {
3627
+ throttleActive = true;
3628
+ pending = false;
3629
+ forceUpdate();
3630
+ timerId = setTimeout(function trailingEdge() {
3631
+ timerId = null;
3632
+ if (active && pending) {
3633
+ pending = false;
3634
+ forceUpdate();
3635
+ timerId = setTimeout(trailingEdge, ms);
3636
+ } else throttleActive = false;
3637
+ }, ms);
3638
+ } else pending = true;
3639
+ };
3640
+ handlers.onMessagesChanged = throttledNotify;
3641
+ } else handlers.onMessagesChanged = forceUpdate;
3642
+ }
2600
3643
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = forceUpdate;
2601
3644
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
2602
3645
  handlers.onRunInitialized = forceUpdate;
@@ -2604,11 +3647,16 @@ function useAgent({ agentId, threadId, updates } = {}) {
2604
3647
  handlers.onRunFailed = forceUpdate;
2605
3648
  }
2606
3649
  const subscription = agent.subscribe(handlers);
2607
- return () => subscription.unsubscribe();
3650
+ return () => {
3651
+ active = false;
3652
+ if (timerId !== null) clearTimeout(timerId);
3653
+ subscription.unsubscribe();
3654
+ };
2608
3655
  }, [
2609
3656
  agent,
2610
3657
  forceUpdate,
2611
- JSON.stringify(updateFlags)
3658
+ effectiveThrottleMs,
3659
+ updateFlags
2612
3660
  ]);
2613
3661
  (0, react.useEffect)(() => {
2614
3662
  if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...copilotkit.headers };
@@ -2666,7 +3714,8 @@ function useRenderCustomMessages() {
2666
3714
  //#region src/v2/hooks/use-render-activity-message.tsx
2667
3715
  function useRenderActivityMessage() {
2668
3716
  const { copilotkit } = useCopilotKit();
2669
- const agentId = useCopilotChatConfiguration()?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
3717
+ const config = useCopilotChatConfiguration();
3718
+ const agentId = config?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
2670
3719
  const renderers = copilotkit.renderActivityMessages;
2671
3720
  const findRenderer = (0, react.useCallback)((activityType) => {
2672
3721
  if (!renderers.length) return null;
@@ -2682,7 +3731,8 @@ function useRenderActivityMessage() {
2682
3731
  return null;
2683
3732
  }
2684
3733
  const Component = renderer.render;
2685
- const agent = copilotkit.getAgent(agentId);
3734
+ const registryAgent = copilotkit.getAgent(agentId);
3735
+ const agent = getThreadClone(registryAgent, config?.threadId) ?? registryAgent;
2686
3736
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Component, {
2687
3737
  activityType: message.activityType,
2688
3738
  content: parseResult.data,
@@ -2691,6 +3741,7 @@ function useRenderActivityMessage() {
2691
3741
  }, message.id);
2692
3742
  }, [
2693
3743
  agentId,
3744
+ config?.threadId,
2694
3745
  copilotkit,
2695
3746
  findRenderer
2696
3747
  ]);
@@ -2716,7 +3767,7 @@ function useFrontendTool(tool, deps) {
2716
3767
  copilotkit.removeTool(name, tool.agentId);
2717
3768
  }
2718
3769
  copilotkit.addTool(tool);
2719
- if (tool.render && tool.parameters) copilotkit.addHookRenderToolCall({
3770
+ if (tool.render) copilotkit.addHookRenderToolCall({
2720
3771
  name,
2721
3772
  args: tool.parameters,
2722
3773
  agentId: tool.agentId,
@@ -2801,18 +3852,6 @@ function useComponent(config, deps) {
2801
3852
  }, deps);
2802
3853
  }
2803
3854
 
2804
- //#endregion
2805
- //#region src/v2/types/defineToolCallRenderer.ts
2806
- function defineToolCallRenderer(def) {
2807
- const argsSchema = def.name === "*" && !def.args ? zod.z.any() : def.args;
2808
- return {
2809
- name: def.name,
2810
- args: argsSchema,
2811
- render: def.render,
2812
- ...def.agentId ? { agentId: def.agentId } : {}
2813
- };
2814
- }
2815
-
2816
3855
  //#endregion
2817
3856
  //#region src/v2/hooks/use-render-tool.tsx
2818
3857
  const EMPTY_DEPS = [];
@@ -3142,31 +4181,6 @@ function useHumanInTheLoop(tool, deps) {
3142
4181
  ]);
3143
4182
  }
3144
4183
 
3145
- //#endregion
3146
- //#region src/v2/hooks/use-agent-context.tsx
3147
- function useAgentContext(context) {
3148
- const { description, value } = context;
3149
- const { copilotkit } = useCopilotKit();
3150
- const stringValue = (0, react.useMemo)(() => {
3151
- if (typeof value === "string") return value;
3152
- return JSON.stringify(value);
3153
- }, [value]);
3154
- (0, react.useLayoutEffect)(() => {
3155
- if (!copilotkit) return;
3156
- const id = copilotkit.addContext({
3157
- description,
3158
- value: stringValue
3159
- });
3160
- return () => {
3161
- copilotkit.removeContext(id);
3162
- };
3163
- }, [
3164
- description,
3165
- stringValue,
3166
- copilotkit
3167
- ]);
3168
- }
3169
-
3170
4184
  //#endregion
3171
4185
  //#region src/v2/hooks/use-suggestions.tsx
3172
4186
  function useSuggestions({ agentId } = {}) {
@@ -3576,11 +4590,19 @@ function useThreadStoreSelector(store, selector) {
3576
4590
  function useThreads$1({ agentId, includeArchived, limit }) {
3577
4591
  const { copilotkit } = useCopilotKit();
3578
4592
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
3579
- const threads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4593
+ const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4594
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4595
+ id,
4596
+ agentId,
4597
+ name,
4598
+ archived,
4599
+ createdAt,
4600
+ updatedAt
4601
+ })), [coreThreads]);
3580
4602
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
3581
4603
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
3582
- const hasNextPage = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
3583
- const isFetchingNextPage = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
4604
+ const hasMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
4605
+ const isFetchingMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
3584
4606
  const headersKey = (0, react.useMemo)(() => {
3585
4607
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
3586
4608
  }, [copilotkit.headers]);
@@ -3623,15 +4645,173 @@ function useThreads$1({ agentId, includeArchived, limit }) {
3623
4645
  threads,
3624
4646
  isLoading,
3625
4647
  error,
3626
- hasNextPage,
3627
- isFetchingNextPage,
3628
- fetchNextPage: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
4648
+ hasMoreThreads,
4649
+ isFetchingMoreThreads,
4650
+ fetchMoreThreads: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
3629
4651
  renameThread,
3630
4652
  archiveThread,
3631
4653
  deleteThread
3632
4654
  };
3633
4655
  }
3634
4656
 
4657
+ //#endregion
4658
+ //#region src/v2/hooks/use-attachments.tsx
4659
+ /**
4660
+ * Hook that manages file attachment state — uploads, drag-and-drop, paste,
4661
+ * and lifecycle. All returned callbacks are referentially stable across
4662
+ * renders (via useCallback) to avoid destabilizing downstream memoization.
4663
+ */
4664
+ function useAttachments({ config }) {
4665
+ const enabled = config?.enabled ?? false;
4666
+ const [attachments, setAttachments] = (0, react.useState)([]);
4667
+ const [dragOver, setDragOver] = (0, react.useState)(false);
4668
+ const fileInputRef = (0, react.useRef)(null);
4669
+ const containerRef = (0, react.useRef)(null);
4670
+ const configRef = (0, react.useRef)(config);
4671
+ configRef.current = config;
4672
+ const attachmentsRef = (0, react.useRef)([]);
4673
+ attachmentsRef.current = attachments;
4674
+ const processFiles = (0, react.useCallback)(async (files) => {
4675
+ const cfg = configRef.current;
4676
+ const accept = cfg?.accept ?? "*/*";
4677
+ const maxSize = cfg?.maxSize ?? 20 * 1024 * 1024;
4678
+ const rejectedFiles = files.filter((file) => !(0, _copilotkit_shared.matchesAcceptFilter)(file, accept));
4679
+ for (const file of rejectedFiles) cfg?.onUploadFailed?.({
4680
+ reason: "invalid-type",
4681
+ file,
4682
+ message: `File "${file.name}" is not accepted. Supported types: ${accept}`
4683
+ });
4684
+ const validFiles = files.filter((file) => (0, _copilotkit_shared.matchesAcceptFilter)(file, accept));
4685
+ for (const file of validFiles) {
4686
+ if ((0, _copilotkit_shared.exceedsMaxSize)(file, maxSize)) {
4687
+ cfg?.onUploadFailed?.({
4688
+ reason: "file-too-large",
4689
+ file,
4690
+ message: `File "${file.name}" exceeds the maximum size of ${(0, _copilotkit_shared.formatFileSize)(maxSize)}`
4691
+ });
4692
+ continue;
4693
+ }
4694
+ const modality = (0, _copilotkit_shared.getModalityFromMimeType)(file.type);
4695
+ const placeholderId = (0, _copilotkit_shared.randomUUID)();
4696
+ const placeholder = {
4697
+ id: placeholderId,
4698
+ type: modality,
4699
+ source: {
4700
+ type: "data",
4701
+ value: "",
4702
+ mimeType: file.type
4703
+ },
4704
+ filename: file.name,
4705
+ size: file.size,
4706
+ status: "uploading"
4707
+ };
4708
+ setAttachments((prev) => [...prev, placeholder]);
4709
+ try {
4710
+ let source;
4711
+ let uploadMetadata;
4712
+ if (cfg?.onUpload) {
4713
+ const { metadata: meta, ...uploadSource } = await cfg.onUpload(file);
4714
+ source = uploadSource;
4715
+ uploadMetadata = meta;
4716
+ } else source = {
4717
+ type: "data",
4718
+ value: await (0, _copilotkit_shared.readFileAsBase64)(file),
4719
+ mimeType: file.type
4720
+ };
4721
+ let thumbnail;
4722
+ if (modality === "video") thumbnail = await (0, _copilotkit_shared.generateVideoThumbnail)(file);
4723
+ setAttachments((prev) => prev.map((att) => att.id === placeholderId ? {
4724
+ ...att,
4725
+ source,
4726
+ status: "ready",
4727
+ thumbnail,
4728
+ metadata: uploadMetadata
4729
+ } : att));
4730
+ } catch (error) {
4731
+ setAttachments((prev) => prev.filter((att) => att.id !== placeholderId));
4732
+ console.error(`[CopilotKit] Failed to upload "${file.name}":`, error);
4733
+ cfg?.onUploadFailed?.({
4734
+ reason: "upload-failed",
4735
+ file,
4736
+ message: error instanceof Error ? error.message : `Failed to upload "${file.name}"`
4737
+ });
4738
+ }
4739
+ }
4740
+ }, []);
4741
+ const handleFileUpload = (0, react.useCallback)(async (e) => {
4742
+ if (!e.target.files?.length) return;
4743
+ try {
4744
+ await processFiles(Array.from(e.target.files));
4745
+ } catch (error) {
4746
+ console.error("[CopilotKit] Upload error:", error);
4747
+ }
4748
+ }, [processFiles]);
4749
+ const handleDragOver = (0, react.useCallback)((e) => {
4750
+ if (!configRef.current?.enabled) return;
4751
+ e.preventDefault();
4752
+ e.stopPropagation();
4753
+ setDragOver(true);
4754
+ }, []);
4755
+ const handleDragLeave = (0, react.useCallback)((e) => {
4756
+ e.preventDefault();
4757
+ e.stopPropagation();
4758
+ setDragOver(false);
4759
+ }, []);
4760
+ const handleDrop = (0, react.useCallback)(async (e) => {
4761
+ e.preventDefault();
4762
+ e.stopPropagation();
4763
+ setDragOver(false);
4764
+ if (!configRef.current?.enabled) return;
4765
+ const files = Array.from(e.dataTransfer.files);
4766
+ if (files.length > 0) try {
4767
+ await processFiles(files);
4768
+ } catch (error) {
4769
+ console.error("[CopilotKit] Drop error:", error);
4770
+ }
4771
+ }, [processFiles]);
4772
+ (0, react.useEffect)(() => {
4773
+ if (!enabled) return;
4774
+ const handlePaste = async (e) => {
4775
+ const target = e.target;
4776
+ if (!target || !containerRef.current?.contains(target)) return;
4777
+ const accept = configRef.current?.accept ?? "*/*";
4778
+ const fileItems = Array.from(e.clipboardData?.items || []).filter((item) => item.kind === "file" && item.getAsFile() !== null && (0, _copilotkit_shared.matchesAcceptFilter)(item.getAsFile(), accept));
4779
+ if (fileItems.length === 0) return;
4780
+ e.preventDefault();
4781
+ const files = fileItems.map((item) => item.getAsFile()).filter((f) => f !== null);
4782
+ try {
4783
+ await processFiles(files);
4784
+ } catch (error) {
4785
+ console.error("[CopilotKit] Paste error:", error);
4786
+ }
4787
+ };
4788
+ document.addEventListener("paste", handlePaste);
4789
+ return () => document.removeEventListener("paste", handlePaste);
4790
+ }, [enabled, processFiles]);
4791
+ return {
4792
+ attachments,
4793
+ enabled,
4794
+ dragOver,
4795
+ fileInputRef,
4796
+ containerRef,
4797
+ processFiles,
4798
+ handleFileUpload,
4799
+ handleDragOver,
4800
+ handleDragLeave,
4801
+ handleDrop,
4802
+ removeAttachment: (0, react.useCallback)((id) => {
4803
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
4804
+ }, []),
4805
+ consumeAttachments: (0, react.useCallback)(() => {
4806
+ const ready = attachmentsRef.current.filter((a) => a.status === "ready");
4807
+ if (ready.length === 0) return ready;
4808
+ setAttachments((prev) => prev.filter((a) => a.status !== "ready"));
4809
+ if (fileInputRef.current) fileInputRef.current.value = "";
4810
+ return ready;
4811
+ }, [])
4812
+ };
4813
+ }
4814
+
3635
4815
  //#endregion
3636
4816
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
3637
4817
  function CopilotChatToolCallsView({ message, messages = [] }) {
@@ -3748,9 +4928,19 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, onThumbsUp,
3748
4928
  _CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
3749
4929
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
3750
4930
  const [copied, setCopied] = (0, react.useState)(false);
4931
+ const timerRef = (0, react.useRef)(null);
4932
+ (0, react.useEffect)(() => {
4933
+ return () => {
4934
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
4935
+ };
4936
+ }, []);
3751
4937
  const handleClick = (event) => {
3752
4938
  setCopied(true);
3753
- setTimeout(() => setCopied(false), 2e3);
4939
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
4940
+ timerRef.current = setTimeout(() => {
4941
+ timerRef.current = null;
4942
+ setCopied(false);
4943
+ }, 2e3);
3754
4944
  if (onClick) onClick(event);
3755
4945
  };
3756
4946
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ToolbarButton, {
@@ -3808,6 +4998,79 @@ CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantM
3808
4998
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
3809
4999
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
3810
5000
 
5001
+ //#endregion
5002
+ //#region src/v2/components/chat/CopilotChatAttachmentRenderer.tsx
5003
+ const ImageAttachment = (0, react.memo)(function ImageAttachment({ src, className }) {
5004
+ const [error, setError] = (0, react.useState)(false);
5005
+ if (error) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5006
+ className: cn("cpk:flex cpk:flex-col cpk:items-center cpk:justify-center cpk:rounded-lg cpk:bg-muted cpk:p-4 cpk:text-sm cpk:text-muted-foreground", className),
5007
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: "Failed to load image" })
5008
+ });
5009
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5010
+ src,
5011
+ alt: "Image attachment",
5012
+ className: cn("cpk:max-w-full cpk:h-auto cpk:rounded-lg", className),
5013
+ onError: () => setError(true)
5014
+ });
5015
+ });
5016
+ const AudioAttachment = (0, react.memo)(function AudioAttachment({ src, filename, className }) {
5017
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5018
+ className: cn("cpk:flex cpk:flex-col cpk:gap-1", className),
5019
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
5020
+ src,
5021
+ controls: true,
5022
+ preload: "metadata",
5023
+ className: "cpk:max-w-[300px] cpk:w-full cpk:h-10"
5024
+ }), filename && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5025
+ className: "cpk:text-xs cpk:text-muted-foreground cpk:truncate cpk:max-w-[300px]",
5026
+ children: filename
5027
+ })]
5028
+ });
5029
+ });
5030
+ const VideoAttachment = (0, react.memo)(function VideoAttachment({ src, className }) {
5031
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
5032
+ src,
5033
+ controls: true,
5034
+ preload: "metadata",
5035
+ className: cn("cpk:max-w-[400px] cpk:w-full cpk:rounded-lg", className)
5036
+ });
5037
+ });
5038
+ const DocumentAttachment = (0, react.memo)(function DocumentAttachment({ source, filename, className }) {
5039
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5040
+ className: cn("cpk:inline-flex cpk:items-center cpk:gap-2 cpk:px-3 cpk:py-2 cpk:border cpk:border-border cpk:rounded-lg cpk:bg-muted", className),
5041
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5042
+ className: "cpk:text-xs cpk:font-bold cpk:uppercase",
5043
+ children: (0, _copilotkit_shared.getDocumentIcon)(source.mimeType ?? "")
5044
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5045
+ className: "cpk:text-sm cpk:text-muted-foreground cpk:truncate",
5046
+ children: filename || source.mimeType || "Unknown type"
5047
+ })]
5048
+ });
5049
+ });
5050
+ const CopilotChatAttachmentRenderer = ({ type, source, filename, className }) => {
5051
+ const src = (0, _copilotkit_shared.getSourceUrl)(source);
5052
+ switch (type) {
5053
+ case "image": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImageAttachment, {
5054
+ src,
5055
+ className
5056
+ });
5057
+ case "audio": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AudioAttachment, {
5058
+ src,
5059
+ filename,
5060
+ className
5061
+ });
5062
+ case "video": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoAttachment, {
5063
+ src,
5064
+ className
5065
+ });
5066
+ case "document": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentAttachment, {
5067
+ source,
5068
+ filename,
5069
+ className
5070
+ });
5071
+ }
5072
+ };
5073
+
3811
5074
  //#endregion
3812
5075
  //#region src/v2/components/chat/CopilotChatUserMessage.tsx
3813
5076
  function flattenUserMessageContent(content) {
@@ -3818,8 +5081,17 @@ function flattenUserMessageContent(content) {
3818
5081
  return "";
3819
5082
  }).filter((text) => text.length > 0).join("\n");
3820
5083
  }
5084
+ function getMediaParts(content) {
5085
+ if (!content || typeof content === "string") return [];
5086
+ return content.filter((part) => part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document");
5087
+ }
5088
+ function getFilename(part) {
5089
+ const meta = part.metadata;
5090
+ if (meta != null && typeof meta === "object" && "filename" in meta && typeof meta.filename === "string") return meta.filename;
5091
+ }
3821
5092
  function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfBranches, onSwitchToBranch, additionalToolbarItems, messageRenderer, toolbar, copyButton, editButton, branchNavigation, children, className, ...props }) {
3822
5093
  const flattenedContent = (0, react.useMemo)(() => flattenUserMessageContent(message.content), [message.content]);
5094
+ const mediaParts = (0, react.useMemo)(() => getMediaParts(message.content), [message.content]);
3823
5095
  const BoundMessageRenderer = renderSlot(messageRenderer, CopilotChatUserMessage.MessageRenderer, { content: flattenedContent });
3824
5096
  const BoundCopyButton = renderSlot(copyButton, CopilotChatUserMessage.CopyButton, { onClick: async () => {
3825
5097
  if (flattenedContent) try {
@@ -3866,7 +5138,18 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
3866
5138
  className: (0, tailwind_merge.twMerge)("copilotKitMessage copilotKitUserMessage cpk:flex cpk:flex-col cpk:items-end cpk:group cpk:pt-10", className),
3867
5139
  "data-message-id": message.id,
3868
5140
  ...props,
3869
- children: [BoundMessageRenderer, BoundToolbar]
5141
+ children: [
5142
+ BoundMessageRenderer,
5143
+ mediaParts.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5144
+ className: "cpk:flex cpk:flex-col cpk:items-end cpk:gap-2 cpk:mt-2",
5145
+ children: mediaParts.map((part, index) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentRenderer, {
5146
+ type: part.type,
5147
+ source: part.source,
5148
+ filename: getFilename(part)
5149
+ }, index))
5150
+ }),
5151
+ BoundToolbar
5152
+ ]
3870
5153
  });
3871
5154
  }
3872
5155
  (function(_CopilotChatUserMessage) {
@@ -4164,6 +5447,7 @@ const CopilotChatSuggestionView = react.default.forwardRef(function CopilotChatS
4164
5447
  const isLoading = loadingSet.has(index) || suggestion.isLoading === true;
4165
5448
  const pill = renderSlot(suggestionSlot, CopilotChatSuggestionPill, {
4166
5449
  children: suggestion.title,
5450
+ className: suggestion.className,
4167
5451
  isLoading,
4168
5452
  type: "button",
4169
5453
  onClick: () => onSelectSuggestion?.(suggestion, index)
@@ -4200,9 +5484,43 @@ const CopilotChatSuggestionView = react.default.forwardRef(function CopilotChatS
4200
5484
  });
4201
5485
  CopilotChatSuggestionView.displayName = "CopilotChatSuggestionView";
4202
5486
 
5487
+ //#endregion
5488
+ //#region src/v2/components/chat/scroll-element-context.ts
5489
+ /**
5490
+ * Provides the scroll container element to child components that need it for
5491
+ * virtualization. Set by CopilotChatView.ScrollView; consumed by
5492
+ * CopilotChatMessageView to feed useVirtualizer's getScrollElement.
5493
+ *
5494
+ * Carries the element itself (not a ref) so that context consumers re-render
5495
+ * reactively when the scroll container is first mounted.
5496
+ */
5497
+ const ScrollElementContext = react.default.createContext(null);
5498
+
4203
5499
  //#endregion
4204
5500
  //#region src/v2/components/chat/CopilotChatMessageView.tsx
4205
5501
  /**
5502
+ * Resolves a slot value into a { Component, slotProps } pair, handling the three
5503
+ * slot forms: a component type, a className string, or a partial-props object.
5504
+ */
5505
+ function resolveSlotComponent(slot, DefaultComponent) {
5506
+ if (isReactComponentType(slot)) return {
5507
+ Component: slot,
5508
+ slotProps: void 0
5509
+ };
5510
+ if (typeof slot === "string") return {
5511
+ Component: DefaultComponent,
5512
+ slotProps: { className: slot }
5513
+ };
5514
+ if (slot && typeof slot === "object") return {
5515
+ Component: DefaultComponent,
5516
+ slotProps: slot
5517
+ };
5518
+ return {
5519
+ Component: DefaultComponent,
5520
+ slotProps: void 0
5521
+ };
5522
+ }
5523
+ /**
4206
5524
  * Memoized wrapper for assistant messages to prevent re-renders when other messages change.
4207
5525
  */
4208
5526
  const MemoizedAssistantMessage = react.default.memo(function MemoizedAssistantMessage({ message, messages, isRunning, AssistantMessageComponent, slotProps }) {
@@ -4221,7 +5539,6 @@ const MemoizedAssistantMessage = react.default.memo(function MemoizedAssistantMe
4221
5539
  if (prevToolCalls && nextToolCalls) for (let i = 0; i < prevToolCalls.length; i++) {
4222
5540
  const prevTc = prevToolCalls[i];
4223
5541
  const nextTc = nextToolCalls[i];
4224
- if (!prevTc || !nextTc) return false;
4225
5542
  if (prevTc.id !== nextTc.id) return false;
4226
5543
  if (prevTc.function.arguments !== nextTc.function.arguments) return false;
4227
5544
  }
@@ -4300,6 +5617,7 @@ const MemoizedCustomMessage = react.default.memo(function MemoizedCustomMessage(
4300
5617
  if (JSON.stringify(prevProps.stateSnapshot) !== JSON.stringify(nextProps.stateSnapshot)) return false;
4301
5618
  return true;
4302
5619
  });
5620
+ const VIRTUALIZE_THRESHOLD = 50;
4303
5621
  function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) {
4304
5622
  const renderCustomMessage = useRenderCustomMessages();
4305
5623
  const { renderActivityMessage } = useRenderActivityMessage();
@@ -4333,9 +5651,34 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4333
5651
  if (!resolvedRunId) return void 0;
4334
5652
  return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
4335
5653
  };
4336
- const deduplicatedMessages = [...new Map(messages.map((m) => [m.id, m])).values()];
5654
+ const deduplicatedMessages = (0, react.useMemo)(() => [...new Map(messages.map((m) => [m.id, m])).values()], [messages]);
4337
5655
  if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Deduplicated ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
4338
- const messageElements = deduplicatedMessages.flatMap((message) => {
5656
+ const { Component: AssistantComponent, slotProps: assistantSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
5657
+ const { Component: UserComponent, slotProps: userSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(userMessage, CopilotChatUserMessage_default), [userMessage]);
5658
+ const { Component: ReasoningComponent, slotProps: reasoningSlotProps } = (0, react.useMemo)(() => resolveSlotComponent(reasoningMessage, CopilotChatReasoningMessage_default), [reasoningMessage]);
5659
+ const scrollElementFromCtx = (0, react.useContext)(ScrollElementContext);
5660
+ const scrollElement = scrollElementFromCtx && scrollElementFromCtx.clientHeight > 0 ? scrollElementFromCtx : null;
5661
+ (0, react.useEffect)(() => {
5662
+ if (process.env.NODE_ENV !== "production" && scrollElementFromCtx && scrollElementFromCtx.clientHeight === 0) console.warn("[CopilotKit] Chat scroll container has clientHeight=0 — virtualization disabled. Ensure the chat is rendered in a visible container with a non-zero height.");
5663
+ }, [scrollElementFromCtx]);
5664
+ const shouldVirtualize = !!scrollElement && !children && deduplicatedMessages.length > VIRTUALIZE_THRESHOLD;
5665
+ const virtualizer = (0, _tanstack_react_virtual.useVirtualizer)({
5666
+ count: shouldVirtualize ? deduplicatedMessages.length : 0,
5667
+ getScrollElement: () => scrollElement,
5668
+ estimateSize: () => 100,
5669
+ overscan: 5,
5670
+ measureElement: (el) => el?.getBoundingClientRect().height ?? 0,
5671
+ initialRect: {
5672
+ width: 0,
5673
+ height: 600
5674
+ }
5675
+ });
5676
+ const firstMessageId = deduplicatedMessages[0]?.id;
5677
+ (0, react.useLayoutEffect)(() => {
5678
+ if (!shouldVirtualize || !deduplicatedMessages.length) return;
5679
+ virtualizer.scrollToIndex(deduplicatedMessages.length - 1, { align: "end" });
5680
+ }, [shouldVirtualize, firstMessageId]);
5681
+ const renderMessageBlock = (message) => {
4339
5682
  const elements = [];
4340
5683
  const stateSnapshot = getStateSnapshotForMessage(message.id);
4341
5684
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
@@ -4344,58 +5687,38 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4344
5687
  renderCustomMessage,
4345
5688
  stateSnapshot
4346
5689
  }, `${message.id}-custom-before`));
4347
- if (message.role === "assistant") {
4348
- let AssistantComponent = CopilotChatAssistantMessage_default;
4349
- let assistantSlotProps;
4350
- if (isReactComponentType(assistantMessage)) AssistantComponent = assistantMessage;
4351
- else if (typeof assistantMessage === "string") assistantSlotProps = { className: assistantMessage };
4352
- else if (assistantMessage && typeof assistantMessage === "object") assistantSlotProps = assistantMessage;
4353
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
4354
- message,
4355
- messages,
4356
- isRunning,
4357
- AssistantMessageComponent: AssistantComponent,
4358
- slotProps: assistantSlotProps
4359
- }, message.id));
4360
- } else if (message.role === "user") {
4361
- let UserComponent = CopilotChatUserMessage_default;
4362
- let userSlotProps;
4363
- if (isReactComponentType(userMessage)) UserComponent = userMessage;
4364
- else if (typeof userMessage === "string") userSlotProps = { className: userMessage };
4365
- else if (userMessage && typeof userMessage === "object") userSlotProps = userMessage;
4366
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
4367
- message,
4368
- UserMessageComponent: UserComponent,
4369
- slotProps: userSlotProps
4370
- }, message.id));
4371
- } else if (message.role === "activity") {
4372
- const activityMsg = message;
4373
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
4374
- message: activityMsg,
4375
- renderActivityMessage
4376
- }, message.id));
4377
- } else if (message.role === "reasoning") {
4378
- let ReasoningComponent = CopilotChatReasoningMessage_default;
4379
- let reasoningSlotProps;
4380
- if (isReactComponentType(reasoningMessage)) ReasoningComponent = reasoningMessage;
4381
- else if (typeof reasoningMessage === "string") reasoningSlotProps = { className: reasoningMessage };
4382
- else if (reasoningMessage && typeof reasoningMessage === "object") reasoningSlotProps = reasoningMessage;
4383
- elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
4384
- message,
4385
- messages,
4386
- isRunning,
4387
- ReasoningMessageComponent: ReasoningComponent,
4388
- slotProps: reasoningSlotProps
4389
- }, message.id));
4390
- }
5690
+ if (message.role === "assistant") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedAssistantMessage, {
5691
+ message,
5692
+ messages,
5693
+ isRunning,
5694
+ AssistantMessageComponent: AssistantComponent,
5695
+ slotProps: assistantSlotProps
5696
+ }, message.id));
5697
+ else if (message.role === "user") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedUserMessage, {
5698
+ message,
5699
+ UserMessageComponent: UserComponent,
5700
+ slotProps: userSlotProps
5701
+ }, message.id));
5702
+ else if (message.role === "activity") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedActivityMessage, {
5703
+ message,
5704
+ renderActivityMessage
5705
+ }, message.id));
5706
+ else if (message.role === "reasoning") elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedReasoningMessage, {
5707
+ message,
5708
+ messages,
5709
+ isRunning,
5710
+ ReasoningMessageComponent: ReasoningComponent,
5711
+ slotProps: reasoningSlotProps
5712
+ }, message.id));
4391
5713
  if (renderCustomMessage) elements.push(/* @__PURE__ */ (0, react_jsx_runtime.jsx)(MemoizedCustomMessage, {
4392
5714
  message,
4393
5715
  position: "after",
4394
5716
  renderCustomMessage,
4395
5717
  stateSnapshot
4396
5718
  }, `${message.id}-custom-after`));
4397
- return elements;
4398
- }).filter(Boolean);
5719
+ return elements.filter(Boolean);
5720
+ };
5721
+ const messageElements = shouldVirtualize ? [] : deduplicatedMessages.flatMap(renderMessageBlock);
4399
5722
  if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4400
5723
  "data-copilotkit": true,
4401
5724
  style: { display: "contents" },
@@ -4409,27 +5732,353 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4409
5732
  const lastMessage = messages[messages.length - 1];
4410
5733
  const showCursor = isRunning && lastMessage?.role !== "reasoning";
4411
5734
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4412
- "data-copilotkit": true,
4413
- "data-testid": "copilot-message-list",
4414
- className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
4415
- ...props,
5735
+ "data-copilotkit": true,
5736
+ "data-testid": "copilot-message-list",
5737
+ className: (0, tailwind_merge.twMerge)("copilotKitMessages cpk:flex cpk:flex-col", className),
5738
+ ...props,
5739
+ children: [
5740
+ shouldVirtualize ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5741
+ style: {
5742
+ height: virtualizer.getTotalSize(),
5743
+ position: "relative"
5744
+ },
5745
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
5746
+ const message = deduplicatedMessages[virtualItem.index];
5747
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5748
+ "data-index": virtualItem.index,
5749
+ ref: virtualizer.measureElement,
5750
+ style: {
5751
+ position: "absolute",
5752
+ top: 0,
5753
+ left: 0,
5754
+ width: "100%",
5755
+ transform: `translateY(${virtualItem.start}px)`
5756
+ },
5757
+ children: renderMessageBlock(message)
5758
+ }, message.id);
5759
+ })
5760
+ }) : messageElements,
5761
+ interruptElement,
5762
+ showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5763
+ className: "cpk:mt-2",
5764
+ children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
5765
+ })
5766
+ ]
5767
+ });
5768
+ }
5769
+ CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
5770
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5771
+ "data-testid": "copilot-loading-cursor",
5772
+ className: (0, tailwind_merge.twMerge)("cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse-cursor cpk:ml-1", className),
5773
+ ...props
5774
+ });
5775
+ };
5776
+
5777
+ //#endregion
5778
+ //#region src/v2/components/chat/CopilotChatAttachmentQueue.tsx
5779
+ const CopilotChatAttachmentQueue = ({ attachments, onRemoveAttachment, className }) => {
5780
+ if (attachments.length === 0) return null;
5781
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5782
+ className: cn("cpk:flex cpk:flex-wrap cpk:gap-2 cpk:p-2", className),
5783
+ children: attachments.map((attachment) => {
5784
+ const isMedia = attachment.type === "image" || attachment.type === "video";
5785
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5786
+ className: cn("cpk:relative cpk:inline-flex cpk:rounded-lg cpk:overflow-hidden cpk:border cpk:border-border", isMedia ? "cpk:w-[72px] cpk:h-[72px]" : attachment.type === "audio" ? "cpk:min-w-[200px] cpk:max-w-[280px] cpk:flex-col cpk:p-1 cpk:pr-8" : "cpk:p-2 cpk:px-3 cpk:pr-8 cpk:max-w-[240px]"),
5787
+ children: [
5788
+ attachment.status === "uploading" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(UploadingOverlay, {}),
5789
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AttachmentPreview, { attachment }),
5790
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5791
+ onClick: () => onRemoveAttachment(attachment.id),
5792
+ className: cn("cpk:absolute cpk:bg-black/60 cpk:text-white cpk:border-none cpk:rounded-full cpk:w-5 cpk:h-5 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer cpk:text-[10px] cpk:z-20", isMedia ? "cpk:top-1 cpk:right-1" : "cpk:top-1.5 cpk:right-1.5"),
5793
+ "aria-label": "Remove attachment",
5794
+ children: "✕"
5795
+ })
5796
+ ]
5797
+ }, attachment.id);
5798
+ })
5799
+ });
5800
+ };
5801
+ function UploadingOverlay() {
5802
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5803
+ className: "cpk:absolute cpk:inset-0 cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/40 cpk:z-10",
5804
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "cpk:w-5 cpk:h-5 cpk:border-2 cpk:border-white cpk:border-t-transparent cpk:rounded-full cpk:animate-spin" })
5805
+ });
5806
+ }
5807
+ function AttachmentPreview({ attachment }) {
5808
+ if (attachment.status === "uploading") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "cpk:w-full cpk:h-full" });
5809
+ switch (attachment.type) {
5810
+ case "image": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ImagePreview, { attachment });
5811
+ case "audio": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(AudioPreview, { attachment });
5812
+ case "video": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(VideoPreview, { attachment });
5813
+ case "document": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentPreview, { attachment });
5814
+ }
5815
+ }
5816
+ function Lightbox({ onClose, children }) {
5817
+ (0, react.useEffect)(() => {
5818
+ const handleKey = (e) => {
5819
+ if (e.key === "Escape") onClose();
5820
+ };
5821
+ document.addEventListener("keydown", handleKey);
5822
+ return () => document.removeEventListener("keydown", handleKey);
5823
+ }, [onClose]);
5824
+ if (typeof document === "undefined") return null;
5825
+ return (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5826
+ className: "cpk:fixed cpk:inset-0 cpk:z-[9999] cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/80 cpk:animate-fade-in",
5827
+ onClick: onClose,
5828
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5829
+ onClick: onClose,
5830
+ className: "cpk:absolute cpk:top-4 cpk:right-4 cpk:text-white cpk:bg-white/10 cpk:hover:bg-white/20 cpk:rounded-full cpk:w-10 cpk:h-10 cpk:flex cpk:items-center cpk:justify-center cpk:cursor-pointer cpk:border-none cpk:z-10",
5831
+ "aria-label": "Close preview",
5832
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.X, { className: "cpk:w-5 cpk:h-5" })
5833
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5834
+ onClick: (e) => e.stopPropagation(),
5835
+ children
5836
+ })]
5837
+ }), document.body);
5838
+ }
5839
+ /**
5840
+ * Hook that manages lightbox open/close and uses the View Transition API to
5841
+ * morph the thumbnail into fullscreen content.
5842
+ *
5843
+ * The trick: `view-transition-name` must live on exactly ONE element at a time.
5844
+ * - Old state (thumbnail visible): name is on the thumbnail.
5845
+ * - New state (lightbox visible): name moves to the lightbox content.
5846
+ * `flushSync` ensures React commits the DOM change synchronously inside the
5847
+ * `startViewTransition` callback so the API can snapshot old → new correctly.
5848
+ */
5849
+ function useLightbox() {
5850
+ const thumbnailRef = (0, react.useRef)(null);
5851
+ const [open, setOpen] = (0, react.useState)(false);
5852
+ const vtName = (0, react.useId)();
5853
+ return {
5854
+ thumbnailRef,
5855
+ vtName,
5856
+ open,
5857
+ openLightbox: (0, react.useCallback)(() => {
5858
+ const thumb = thumbnailRef.current;
5859
+ const doc = document;
5860
+ if (doc.startViewTransition && thumb) {
5861
+ thumb.style.viewTransitionName = vtName;
5862
+ doc.startViewTransition(() => {
5863
+ thumb.style.viewTransitionName = "";
5864
+ (0, react_dom.flushSync)(() => setOpen(true));
5865
+ });
5866
+ } else setOpen(true);
5867
+ }, []),
5868
+ closeLightbox: (0, react.useCallback)(() => {
5869
+ const thumb = thumbnailRef.current;
5870
+ const doc = document;
5871
+ if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
5872
+ (0, react_dom.flushSync)(() => setOpen(false));
5873
+ thumb.style.viewTransitionName = vtName;
5874
+ }).finished.then(() => {
5875
+ thumb.style.viewTransitionName = "";
5876
+ }).catch(() => {
5877
+ thumb.style.viewTransitionName = "";
5878
+ });
5879
+ else setOpen(false);
5880
+ }, [])
5881
+ };
5882
+ }
5883
+ function ImagePreview({ attachment }) {
5884
+ const src = (0, _copilotkit_shared.getSourceUrl)(attachment.source);
5885
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5886
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5887
+ ref: thumbnailRef,
5888
+ src,
5889
+ alt: attachment.filename || "Image attachment",
5890
+ className: "cpk:w-full cpk:h-full cpk:object-cover cpk:cursor-pointer",
5891
+ onClick: openLightbox
5892
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
5893
+ onClose: closeLightbox,
5894
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5895
+ style: { viewTransitionName: vtName },
5896
+ src,
5897
+ alt: attachment.filename || "Image attachment",
5898
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:object-contain cpk:rounded-lg"
5899
+ })
5900
+ })] });
5901
+ }
5902
+ function AudioPreview({ attachment }) {
5903
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5904
+ className: "cpk:flex cpk:flex-col cpk:gap-1 cpk:w-full",
5905
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("audio", {
5906
+ src: (0, _copilotkit_shared.getSourceUrl)(attachment.source),
5907
+ controls: true,
5908
+ preload: "metadata",
5909
+ className: "cpk:w-full cpk:h-8"
5910
+ }), attachment.filename && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
5911
+ className: "cpk:text-xs cpk:font-medium cpk:overflow-hidden cpk:text-ellipsis cpk:whitespace-nowrap",
5912
+ children: attachment.filename
5913
+ })]
5914
+ });
5915
+ }
5916
+ function VideoPreview({ attachment }) {
5917
+ const src = (0, _copilotkit_shared.getSourceUrl)(attachment.source);
5918
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5919
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
5920
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5921
+ ref: thumbnailRef,
5922
+ className: "cpk:w-full cpk:h-full",
5923
+ children: attachment.thumbnail ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", {
5924
+ src: attachment.thumbnail,
5925
+ alt: attachment.filename || "Video thumbnail",
5926
+ className: "cpk:w-full cpk:h-full cpk:object-cover"
5927
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
5928
+ src,
5929
+ preload: "metadata",
5930
+ muted: true,
5931
+ className: "cpk:w-full cpk:h-full cpk:object-cover"
5932
+ })
5933
+ }),
5934
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
5935
+ onClick: openLightbox,
5936
+ className: "cpk:absolute cpk:inset-0 cpk:flex cpk:items-center cpk:justify-center cpk:z-10 cpk:cursor-pointer cpk:bg-black/20 cpk:border-none cpk:p-0",
5937
+ "aria-label": "Play video",
5938
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5939
+ className: "cpk:w-8 cpk:h-8 cpk:rounded-full cpk:bg-black/60 cpk:flex cpk:items-center cpk:justify-center",
5940
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Play, { className: "cpk:w-4 cpk:h-4 cpk:text-white cpk:ml-0.5" })
5941
+ })
5942
+ }),
5943
+ open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
5944
+ onClose: closeLightbox,
5945
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("video", {
5946
+ style: { viewTransitionName: vtName },
5947
+ src,
5948
+ controls: true,
5949
+ autoPlay: true,
5950
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:rounded-lg"
5951
+ })
5952
+ })
5953
+ ] });
5954
+ }
5955
+ function isPdf(mimeType) {
5956
+ return !!mimeType && mimeType.includes("pdf");
5957
+ }
5958
+ function isText(mimeType) {
5959
+ return !!mimeType && mimeType.startsWith("text/");
5960
+ }
5961
+ function canPreviewInBrowser(mimeType) {
5962
+ return isPdf(mimeType) || isText(mimeType);
5963
+ }
5964
+ /**
5965
+ * Convert a base64-encoded data source to a blob: URL that browsers will
5966
+ * render inside an iframe (data: URLs are blocked for PDFs in most browsers).
5967
+ */
5968
+ function useBlobUrl(attachment) {
5969
+ const [url, setUrl] = (0, react.useState)(null);
5970
+ (0, react.useEffect)(() => {
5971
+ if (attachment.source.type !== "data") return;
5972
+ try {
5973
+ const binary = atob(attachment.source.value);
5974
+ const bytes = new Uint8Array(binary.length);
5975
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
5976
+ const blob = new Blob([bytes], { type: attachment.source.mimeType || "application/octet-stream" });
5977
+ const blobUrl = URL.createObjectURL(blob);
5978
+ setUrl(blobUrl);
5979
+ return () => URL.revokeObjectURL(blobUrl);
5980
+ } catch (error) {
5981
+ console.error("[CopilotKit] Failed to decode attachment data:", error);
5982
+ setUrl(null);
5983
+ }
5984
+ }, [
5985
+ attachment.source.type,
5986
+ attachment.source.value,
5987
+ attachment.source.mimeType
5988
+ ]);
5989
+ if (attachment.source.type === "url") return attachment.source.value;
5990
+ return url;
5991
+ }
5992
+ function DocumentLightboxContent({ attachment, vtName }) {
5993
+ const mimeType = attachment.source.mimeType;
5994
+ const blobUrl = useBlobUrl(attachment);
5995
+ if (isPdf(mimeType)) {
5996
+ if (!blobUrl) return null;
5997
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
5998
+ style: { viewTransitionName: vtName },
5999
+ src: blobUrl,
6000
+ title: attachment.filename || "PDF preview",
6001
+ className: "cpk:w-[90vw] cpk:h-[90vh] cpk:max-w-[1000px] cpk:rounded-lg cpk:bg-white"
6002
+ });
6003
+ }
6004
+ if (isText(mimeType)) {
6005
+ const textContent = attachment.source.type === "data" ? (() => {
6006
+ try {
6007
+ return atob(attachment.source.value);
6008
+ } catch {
6009
+ return attachment.source.value;
6010
+ }
6011
+ })() : null;
6012
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6013
+ style: { viewTransitionName: vtName },
6014
+ className: "cpk:w-[90vw] cpk:max-w-[800px] cpk:max-h-[90vh] cpk:overflow-auto cpk:rounded-lg cpk:bg-white cpk:dark:bg-gray-900 cpk:p-6",
6015
+ children: [attachment.filename && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6016
+ className: "cpk:text-sm cpk:font-medium cpk:text-gray-500 cpk:dark:text-gray-400 cpk:mb-4 cpk:pb-2 cpk:border-b cpk:border-gray-200 cpk:dark:border-gray-700",
6017
+ children: attachment.filename
6018
+ }), textContent ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("pre", {
6019
+ className: "cpk:text-sm cpk:whitespace-pre-wrap cpk:break-words cpk:text-gray-800 cpk:dark:text-gray-200 cpk:font-mono cpk:m-0",
6020
+ children: textContent
6021
+ }) : blobUrl ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("iframe", {
6022
+ src: blobUrl,
6023
+ title: attachment.filename || "Text preview",
6024
+ className: "cpk:w-full cpk:h-[80vh] cpk:border-none"
6025
+ }) : null]
6026
+ });
6027
+ }
6028
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6029
+ style: { viewTransitionName: vtName },
6030
+ className: "cpk:flex cpk:flex-col cpk:items-center cpk:gap-4 cpk:p-8 cpk:rounded-lg cpk:bg-white cpk:dark:bg-gray-900",
4416
6031
  children: [
4417
- messageElements,
4418
- interruptElement,
4419
- showCursor && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4420
- className: "cpk:mt-2",
4421
- children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
6032
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6033
+ className: "cpk:w-16 cpk:h-16 cpk:rounded-xl cpk:bg-primary cpk:text-primary-foreground cpk:flex cpk:items-center cpk:justify-center cpk:text-xl cpk:font-bold",
6034
+ children: (0, _copilotkit_shared.getDocumentIcon)(mimeType ?? "")
6035
+ }),
6036
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6037
+ className: "cpk:text-center",
6038
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6039
+ className: "cpk:text-base cpk:font-medium cpk:text-gray-800 cpk:dark:text-gray-200",
6040
+ children: attachment.filename || "Document"
6041
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6042
+ className: "cpk:text-sm cpk:text-gray-500 cpk:dark:text-gray-400 cpk:mt-1",
6043
+ children: [mimeType || "Unknown type", attachment.size != null && ` · ${(0, _copilotkit_shared.formatFileSize)(attachment.size)}`]
6044
+ })]
6045
+ }),
6046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6047
+ className: "cpk:text-xs cpk:text-gray-400 cpk:dark:text-gray-500",
6048
+ children: "No preview available for this file type"
4422
6049
  })
4423
6050
  ]
4424
6051
  });
4425
6052
  }
4426
- CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
4427
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4428
- "data-testid": "copilot-loading-cursor",
4429
- className: (0, tailwind_merge.twMerge)("cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse-cursor cpk:ml-1", className),
4430
- ...props
4431
- });
4432
- };
6053
+ function DocumentPreview({ attachment }) {
6054
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
6055
+ const mimeType = attachment.source.mimeType;
6056
+ const previewable = canPreviewInBrowser(mimeType);
6057
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6058
+ ref: thumbnailRef,
6059
+ className: cn("cpk:flex cpk:items-center cpk:gap-2", previewable && "cpk:cursor-pointer"),
6060
+ onClick: previewable ? openLightbox : void 0,
6061
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6062
+ className: "cpk:w-8 cpk:h-8 cpk:rounded-md cpk:bg-primary cpk:text-primary-foreground cpk:flex cpk:items-center cpk:justify-center cpk:text-[10px] cpk:font-semibold cpk:shrink-0",
6063
+ children: (0, _copilotkit_shared.getDocumentIcon)(mimeType ?? "")
6064
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6065
+ className: "cpk:flex cpk:flex-col cpk:min-w-0",
6066
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6067
+ className: "cpk:text-xs cpk:font-medium cpk:break-all cpk:leading-tight",
6068
+ children: attachment.filename || "Document"
6069
+ }), attachment.size != null && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6070
+ className: "cpk:text-[11px] cpk:text-muted-foreground",
6071
+ children: (0, _copilotkit_shared.formatFileSize)(attachment.size)
6072
+ })]
6073
+ })]
6074
+ }), open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Lightbox, {
6075
+ onClose: closeLightbox,
6076
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DocumentLightboxContent, {
6077
+ attachment,
6078
+ vtName
6079
+ })
6080
+ })] });
6081
+ }
4433
6082
 
4434
6083
  //#endregion
4435
6084
  //#region src/v2/hooks/use-keyboard-height.tsx
@@ -4475,7 +6124,19 @@ function useKeyboardHeight() {
4475
6124
  //#endregion
4476
6125
  //#region src/v2/components/chat/CopilotChatView.tsx
4477
6126
  const FEATHER_HEIGHT = 96;
4478
- function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, disclaimer, children, className, ...props }) {
6127
+ function DropOverlay() {
6128
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6129
+ className: cn("cpk:absolute cpk:inset-0 cpk:z-50 cpk:pointer-events-none", "cpk:flex cpk:items-center cpk:justify-center", "cpk:bg-primary/5 cpk:backdrop-blur-[2px]", "cpk:border-2 cpk:border-dashed cpk:border-primary/40 cpk:rounded-lg cpk:m-2"),
6130
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6131
+ className: "cpk:flex cpk:flex-col cpk:items-center cpk:gap-2 cpk:text-primary/70",
6132
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Upload, { className: "cpk:w-8 cpk:h-8" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
6133
+ className: "cpk:text-sm cpk:font-medium",
6134
+ children: "Drop files here"
6135
+ })]
6136
+ })
6137
+ });
6138
+ }
6139
+ 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, disclaimer, children, className, ...props }) {
4479
6140
  const inputContainerRef = (0, react.useRef)(null);
4480
6141
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
4481
6142
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -4522,6 +6183,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4522
6183
  onCancelTranscribe,
4523
6184
  onFinishTranscribe,
4524
6185
  onFinishTranscribeWithAudio,
6186
+ onAddFile,
4525
6187
  positioning: "static",
4526
6188
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
4527
6189
  containerRef: inputContainerRef,
@@ -4562,21 +6224,34 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4562
6224
  onCancelTranscribe,
4563
6225
  onFinishTranscribe,
4564
6226
  onFinishTranscribeWithAudio,
6227
+ onAddFile,
4565
6228
  positioning: "static",
4566
6229
  showDisclaimer: true,
4567
6230
  ...disclaimer !== void 0 ? { disclaimer } : {}
4568
6231
  });
4569
- const BoundWelcomeScreen = renderSlot(welcomeScreen === true ? void 0 : welcomeScreen, CopilotChatView.WelcomeScreen, {
4570
- input: BoundInputForWelcome,
6232
+ const welcomeScreenSlot = welcomeScreen === true ? void 0 : welcomeScreen;
6233
+ const inputWithAttachments = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6234
+ className: "cpk:w-full",
6235
+ children: [attachments && attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentQueue, {
6236
+ attachments,
6237
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
6238
+ className: "cpk:mb-2"
6239
+ }), BoundInputForWelcome]
6240
+ });
6241
+ const BoundWelcomeScreen = renderSlot(welcomeScreenSlot, CopilotChatView.WelcomeScreen, {
6242
+ input: inputWithAttachments,
4571
6243
  suggestionView: BoundSuggestionView ?? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, {})
4572
6244
  });
4573
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6245
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4574
6246
  "data-copilotkit": true,
4575
6247
  "data-testid": "copilot-chat",
4576
6248
  "data-copilot-running": isRunning ? "true" : "false",
4577
- className: (0, tailwind_merge.twMerge)("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
6249
+ onDragOver,
6250
+ onDragLeave,
6251
+ onDrop,
6252
+ className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
4578
6253
  ...props,
4579
- children: BoundWelcomeScreen
6254
+ children: [dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}), BoundWelcomeScreen]
4580
6255
  });
4581
6256
  }
4582
6257
  if (children) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -4593,39 +6268,66 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4593
6268
  "data-copilotkit": true,
4594
6269
  "data-testid": "copilot-chat",
4595
6270
  "data-copilot-running": isRunning ? "true" : "false",
4596
- className: (0, tailwind_merge.twMerge)("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
6271
+ onDragOver,
6272
+ onDragLeave,
6273
+ onDrop,
6274
+ className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
4597
6275
  ...props,
4598
- children: [BoundScrollView, BoundInput]
6276
+ children: [
6277
+ dragOver && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DropOverlay, {}),
6278
+ BoundScrollView,
6279
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6280
+ className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full",
6281
+ children: attachments && attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatAttachmentQueue, {
6282
+ attachments,
6283
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
6284
+ className: "cpk:px-4"
6285
+ })
6286
+ }),
6287
+ BoundInput
6288
+ ]
4599
6289
  });
4600
6290
  }
4601
6291
  (function(_CopilotChatView) {
4602
6292
  const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
4603
- const { isAtBottom, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottomContext)();
6293
+ const { isAtBottom, scrollToBottom, scrollRef } = (0, use_stick_to_bottom.useStickToBottomContext)();
6294
+ const [scrollEl, setScrollEl] = (0, react.useState)(null);
6295
+ (0, react.useLayoutEffect)(() => {
6296
+ setScrollEl(scrollRef.current ?? null);
6297
+ }, []);
4604
6298
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
4605
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
4606
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom.Content, {
4607
- className: "cpk:overflow-y-auto cpk:overflow-x-hidden",
4608
- style: {
4609
- flex: "1 1 0%",
4610
- minHeight: 0
4611
- },
4612
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4613
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
4614
- children
6299
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6300
+ value: scrollEl,
6301
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [
6302
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom.Content, {
6303
+ className: "cpk:overflow-y-auto cpk:overflow-x-hidden",
6304
+ style: {
6305
+ flex: "1 1 0%",
6306
+ minHeight: 0
6307
+ },
6308
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6309
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6310
+ children
6311
+ })
6312
+ }),
6313
+ BoundFeather,
6314
+ !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6315
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6316
+ style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6317
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4615
6318
  })
4616
- }),
4617
- BoundFeather,
4618
- !isAtBottom && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4619
- className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
4620
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
4621
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4622
- })
4623
- ] });
6319
+ ] })
6320
+ });
4624
6321
  };
4625
6322
  _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
4626
6323
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
4627
6324
  const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
4628
6325
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6326
+ const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6327
+ const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
6328
+ scrollRef.current = el;
6329
+ setNonAutoScrollEl(el);
6330
+ }, []);
4629
6331
  (0, react.useEffect)(() => {
4630
6332
  setHasMounted(true);
4631
6333
  }, []);
@@ -4654,23 +6356,26 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4654
6356
  });
4655
6357
  if (!autoScroll) {
4656
6358
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
4657
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4658
- ref: scrollRef,
4659
- className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden cpk:relative", className),
4660
- ...props,
4661
- children: [
4662
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4663
- ref: contentRef,
4664
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
4665
- children
4666
- }),
4667
- BoundFeather,
4668
- showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4669
- className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
4670
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
4671
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4672
- })
4673
- ]
6359
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6360
+ value: nonAutoScrollEl,
6361
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6362
+ ref: nonAutoScrollRefCallback,
6363
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden cpk:relative", className),
6364
+ ...props,
6365
+ children: [
6366
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6367
+ ref: contentRef,
6368
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6369
+ children
6370
+ }),
6371
+ BoundFeather,
6372
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6373
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6374
+ style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6375
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6376
+ })
6377
+ ]
6378
+ })
4674
6379
  });
4675
6380
  }
4676
6381
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
@@ -4862,13 +6567,14 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
4862
6567
 
4863
6568
  //#endregion
4864
6569
  //#region src/v2/components/chat/CopilotChat.tsx
4865
- function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, onError, ...props }) {
6570
+ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
4866
6571
  const existingConfig = useCopilotChatConfiguration();
4867
6572
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
4868
6573
  const resolvedThreadId = (0, react.useMemo)(() => threadId ?? existingConfig?.threadId ?? (0, _copilotkit_shared.randomUUID)(), [threadId, existingConfig?.threadId]);
4869
6574
  const { agent } = useAgent({
4870
6575
  agentId: resolvedAgentId,
4871
- threadId: resolvedThreadId
6576
+ threadId: resolvedThreadId,
6577
+ throttleMs
4872
6578
  });
4873
6579
  const { copilotkit } = useCopilotKit();
4874
6580
  const { suggestions: autoSuggestions } = useSuggestions({ agentId: resolvedAgentId });
@@ -4898,6 +6604,7 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4898
6604
  const [inputValue, setInputValue] = (0, react.useState)("");
4899
6605
  const [transcriptionError, setTranscriptionError] = (0, react.useState)(null);
4900
6606
  const [isTranscribing, setIsTranscribing] = (0, react.useState)(false);
6607
+ const { attachments: selectedAttachments, enabled: attachmentsEnabled, dragOver, fileInputRef, containerRef: chatContainerRef, handleFileUpload, handleDragOver, handleDragLeave, handleDrop, removeAttachment, consumeAttachments } = useAttachments({ config: attachmentsConfig });
4901
6608
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
4902
6609
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
4903
6610
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
@@ -4925,7 +6632,31 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4925
6632
  resolvedAgentId
4926
6633
  ]);
4927
6634
  const onSubmitInput = (0, react.useCallback)(async (value) => {
4928
- agent.addMessage({
6635
+ if (selectedAttachments.some((a) => a.status === "uploading")) {
6636
+ console.error("[CopilotKit] Cannot send while attachments are uploading");
6637
+ return;
6638
+ }
6639
+ const readyAttachments = consumeAttachments();
6640
+ if (readyAttachments.length > 0) {
6641
+ const contentParts = [];
6642
+ if (value.trim()) contentParts.push({
6643
+ type: "text",
6644
+ text: value
6645
+ });
6646
+ for (const att of readyAttachments) contentParts.push({
6647
+ type: att.type,
6648
+ source: att.source,
6649
+ metadata: {
6650
+ ...att.filename ? { filename: att.filename } : {},
6651
+ ...att.metadata
6652
+ }
6653
+ });
6654
+ agent.addMessage({
6655
+ id: (0, _copilotkit_shared.randomUUID)(),
6656
+ role: "user",
6657
+ content: contentParts
6658
+ });
6659
+ } else agent.addMessage({
4929
6660
  id: (0, _copilotkit_shared.randomUUID)(),
4930
6661
  role: "user",
4931
6662
  content: value
@@ -4936,7 +6667,11 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4936
6667
  } catch (error) {
4937
6668
  console.error("CopilotChat: runAgent failed", error);
4938
6669
  }
4939
- }, [agent]);
6670
+ }, [
6671
+ agent,
6672
+ selectedAttachments,
6673
+ consumeAttachments
6674
+ ]);
4940
6675
  const handleSelectSuggestion = (0, react.useCallback)(async (suggestion) => {
4941
6676
  agent.addMessage({
4942
6677
  id: (0, _copilotkit_shared.randomUUID)(),
@@ -5023,21 +6758,33 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
5023
6758
  return () => clearTimeout(timer);
5024
6759
  }
5025
6760
  }, [transcriptionError]);
5026
- const mergedProps = (0, ts_deepmerge.merge)({
6761
+ const stableMessageView = useShallowStableRef(typeof providedMessageView === "string" ? { className: providedMessageView } : providedMessageView);
6762
+ const stableSuggestionView = useShallowStableRef(providedSuggestionView);
6763
+ const handleAddFile = (0, react.useCallback)(() => {
6764
+ setTimeout(() => {
6765
+ fileInputRef.current?.click();
6766
+ }, 100);
6767
+ }, []);
6768
+ const mergedProps = {
5027
6769
  isRunning: agent.isRunning,
5028
6770
  suggestions: autoSuggestions,
5029
6771
  onSelectSuggestion: handleSelectSuggestion,
5030
- suggestionView: providedSuggestionView
5031
- }, {
5032
- ...restProps,
5033
- ...typeof providedMessageView === "string" ? { messageView: { className: providedMessageView } } : providedMessageView !== void 0 ? { messageView: providedMessageView } : {}
5034
- });
6772
+ suggestionView: stableSuggestionView,
6773
+ ...restProps
6774
+ };
6775
+ if (stableMessageView !== void 0) mergedProps.messageView = stableMessageView;
5035
6776
  const hasMessages = agent.messages.length > 0;
5036
6777
  const effectiveStopHandler = agent.isRunning && hasMessages ? providedStopHandler ?? stopCurrentRun : providedStopHandler;
5037
6778
  const showTranscription = isTranscriptionEnabled && isMediaRecorderSupported;
5038
6779
  const effectiveMode = isTranscribing ? "processing" : transcribeMode;
5039
- const RenderedChatView = renderSlot(chatView, CopilotChatView, (0, ts_deepmerge.merge)(mergedProps, {
5040
- messages: (0, react.useMemo)(() => [...agent.messages], [JSON.stringify(agent.messages)]),
6780
+ const messages = (0, react.useMemo)(() => [...agent.messages], [agent.messages.map((m) => {
6781
+ const contentKey = typeof m.content === "string" ? m.content.length : Array.isArray(m.content) ? m.content.length : 0;
6782
+ const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6783
+ return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6784
+ }).join(",")]);
6785
+ const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6786
+ ...mergedProps,
6787
+ messages,
5041
6788
  onSubmitMessage: onSubmitInput,
5042
6789
  onStop: effectiveStopHandler,
5043
6790
  inputMode: effectiveMode,
@@ -5046,32 +6793,51 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
5046
6793
  onStartTranscribe: showTranscription ? handleStartTranscribe : void 0,
5047
6794
  onCancelTranscribe: showTranscription ? handleCancelTranscribe : void 0,
5048
6795
  onFinishTranscribe: showTranscription ? handleFinishTranscribe : void 0,
5049
- onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0
5050
- }));
5051
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotChatConfigurationProvider, {
6796
+ onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0,
6797
+ attachments: selectedAttachments,
6798
+ onRemoveAttachment: removeAttachment,
6799
+ onAddFile: attachmentsEnabled ? handleAddFile : void 0,
6800
+ dragOver,
6801
+ onDragOver: handleDragOver,
6802
+ onDragLeave: handleDragLeave,
6803
+ onDrop: handleDrop
6804
+ });
6805
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
5052
6806
  agentId: resolvedAgentId,
5053
6807
  threadId: resolvedThreadId,
5054
6808
  labels,
5055
6809
  isModalDefaultOpen,
5056
- children: [
5057
- !isChatLicensed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineFeatureWarning, { featureName: "Chat" }),
5058
- transcriptionError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5059
- style: {
5060
- position: "absolute",
5061
- bottom: "100px",
5062
- left: "50%",
5063
- transform: "translateX(-50%)",
5064
- backgroundColor: "#ef4444",
5065
- color: "white",
5066
- padding: "8px 16px",
5067
- borderRadius: "8px",
5068
- fontSize: "14px",
5069
- zIndex: 50
5070
- },
5071
- children: transcriptionError
5072
- }),
5073
- RenderedChatView
5074
- ]
6810
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6811
+ ref: chatContainerRef,
6812
+ style: { display: "contents" },
6813
+ children: [
6814
+ attachmentsEnabled && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
6815
+ type: "file",
6816
+ multiple: true,
6817
+ ref: fileInputRef,
6818
+ onChange: handleFileUpload,
6819
+ accept: attachmentsConfig?.accept ?? "*/*",
6820
+ style: { display: "none" }
6821
+ }),
6822
+ !isChatLicensed && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(InlineFeatureWarning, { featureName: "Chat" }),
6823
+ transcriptionError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6824
+ style: {
6825
+ position: "absolute",
6826
+ bottom: "100px",
6827
+ left: "50%",
6828
+ transform: "translateX(-50%)",
6829
+ backgroundColor: "#ef4444",
6830
+ color: "white",
6831
+ padding: "8px 16px",
6832
+ borderRadius: "8px",
6833
+ fontSize: "14px",
6834
+ zIndex: 50
6835
+ },
6836
+ children: transcriptionError
6837
+ }),
6838
+ RenderedChatView
6839
+ ]
6840
+ })
5075
6841
  });
5076
6842
  }
5077
6843
  (function(_CopilotChat) {
@@ -5936,6 +7702,227 @@ function useToast() {
5936
7702
  if (!context) throw new Error("useToast must be used within a ToastProvider");
5937
7703
  return context;
5938
7704
  }
7705
+ function formatBannerMessage(message) {
7706
+ const jsonMatch = message.match(/'message':\s*'([^']+)'/);
7707
+ if (jsonMatch) return jsonMatch[1];
7708
+ let cleaned = message.split(" - ")[0];
7709
+ cleaned = cleaned.split(": Error code")[0];
7710
+ cleaned = cleaned.replace(/:\s*\d{3}$/, "");
7711
+ cleaned = cleaned.replace(/See more:.*$/g, "");
7712
+ cleaned = cleaned.trim();
7713
+ return cleaned || "An error occurred.";
7714
+ }
7715
+ function extractUrl(message) {
7716
+ const markdownMatch = /\[([^\]]+)\]\(([^)]+)\)/.exec(message);
7717
+ if (markdownMatch) return {
7718
+ url: markdownMatch[2],
7719
+ text: "See More"
7720
+ };
7721
+ const plainMatch = /(https?:\/\/[^\s)]+)/.exec(message);
7722
+ if (plainMatch) return {
7723
+ url: plainMatch[0].replace(/[.,;:'"]*$/, ""),
7724
+ text: "See More"
7725
+ };
7726
+ return null;
7727
+ }
7728
+ function BannerErrorDisplay({ bannerError, onDismiss }) {
7729
+ const [detailsExpanded, setDetailsExpanded] = (0, react.useState)(false);
7730
+ const colors = getErrorColors(getErrorSeverity(bannerError));
7731
+ const details = bannerError.details;
7732
+ const link = extractUrl(bannerError.message);
7733
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7734
+ style: {
7735
+ position: "fixed",
7736
+ bottom: "20px",
7737
+ left: "50%",
7738
+ transform: "translateX(-50%)",
7739
+ zIndex: 9999,
7740
+ backgroundColor: colors.background,
7741
+ border: `1px solid ${colors.border}`,
7742
+ borderLeft: `4px solid ${colors.border}`,
7743
+ borderRadius: "8px",
7744
+ padding: "12px 16px",
7745
+ fontSize: "13px",
7746
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
7747
+ backdropFilter: "blur(8px)",
7748
+ maxWidth: "min(90vw, 700px)",
7749
+ width: "100%",
7750
+ boxSizing: "border-box",
7751
+ overflow: "hidden"
7752
+ },
7753
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7754
+ style: {
7755
+ display: "flex",
7756
+ justifyContent: "space-between",
7757
+ alignItems: "center",
7758
+ gap: "10px"
7759
+ },
7760
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7761
+ style: {
7762
+ display: "flex",
7763
+ alignItems: "center",
7764
+ gap: "8px",
7765
+ flex: 1,
7766
+ minWidth: 0
7767
+ },
7768
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
7769
+ width: "12px",
7770
+ height: "12px",
7771
+ borderRadius: "50%",
7772
+ backgroundColor: colors.border,
7773
+ flexShrink: 0
7774
+ } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7775
+ style: {
7776
+ display: "flex",
7777
+ alignItems: "center",
7778
+ gap: "10px",
7779
+ flex: 1,
7780
+ minWidth: 0
7781
+ },
7782
+ children: [
7783
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
7784
+ style: {
7785
+ color: colors.text,
7786
+ lineHeight: "1.4",
7787
+ fontWeight: "400",
7788
+ fontSize: "13px",
7789
+ flex: 1,
7790
+ wordBreak: "break-all",
7791
+ overflowWrap: "break-word",
7792
+ maxWidth: "550px",
7793
+ overflow: "hidden",
7794
+ display: "-webkit-box",
7795
+ WebkitLineClamp: 10,
7796
+ WebkitBoxOrient: "vertical"
7797
+ },
7798
+ children: formatBannerMessage(bannerError.message)
7799
+ }),
7800
+ link && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7801
+ onClick: () => window.open(link.url, "_blank", "noopener,noreferrer"),
7802
+ style: {
7803
+ background: colors.border,
7804
+ color: "white",
7805
+ border: "none",
7806
+ borderRadius: "5px",
7807
+ padding: "4px 10px",
7808
+ fontSize: "11px",
7809
+ fontWeight: "500",
7810
+ cursor: "pointer",
7811
+ transition: "all 0.2s ease",
7812
+ flexShrink: 0
7813
+ },
7814
+ onMouseEnter: (e) => {
7815
+ e.currentTarget.style.opacity = "0.9";
7816
+ e.currentTarget.style.transform = "translateY(-1px)";
7817
+ },
7818
+ onMouseLeave: (e) => {
7819
+ e.currentTarget.style.opacity = "1";
7820
+ e.currentTarget.style.transform = "translateY(0)";
7821
+ },
7822
+ children: link.text
7823
+ }),
7824
+ details && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7825
+ onClick: () => setDetailsExpanded(!detailsExpanded),
7826
+ style: {
7827
+ background: "transparent",
7828
+ border: `1px solid ${colors.border}`,
7829
+ borderRadius: "5px",
7830
+ padding: "4px 10px",
7831
+ fontSize: "11px",
7832
+ fontWeight: "500",
7833
+ cursor: "pointer",
7834
+ color: colors.text,
7835
+ flexShrink: 0,
7836
+ transition: "all 0.2s ease"
7837
+ },
7838
+ onMouseEnter: (e) => {
7839
+ e.currentTarget.style.background = "rgba(0, 0, 0, 0.05)";
7840
+ },
7841
+ onMouseLeave: (e) => {
7842
+ e.currentTarget.style.background = "transparent";
7843
+ },
7844
+ children: detailsExpanded ? "Hide Details" : "Show Details"
7845
+ })
7846
+ ]
7847
+ })]
7848
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
7849
+ onClick: onDismiss,
7850
+ style: {
7851
+ background: "transparent",
7852
+ border: "none",
7853
+ color: colors.text,
7854
+ cursor: "pointer",
7855
+ padding: "2px",
7856
+ borderRadius: "3px",
7857
+ fontSize: "14px",
7858
+ lineHeight: "1",
7859
+ opacity: .6,
7860
+ transition: "all 0.2s ease",
7861
+ flexShrink: 0
7862
+ },
7863
+ title: "Dismiss",
7864
+ onMouseEnter: (e) => {
7865
+ e.currentTarget.style.opacity = "1";
7866
+ e.currentTarget.style.background = "rgba(0, 0, 0, 0.05)";
7867
+ },
7868
+ onMouseLeave: (e) => {
7869
+ e.currentTarget.style.opacity = "0.6";
7870
+ e.currentTarget.style.background = "transparent";
7871
+ },
7872
+ children: "x"
7873
+ })]
7874
+ }), detailsExpanded && details && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7875
+ style: {
7876
+ marginTop: "10px",
7877
+ padding: "10px",
7878
+ background: "rgba(0, 0, 0, 0.04)",
7879
+ borderRadius: "6px",
7880
+ fontSize: "11px",
7881
+ fontFamily: "monospace",
7882
+ color: colors.text,
7883
+ lineHeight: "1.5",
7884
+ maxHeight: "200px",
7885
+ overflowY: "auto",
7886
+ whiteSpace: "pre-wrap",
7887
+ wordBreak: "break-all"
7888
+ },
7889
+ children: [
7890
+ details.code && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [
7891
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Code:" }),
7892
+ " ",
7893
+ details.code
7894
+ ] }),
7895
+ details.originalMessage && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7896
+ style: { marginTop: "4px" },
7897
+ children: [
7898
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Message:" }),
7899
+ " ",
7900
+ details.originalMessage
7901
+ ]
7902
+ }),
7903
+ details.context && Object.keys(details.context).length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7904
+ style: { marginTop: "4px" },
7905
+ children: [
7906
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Context:" }),
7907
+ " ",
7908
+ JSON.stringify(details.context, null, 2)
7909
+ ]
7910
+ }),
7911
+ details.stack && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
7912
+ style: {
7913
+ marginTop: "4px",
7914
+ opacity: .7
7915
+ },
7916
+ children: [
7917
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("strong", { children: "Stack:" }),
7918
+ "\n",
7919
+ details.stack
7920
+ ]
7921
+ })
7922
+ ]
7923
+ })]
7924
+ });
7925
+ }
5939
7926
  function ToastProvider({ enabled, children }) {
5940
7927
  const [toasts, setToasts] = (0, react.useState)([]);
5941
7928
  const [bannerError, setBannerErrorState] = (0, react.useState)(null);
@@ -5973,156 +7960,10 @@ function ToastProvider({ enabled, children }) {
5973
7960
  };
5974
7961
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(ToastContext.Provider, {
5975
7962
  value,
5976
- children: [bannerError && (() => {
5977
- const colors = getErrorColors(getErrorSeverity(bannerError));
5978
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
5979
- style: {
5980
- position: "fixed",
5981
- bottom: "20px",
5982
- left: "50%",
5983
- transform: "translateX(-50%)",
5984
- zIndex: 9999,
5985
- backgroundColor: colors.background,
5986
- border: `1px solid ${colors.border}`,
5987
- borderLeft: `4px solid ${colors.border}`,
5988
- borderRadius: "8px",
5989
- padding: "12px 16px",
5990
- fontSize: "13px",
5991
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
5992
- backdropFilter: "blur(8px)",
5993
- maxWidth: "min(90vw, 700px)",
5994
- width: "100%",
5995
- boxSizing: "border-box",
5996
- overflow: "hidden"
5997
- },
5998
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
5999
- style: {
6000
- display: "flex",
6001
- justifyContent: "space-between",
6002
- alignItems: "center",
6003
- gap: "10px"
6004
- },
6005
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6006
- style: {
6007
- display: "flex",
6008
- alignItems: "center",
6009
- gap: "8px",
6010
- flex: 1,
6011
- minWidth: 0
6012
- },
6013
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: {
6014
- width: "12px",
6015
- height: "12px",
6016
- borderRadius: "50%",
6017
- backgroundColor: colors.border,
6018
- flexShrink: 0
6019
- } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6020
- style: {
6021
- display: "flex",
6022
- alignItems: "center",
6023
- gap: "10px",
6024
- flex: 1,
6025
- minWidth: 0
6026
- },
6027
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6028
- style: {
6029
- color: colors.text,
6030
- lineHeight: "1.4",
6031
- fontWeight: "400",
6032
- fontSize: "13px",
6033
- flex: 1,
6034
- wordBreak: "break-all",
6035
- overflowWrap: "break-word",
6036
- maxWidth: "550px",
6037
- overflow: "hidden",
6038
- display: "-webkit-box",
6039
- WebkitLineClamp: 10,
6040
- WebkitBoxOrient: "vertical"
6041
- },
6042
- children: (() => {
6043
- let message = bannerError.message;
6044
- const jsonMatch = message.match(/'message':\s*'([^']+)'/);
6045
- if (jsonMatch) return jsonMatch[1];
6046
- message = message.split(" - ")[0];
6047
- message = message.split(": Error code")[0];
6048
- message = message.replace(/:\s*\d{3}$/, "");
6049
- message = message.replace(/See more:.*$/g, "");
6050
- message = message.trim();
6051
- return message || "Configuration error occurred.";
6052
- })()
6053
- }), (() => {
6054
- const message = bannerError.message;
6055
- const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
6056
- const plainUrlRegex = /(https?:\/\/[^\s)]+)/g;
6057
- let url = null;
6058
- let buttonText = "See More";
6059
- const markdownMatch = markdownLinkRegex.exec(message);
6060
- if (markdownMatch) {
6061
- url = markdownMatch[2];
6062
- buttonText = "See More";
6063
- } else {
6064
- const urlMatch = plainUrlRegex.exec(message);
6065
- if (urlMatch) {
6066
- url = urlMatch[0].replace(/[.,;:'"]*$/, "");
6067
- buttonText = "See More";
6068
- }
6069
- }
6070
- if (!url) return null;
6071
- return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6072
- onClick: () => window.open(url, "_blank", "noopener,noreferrer"),
6073
- style: {
6074
- background: colors.border,
6075
- color: "white",
6076
- border: "none",
6077
- borderRadius: "5px",
6078
- padding: "4px 10px",
6079
- fontSize: "11px",
6080
- fontWeight: "500",
6081
- cursor: "pointer",
6082
- transition: "all 0.2s ease",
6083
- flexShrink: 0
6084
- },
6085
- onMouseEnter: (e) => {
6086
- e.currentTarget.style.opacity = "0.9";
6087
- e.currentTarget.style.transform = "translateY(-1px)";
6088
- },
6089
- onMouseLeave: (e) => {
6090
- e.currentTarget.style.opacity = "1";
6091
- e.currentTarget.style.transform = "translateY(0)";
6092
- },
6093
- children: buttonText
6094
- });
6095
- })()]
6096
- })]
6097
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
6098
- onClick: () => setBannerError(null),
6099
- style: {
6100
- background: "transparent",
6101
- border: "none",
6102
- color: colors.text,
6103
- cursor: "pointer",
6104
- padding: "2px",
6105
- borderRadius: "3px",
6106
- fontSize: "14px",
6107
- lineHeight: "1",
6108
- opacity: .6,
6109
- transition: "all 0.2s ease",
6110
- flexShrink: 0
6111
- },
6112
- title: "Dismiss",
6113
- onMouseEnter: (e) => {
6114
- e.currentTarget.style.opacity = "1";
6115
- e.currentTarget.style.background = "rgba(0, 0, 0, 0.05)";
6116
- },
6117
- onMouseLeave: (e) => {
6118
- e.currentTarget.style.opacity = "0.6";
6119
- e.currentTarget.style.background = "transparent";
6120
- },
6121
- children: "×"
6122
- })]
6123
- })
6124
- });
6125
- })(), children]
7963
+ children: [bannerError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(BannerErrorDisplay, {
7964
+ bannerError,
7965
+ onDismiss: () => setBannerError(null)
7966
+ }), children]
6126
7967
  });
6127
7968
  }
6128
7969
 
@@ -7129,12 +8970,21 @@ function CopilotListeners() {
7129
8970
  const { agent } = useAgent({ agentId: resolvedAgentId });
7130
8971
  usePredictStateSubscription(agent);
7131
8972
  (0, react.useEffect)(() => {
7132
- const subscription = copilotkit.subscribe({ onError: ({ error }) => {
7133
- setBannerError(new _copilotkit_shared.CopilotKitLowLevelError({
8973
+ const subscription = copilotkit.subscribe({ onError: ({ error, code, context }) => {
8974
+ if (error.name === "AbortError" || error.message === "Fetch is aborted" || error.message === "signal is aborted without reason" || error.message === "component unmounted" || !error.message) return;
8975
+ if (process.env.NODE_ENV === "development") console.error("[CopilotKit] Agent error:", error.message, "\n Code:", code, "\n Context:", context, "\n Stack:", error.stack);
8976
+ const ckError = new _copilotkit_shared.CopilotKitLowLevelError({
7134
8977
  error,
7135
8978
  message: error.message,
7136
8979
  url: typeof window !== "undefined" ? window.location.href : ""
7137
- }));
8980
+ });
8981
+ ckError.details = {
8982
+ code,
8983
+ context,
8984
+ stack: error.stack,
8985
+ originalMessage: error.message
8986
+ };
8987
+ setBannerError(ckError);
7138
8988
  } });
7139
8989
  return () => {
7140
8990
  subscription.unsubscribe();
@@ -7688,6 +9538,18 @@ Object.defineProperty(exports, 'CopilotChatAssistantMessage_default', {
7688
9538
  return CopilotChatAssistantMessage_default;
7689
9539
  }
7690
9540
  });
9541
+ Object.defineProperty(exports, 'CopilotChatAttachmentQueue', {
9542
+ enumerable: true,
9543
+ get: function () {
9544
+ return CopilotChatAttachmentQueue;
9545
+ }
9546
+ });
9547
+ Object.defineProperty(exports, 'CopilotChatAttachmentRenderer', {
9548
+ enumerable: true,
9549
+ get: function () {
9550
+ return CopilotChatAttachmentRenderer;
9551
+ }
9552
+ });
7691
9553
  Object.defineProperty(exports, 'CopilotChatAudioRecorder', {
7692
9554
  enumerable: true,
7693
9555
  get: function () {
@@ -7850,6 +9712,12 @@ Object.defineProperty(exports, 'MCPAppsActivityType', {
7850
9712
  return MCPAppsActivityType;
7851
9713
  }
7852
9714
  });
9715
+ Object.defineProperty(exports, 'SandboxFunctionsContext', {
9716
+ enumerable: true,
9717
+ get: function () {
9718
+ return SandboxFunctionsContext;
9719
+ }
9720
+ });
7853
9721
  Object.defineProperty(exports, 'ThreadsContext', {
7854
9722
  enumerable: true,
7855
9723
  get: function () {
@@ -7922,6 +9790,12 @@ Object.defineProperty(exports, 'useAsyncCallback', {
7922
9790
  return useAsyncCallback;
7923
9791
  }
7924
9792
  });
9793
+ Object.defineProperty(exports, 'useAttachments', {
9794
+ enumerable: true,
9795
+ get: function () {
9796
+ return useAttachments;
9797
+ }
9798
+ });
7925
9799
  Object.defineProperty(exports, 'useCoAgentStateRenders', {
7926
9800
  enumerable: true,
7927
9801
  get: function () {
@@ -8012,6 +9886,12 @@ Object.defineProperty(exports, 'useRenderToolCall', {
8012
9886
  return useRenderToolCall;
8013
9887
  }
8014
9888
  });
9889
+ Object.defineProperty(exports, 'useSandboxFunctions', {
9890
+ enumerable: true,
9891
+ get: function () {
9892
+ return useSandboxFunctions;
9893
+ }
9894
+ });
8015
9895
  Object.defineProperty(exports, 'useSuggestions', {
8016
9896
  enumerable: true,
8017
9897
  get: function () {
@@ -8036,4 +9916,4 @@ Object.defineProperty(exports, 'useToast', {
8036
9916
  return useToast;
8037
9917
  }
8038
9918
  });
8039
- //# sourceMappingURL=copilotkit-BDNjFNmk.cjs.map
9919
+ //# sourceMappingURL=copilotkit-Bz5-ImDl.cjs.map