@copilotkit/react-core 1.55.0-next.8 → 1.55.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 (94) hide show
  1. package/CHANGELOG.md +48 -5
  2. package/dist/{copilotkit-DNYSFuz5.mjs → copilotkit-BY5S1-0P.mjs} +2772 -858
  3. package/dist/copilotkit-BY5S1-0P.mjs.map +1 -0
  4. package/dist/{copilotkit-Dy5w3qEV.d.mts → copilotkit-BuhSUZHb.d.mts} +230 -17
  5. package/dist/copilotkit-BuhSUZHb.d.mts.map +1 -0
  6. package/dist/{copilotkit-B3Mb1yVE.cjs → copilotkit-Bz5-ImDl.cjs} +2776 -832
  7. package/dist/copilotkit-Bz5-ImDl.cjs.map +1 -0
  8. package/dist/{copilotkit-DBzgOMby.d.cts → copilotkit-dwDWYpya.d.cts} +230 -17
  9. package/dist/copilotkit-dwDWYpya.d.cts.map +1 -0
  10. package/dist/index.cjs +9 -4
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +1 -1
  13. package/dist/index.d.mts +1 -1
  14. package/dist/index.mjs +9 -4
  15. package/dist/index.mjs.map +1 -1
  16. package/dist/index.umd.js +1624 -396
  17. package/dist/index.umd.js.map +1 -1
  18. package/dist/v2/index.cjs +13 -1
  19. package/dist/v2/index.css +1 -1
  20. package/dist/v2/index.d.cts +3 -3
  21. package/dist/v2/index.d.mts +3 -3
  22. package/dist/v2/index.mjs +3 -2
  23. package/dist/v2/index.umd.js +2746 -790
  24. package/dist/v2/index.umd.js.map +1 -1
  25. package/package.json +62 -54
  26. package/scripts/scope-preflight.mjs +1 -2
  27. package/src/components/CopilotListeners.tsx +41 -8
  28. package/src/components/copilot-provider/__tests__/copilot-messages-key.test.tsx +92 -0
  29. package/src/components/copilot-provider/copilotkit-props.tsx +4 -2
  30. package/src/components/copilot-provider/copilotkit.tsx +3 -3
  31. package/src/components/toast/toast-provider.tsx +269 -194
  32. package/src/hooks/__tests__/use-copilot-chat-internal-connect.test.tsx +27 -16
  33. package/src/hooks/use-copilot-chat_internal.ts +15 -4
  34. package/src/v2/__tests__/A2UIMessageRenderer.test.tsx +86 -22
  35. package/src/v2/__tests__/utils/test-helpers.tsx +107 -7
  36. package/src/v2/a2ui/A2UICatalogContext.tsx +79 -0
  37. package/src/v2/a2ui/A2UIMessageRenderer.tsx +125 -37
  38. package/src/v2/a2ui/A2UIToolCallRenderer.tsx +290 -0
  39. package/src/v2/components/CopilotKitInspector.tsx +2 -0
  40. package/src/v2/components/OpenGenerativeUIRenderer.tsx +598 -0
  41. package/src/v2/components/__tests__/OpenGenerativeUIRenderer.test.tsx +665 -0
  42. package/src/v2/components/chat/CopilotChat.tsx +197 -52
  43. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +17 -2
  44. package/src/v2/components/chat/CopilotChatAttachmentQueue.tsx +481 -0
  45. package/src/v2/components/chat/CopilotChatAttachmentRenderer.tsx +139 -0
  46. package/src/v2/components/chat/CopilotChatInput.tsx +146 -77
  47. package/src/v2/components/chat/CopilotChatMessageView.tsx +260 -151
  48. package/src/v2/components/chat/CopilotChatSuggestionView.tsx +1 -0
  49. package/src/v2/components/chat/CopilotChatUserMessage.tsx +54 -0
  50. package/src/v2/components/chat/CopilotChatView.tsx +179 -66
  51. package/src/v2/components/chat/__tests__/CopilotChat.attachments.test.tsx +168 -0
  52. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +63 -2
  53. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +544 -1
  54. package/src/v2/components/chat/__tests__/CopilotChatPerf.e2e.test.tsx +268 -0
  55. package/src/v2/components/chat/__tests__/CopilotChatPropsRerender.e2e.test.tsx +249 -0
  56. package/src/v2/components/chat/__tests__/CopilotChatToolRendering.e2e.test.tsx +5 -2
  57. package/src/v2/components/chat/__tests__/CopilotChatToolRerenders.e2e.test.tsx +5 -2
  58. package/src/v2/components/chat/__tests__/MCPAppsActivityRenderer.e2e.test.tsx +60 -3
  59. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +138 -0
  60. package/src/v2/components/chat/index.ts +9 -0
  61. package/src/v2/components/chat/scroll-element-context.ts +13 -0
  62. package/src/v2/hooks/__tests__/use-agent-context-timing.e2e.test.tsx +8 -0
  63. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +327 -0
  64. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +1003 -0
  65. package/src/v2/hooks/__tests__/use-agent.e2e.test.tsx +13 -2
  66. package/src/v2/hooks/__tests__/use-attachments.test.tsx +169 -0
  67. package/src/v2/hooks/__tests__/use-frontend-tool.e2e.test.tsx +23 -4
  68. package/src/v2/hooks/__tests__/use-threads.test.tsx +54 -0
  69. package/src/v2/hooks/index.ts +5 -0
  70. package/src/v2/hooks/use-agent.tsx +220 -15
  71. package/src/v2/hooks/use-attachments.tsx +269 -0
  72. package/src/v2/hooks/use-frontend-tool.tsx +5 -2
  73. package/src/v2/hooks/use-render-activity-message.tsx +9 -2
  74. package/src/v2/hooks/use-render-custom-messages.tsx +6 -1
  75. package/src/v2/hooks/use-threads.tsx +35 -15
  76. package/src/v2/index.ts +5 -1
  77. package/src/v2/lib/__tests__/processPartialHtml.test.ts +112 -0
  78. package/src/v2/lib/__tests__/slots.test.ts +56 -0
  79. package/src/v2/lib/processPartialHtml.ts +45 -0
  80. package/src/v2/lib/slots.tsx +42 -1
  81. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +9 -3
  82. package/src/v2/providers/CopilotKitProvider.tsx +268 -32
  83. package/src/v2/providers/SandboxFunctionsContext.ts +10 -0
  84. package/src/v2/providers/__tests__/CopilotKitProvider.sandboxFunctions.test.tsx +198 -0
  85. package/src/v2/providers/__tests__/CopilotKitProvider.test.tsx +71 -0
  86. package/src/v2/providers/index.ts +7 -0
  87. package/src/v2/styles/globals.css +2 -1
  88. package/src/v2/types/index.ts +1 -0
  89. package/src/v2/types/sandbox-function.ts +11 -0
  90. package/dist/copilotkit-B3Mb1yVE.cjs.map +0 -1
  91. package/dist/copilotkit-DBzgOMby.d.cts.map +0 -1
  92. package/dist/copilotkit-DNYSFuz5.mjs.map +0 -1
  93. package/dist/copilotkit-Dy5w3qEV.d.mts.map +0 -1
  94. package/src/v2/components/__tests__/license-warning-banner.test.tsx +0 -46
@@ -125,7 +125,7 @@ export function CopilotChatInput({
125
125
  onChange,
126
126
  value,
127
127
  toolsMenu,
128
- autoFocus = true,
128
+ autoFocus = false,
129
129
  positioning = "static",
130
130
  keyboardHeight = 0,
131
131
  containerRef,
@@ -179,6 +179,12 @@ export function CopilotChatInput({
179
179
  paddingRight: 0,
180
180
  });
181
181
 
182
+ // Cached container dimensions — invalidated on resize, lazily repopulated on next layout pass.
183
+ // Eliminates getComputedStyle(grid) + 2x getBoundingClientRect per compact-layout evaluation.
184
+ const containerCacheRef = useRef<{
185
+ compactWidth: number;
186
+ } | null>(null);
187
+
182
188
  const commandItems = useMemo(() => {
183
189
  const entries: ToolsMenuItem[] = [];
184
190
  const seen = new Set<string>();
@@ -252,7 +258,7 @@ export function CopilotChatInput({
252
258
  }
253
259
 
254
260
  if (config?.isModalOpen && !previousModalStateRef.current) {
255
- inputRef.current?.focus();
261
+ inputRef.current?.focus({ preventScroll: true });
256
262
  }
257
263
 
258
264
  previousModalStateRef.current = config?.isModalOpen;
@@ -677,6 +683,36 @@ export function CopilotChatInput({
677
683
  });
678
684
  }, []);
679
685
 
686
+ const updateContainerCache = useCallback((): {
687
+ compactWidth: number;
688
+ } | null => {
689
+ const grid = gridRef.current;
690
+ const addContainer = addButtonContainerRef.current;
691
+ const actionsContainer = actionsContainerRef.current;
692
+ if (!grid || !addContainer || !actionsContainer) return null;
693
+
694
+ const gridStyles = window.getComputedStyle(grid);
695
+ const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0;
696
+ const paddingRight = parseFloat(gridStyles.paddingRight) || 0;
697
+ const columnGap = parseFloat(gridStyles.columnGap) || 0;
698
+ const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight;
699
+
700
+ if (gridAvailableWidth <= 0) return null;
701
+
702
+ const addWidth = addContainer.getBoundingClientRect().width;
703
+ const actionsWidth = actionsContainer.getBoundingClientRect().width;
704
+ const compactWidth = Math.max(
705
+ gridAvailableWidth - addWidth - actionsWidth - columnGap * 2,
706
+ 0,
707
+ );
708
+
709
+ if (compactWidth <= 0) return null;
710
+
711
+ const result = { compactWidth };
712
+ containerCacheRef.current = result;
713
+ return result;
714
+ }, []);
715
+
680
716
  const evaluateLayout = useCallback(() => {
681
717
  if (mode !== "input") {
682
718
  updateLayout("compact");
@@ -717,55 +753,71 @@ export function CopilotChatInput({
717
753
  let shouldExpand = hasExplicitBreak || renderedMultiline;
718
754
 
719
755
  if (!shouldExpand) {
720
- const gridStyles = window.getComputedStyle(grid);
721
- const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0;
722
- const paddingRight = parseFloat(gridStyles.paddingRight) || 0;
723
- const columnGap = parseFloat(gridStyles.columnGap) || 0;
724
- const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight;
725
-
726
- if (gridAvailableWidth > 0) {
727
- const addWidth = addContainer.getBoundingClientRect().width;
728
- const actionsWidth = actionsContainer.getBoundingClientRect().width;
729
- const compactWidth = Math.max(
730
- gridAvailableWidth - addWidth - actionsWidth - columnGap * 2,
756
+ // Use cached container dimensions (lazily populated on first access, invalidated on resize).
757
+ const cache = containerCacheRef.current ?? updateContainerCache();
758
+
759
+ if (cache && cache.compactWidth > 0) {
760
+ const compactInnerWidth = Math.max(
761
+ cache.compactWidth -
762
+ (measurementsRef.current.paddingLeft || 0) -
763
+ (measurementsRef.current.paddingRight || 0),
731
764
  0,
732
765
  );
733
766
 
734
- const canvas =
735
- measurementCanvasRef.current ?? document.createElement("canvas");
736
- if (!measurementCanvasRef.current) {
737
- measurementCanvasRef.current = canvas;
738
- }
739
-
740
- const context = canvas.getContext("2d");
741
- if (context) {
767
+ if (compactInnerWidth > 0) {
768
+ // Read font fresh each evaluation — getComputedStyle for style-only
769
+ // properties is cheap and avoids stale values after CSS/theme changes.
742
770
  const textareaStyles = window.getComputedStyle(textarea);
743
- const font =
744
- textareaStyles.font ||
745
- `${textareaStyles.fontStyle} ${textareaStyles.fontVariant} ${textareaStyles.fontWeight} ${textareaStyles.fontSize}/${textareaStyles.lineHeight} ${textareaStyles.fontFamily}`;
746
- context.font = font;
747
-
748
- const compactInnerWidth = Math.max(
749
- compactWidth -
750
- (measurementsRef.current.paddingLeft || 0) -
751
- (measurementsRef.current.paddingRight || 0),
752
- 0,
753
- );
771
+ let font = textareaStyles.font;
772
+ if (!font) {
773
+ const {
774
+ fontStyle,
775
+ fontVariant,
776
+ fontWeight,
777
+ fontSize,
778
+ lineHeight,
779
+ fontFamily,
780
+ } = textareaStyles;
781
+ if (fontSize && fontFamily) {
782
+ font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
783
+ }
784
+ }
754
785
 
755
- if (compactInnerWidth > 0) {
756
- const lines =
757
- resolvedValue.length > 0 ? resolvedValue.split("\n") : [""];
758
- let longestWidth = 0;
759
- for (const line of lines) {
760
- const metrics = context.measureText(line || " ");
761
- if (metrics.width > longestWidth) {
762
- longestWidth = metrics.width;
763
- }
786
+ if (font?.trim()) {
787
+ const canvas =
788
+ measurementCanvasRef.current ?? document.createElement("canvas");
789
+ if (!measurementCanvasRef.current) {
790
+ measurementCanvasRef.current = canvas;
764
791
  }
765
792
 
766
- if (longestWidth > compactInnerWidth) {
767
- shouldExpand = true;
793
+ const context = canvas.getContext("2d");
794
+ if (context) {
795
+ context.font = font;
796
+
797
+ const lines =
798
+ resolvedValue.length > 0 ? resolvedValue.split("\n") : [""];
799
+ let longestWidth = 0;
800
+ for (const line of lines) {
801
+ const metrics = context.measureText(line || " ");
802
+ if (metrics.width > longestWidth) {
803
+ longestWidth = metrics.width;
804
+ }
805
+ }
806
+
807
+ if (longestWidth > compactInnerWidth) {
808
+ shouldExpand = true;
809
+ }
810
+ } else if (process.env.NODE_ENV !== "production") {
811
+ console.warn(
812
+ "[CopilotChatInput] canvas.getContext('2d') returned null. " +
813
+ "Text-width-based expansion will be unavailable.",
814
+ );
768
815
  }
816
+ } else if (process.env.NODE_ENV !== "production") {
817
+ console.warn(
818
+ "[CopilotChatInput] Could not resolve textarea font for layout measurement. " +
819
+ "Text-width-based expansion will be skipped until the next evaluation.",
820
+ );
769
821
  }
770
822
  }
771
823
  }
@@ -778,6 +830,7 @@ export function CopilotChatInput({
778
830
  ensureMeasurements,
779
831
  mode,
780
832
  resolvedValue,
833
+ updateContainerCache,
781
834
  updateLayout,
782
835
  ]);
783
836
 
@@ -799,12 +852,24 @@ export function CopilotChatInput({
799
852
  return;
800
853
  }
801
854
 
802
- const scheduleEvaluation = () => {
855
+ const containerTargets = new Set<Element>([
856
+ grid,
857
+ addContainer,
858
+ actionsContainer,
859
+ ]);
860
+
861
+ const scheduleEvaluation = (invalidateCache: boolean) => {
803
862
  if (ignoreResizeRef.current) {
804
863
  ignoreResizeRef.current = false;
864
+ // Self-inflicted resize from a layout toggle — container dimensions
865
+ // are unchanged, so keep the cache warm.
805
866
  return;
806
867
  }
807
868
 
869
+ if (invalidateCache) {
870
+ containerCacheRef.current = null;
871
+ }
872
+
808
873
  if (typeof window === "undefined") {
809
874
  evaluateLayout();
810
875
  return;
@@ -820,8 +885,19 @@ export function CopilotChatInput({
820
885
  });
821
886
  };
822
887
 
823
- const observer = new ResizeObserver(() => {
824
- scheduleEvaluation();
888
+ // Single observer for all elements inspect entry.target to decide
889
+ // whether to invalidate the container dimension cache. Container
890
+ // targets (grid, buttons) changing size means compactWidth may have
891
+ // changed; textarea height changes (typing) do not affect it.
892
+ const observer = new ResizeObserver((entries) => {
893
+ let shouldInvalidate = false;
894
+ for (const entry of entries) {
895
+ if (containerTargets.has(entry.target)) {
896
+ shouldInvalidate = true;
897
+ break;
898
+ }
899
+ }
900
+ scheduleEvaluation(shouldInvalidate);
825
901
  });
826
902
 
827
903
  observer.observe(grid);
@@ -1106,6 +1182,10 @@ export namespace CopilotChatInput {
1106
1182
  const config = useCopilotChatConfiguration();
1107
1183
  const labels = config?.labels ?? CopilotChatDefaultLabels;
1108
1184
 
1185
+ // Defer Radix UI rendering until after hydration to avoid ID mismatches
1186
+ const [mounted, setMounted] = useState(false);
1187
+ useEffect(() => setMounted(true), []);
1188
+
1109
1189
  const menuItems = useMemo<(ToolsMenuItem | "-")[]>(() => {
1110
1190
  const items: (ToolsMenuItem | "-")[] = [];
1111
1191
 
@@ -1170,27 +1250,32 @@ export namespace CopilotChatInput {
1170
1250
  const hasMenuItems = menuItems.length > 0;
1171
1251
  const isDisabled = disabled || !hasMenuItems;
1172
1252
 
1253
+ const button = (
1254
+ <Button
1255
+ type="button"
1256
+ data-testid="copilot-add-menu-button"
1257
+ variant="chatInputToolbarSecondary"
1258
+ size="chatInputToolbarIcon"
1259
+ className={twMerge("cpk:ml-1", className)}
1260
+ disabled={isDisabled}
1261
+ {...props}
1262
+ >
1263
+ <Plus className="cpk:size-[20px]" />
1264
+ </Button>
1265
+ );
1266
+
1267
+ // Render plain button during SSR; Radix wrappers only after hydration
1268
+ if (!mounted) return button;
1269
+
1173
1270
  return (
1174
1271
  <DropdownMenu>
1175
1272
  <Tooltip>
1176
1273
  <TooltipTrigger asChild>
1177
- <DropdownMenuTrigger asChild>
1178
- <Button
1179
- type="button"
1180
- data-testid="copilot-add-menu-button"
1181
- variant="chatInputToolbarSecondary"
1182
- size="chatInputToolbarIcon"
1183
- className={twMerge("cpk:ml-1", className)}
1184
- disabled={isDisabled}
1185
- {...props}
1186
- >
1187
- <Plus className="cpk:size-[20px]" />
1188
- </Button>
1189
- </DropdownMenuTrigger>
1274
+ <DropdownMenuTrigger asChild>{button}</DropdownMenuTrigger>
1190
1275
  </TooltipTrigger>
1191
1276
  <TooltipContent side="bottom">
1192
1277
  <p className="cpk:flex cpk:items-center cpk:gap-1 cpk:text-xs cpk:font-medium">
1193
- <span>Add files and more</span>
1278
+ <span>Add attachments</span>
1194
1279
  <code 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">
1195
1280
  /
1196
1281
  </code>
@@ -1222,25 +1307,9 @@ export namespace CopilotChatInput {
1222
1307
  () => internalTextareaRef.current as HTMLTextAreaElement,
1223
1308
  );
1224
1309
 
1225
- // Auto-scroll input into view on mobile when focused
1226
- useEffect(() => {
1227
- const textarea = internalTextareaRef.current;
1228
- if (!textarea) return;
1229
-
1230
- const handleFocus = () => {
1231
- // Small delay to let the keyboard start appearing
1232
- setTimeout(() => {
1233
- textarea.scrollIntoView({ behavior: "smooth", block: "nearest" });
1234
- }, 300);
1235
- };
1236
-
1237
- textarea.addEventListener("focus", handleFocus);
1238
- return () => textarea.removeEventListener("focus", handleFocus);
1239
- }, []);
1240
-
1241
1310
  useEffect(() => {
1242
1311
  if (autoFocus) {
1243
- internalTextareaRef.current?.focus();
1312
+ internalTextareaRef.current?.focus({ preventScroll: true });
1244
1313
  }
1245
1314
  }, [autoFocus]);
1246
1315