@copilotkit/react-ui 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.
@@ -108,7 +108,18 @@ import {
108
108
  UserMessageProps,
109
109
  } from "./props";
110
110
 
111
- import { ImageUploadQueue } from "./ImageUploadQueue";
111
+ import { AttachmentQueue } from "./AttachmentQueue";
112
+ import type { Attachment, AttachmentsConfig } from "./props";
113
+ import {
114
+ getModalityFromMimeType,
115
+ exceedsMaxSize,
116
+ readFileAsBase64,
117
+ generateVideoThumbnail,
118
+ matchesAcceptFilter,
119
+ formatFileSize,
120
+ deprecationWarning,
121
+ } from "./attachment-utils";
122
+ import type { InputContent } from "@copilotkit/shared";
112
123
  import { Suggestions as DefaultRenderSuggestionsList } from "./Suggestions";
113
124
 
114
125
  /**
@@ -201,16 +212,48 @@ export interface CopilotChatProps {
201
212
  labels?: CopilotChatLabels;
202
213
 
203
214
  /**
215
+ * @deprecated Use `attachments={{ enabled: true }}` instead.
216
+ * `imageUploadsEnabled` only supports images. The new `attachments` prop supports
217
+ * images, audio, video, and documents.
218
+ * See https://docs.copilotkit.ai/migration-guides/migrate-attachments
219
+ * @since 1.56.0
220
+ *
204
221
  * Enable image upload button (image inputs only supported on some models)
205
222
  */
206
223
  imageUploadsEnabled?: boolean;
207
224
 
208
225
  /**
226
+ * @deprecated Use `attachments={{ enabled: true, accept: "..." }}` instead.
227
+ * The `accept` field on the `attachments` prop replaces `inputFileAccept`.
228
+ * See https://docs.copilotkit.ai/migration-guides/migrate-attachments
229
+ * @since 1.56.0
230
+ *
209
231
  * The 'accept' attribute for the file input used for image uploads.
210
232
  * Defaults to "image/*".
211
233
  */
212
234
  inputFileAccept?: string;
213
235
 
236
+ /**
237
+ * Configuration for file attachments in the chat input.
238
+ * Enables users to attach images, audio, video, and documents.
239
+ *
240
+ * @example
241
+ * ```tsx
242
+ * <CopilotChat
243
+ * attachments={{
244
+ * enabled: true,
245
+ * accept: "image/*,application/pdf",
246
+ * maxSize: 10 * 1024 * 1024, // 10MB
247
+ * onUpload: async (file) => {
248
+ * const url = await uploadToS3(file);
249
+ * return { url, mimeType: file.type };
250
+ * },
251
+ * }}
252
+ * />
253
+ * ```
254
+ */
255
+ attachments?: AttachmentsConfig;
256
+
214
257
  /**
215
258
  * A function that takes in context string and instructions and returns
216
259
  * the system message to include in the chat request.
@@ -290,6 +333,9 @@ export interface CopilotChatProps {
290
333
  Input?: React.ComponentType<InputProps>;
291
334
 
292
335
  /**
336
+ * @deprecated Use the v2 `CopilotChat` attachment system instead.
337
+ * See https://docs.copilotkit.ai/migration-guides/migrate-attachments
338
+ *
293
339
  * A custom image rendering component to use instead of the default.
294
340
  */
295
341
  ImageRenderer?: React.ComponentType<ImageRendererProps>;
@@ -330,6 +376,13 @@ export interface CopilotChatProps {
330
376
  onError?: CopilotErrorHandler;
331
377
  }
332
378
 
379
+ /**
380
+ * @deprecated Use the `Attachment` type from `@copilotkit/react-ui` instead.
381
+ * `ImageUpload` only described image payloads. `Attachment` supports images,
382
+ * audio, video, and documents.
383
+ * See https://docs.copilotkit.ai/migration-guides/migrate-attachments
384
+ * @since 1.56.0
385
+ */
333
386
  export type ImageUpload = {
334
387
  contentType: string;
335
388
  bytes: string;
@@ -362,6 +415,7 @@ export function CopilotChat({
362
415
  ErrorMessage,
363
416
  imageUploadsEnabled,
364
417
  inputFileAccept = "image/*",
418
+ attachments,
365
419
  hideStopButton,
366
420
  observabilityHooks,
367
421
  renderError,
@@ -384,7 +438,33 @@ export function CopilotChat({
384
438
 
385
439
  // Destructure stable values to avoid object reference changes
386
440
  const { publicApiKey, chatApiEndpoint } = copilotApiConfig;
387
- const [selectedImages, setSelectedImages] = useState<Array<ImageUpload>>([]);
441
+
442
+ // Resolve attachments config with deprecation bridge
443
+ const resolvedAttachments: AttachmentsConfig | undefined = (() => {
444
+ if (attachments) return attachments;
445
+ if (imageUploadsEnabled) {
446
+ deprecationWarning(
447
+ "imageUploadsEnabled",
448
+ "imageUploadsEnabled is deprecated. Use attachments={{ enabled: true }} instead. " +
449
+ "See https://docs.copilotkit.ai/migration-guides/migrate-attachments",
450
+ );
451
+ return { enabled: true, accept: inputFileAccept || "image/*" };
452
+ }
453
+ return undefined;
454
+ })();
455
+
456
+ const attachmentsEnabled = resolvedAttachments?.enabled ?? false;
457
+ const attachmentsAccept = resolvedAttachments?.accept ?? "*/*";
458
+ const attachmentsMaxSize = resolvedAttachments?.maxSize ?? 20 * 1024 * 1024;
459
+
460
+ const [selectedAttachments, setSelectedAttachments] = useState<Attachment[]>(
461
+ [],
462
+ );
463
+ const [dragOver, setDragOver] = useState(false);
464
+ const processFilesRef = useRef<(files: File[]) => Promise<void>>(
465
+ async () => {},
466
+ );
467
+
388
468
  const [chatError, setChatError] = useState<ChatError | null>(null);
389
469
  const [messageFeedback, setMessageFeedback] = useState<
390
470
  Record<string, "thumbsUp" | "thumbsDown">
@@ -418,6 +498,12 @@ export function CopilotChat({
418
498
  const errorMessage =
419
499
  error?.message || error?.toString() || "An error occurred";
420
500
 
501
+ console.error(
502
+ `[CopilotKit] ${operation} error:`,
503
+ errorMessage,
504
+ originalError ?? error,
505
+ );
506
+
421
507
  // Set chat error state for rendering
422
508
  setChatError({
423
509
  message: errorMessage,
@@ -490,58 +576,42 @@ export function CopilotChat({
490
576
 
491
577
  // Clipboard paste handler
492
578
  useEffect(() => {
493
- if (!imageUploadsEnabled) return;
579
+ if (!attachmentsEnabled) return;
494
580
 
495
581
  const handlePaste = async (e: ClipboardEvent) => {
496
582
  const target = e.target as HTMLElement;
497
583
  if (!target.parentElement?.classList.contains("copilotKitInput")) return;
498
584
 
499
585
  const items = Array.from(e.clipboardData?.items || []);
500
- const imageItems = items.filter((item) => item.type.startsWith("image/"));
501
-
502
- if (imageItems.length === 0) return;
503
-
504
- e.preventDefault(); // Prevent default paste behavior for images
505
-
506
- const imagePromises: Promise<ImageUpload | null>[] = imageItems.map(
507
- (item) => {
508
- const file = item.getAsFile();
509
- if (!file) return Promise.resolve(null);
510
-
511
- return new Promise<ImageUpload | null>((resolve, reject) => {
512
- const reader = new FileReader();
513
- reader.onload = (e) => {
514
- const base64String = (e.target?.result as string)?.split(",")[1];
515
- if (base64String) {
516
- resolve({
517
- contentType: file.type,
518
- bytes: base64String,
519
- });
520
- } else {
521
- resolve(null);
522
- }
523
- };
524
- reader.onerror = reject;
525
- reader.readAsDataURL(file);
526
- });
527
- },
586
+ const fileItems = items.filter(
587
+ (item) =>
588
+ item.kind === "file" &&
589
+ item.getAsFile() !== null &&
590
+ matchesAcceptFilter(item.getAsFile()!, attachmentsAccept),
528
591
  );
529
592
 
593
+ if (fileItems.length === 0) return;
594
+ e.preventDefault();
595
+
596
+ const files = fileItems
597
+ .map((item) => item.getAsFile())
598
+ .filter((f): f is File => f !== null);
599
+
530
600
  try {
531
- const loadedImages = (await Promise.all(imagePromises)).filter(
532
- (img) => img !== null,
533
- );
534
- setSelectedImages((prev) => [...prev, ...loadedImages]);
601
+ await processFilesRef.current(files);
535
602
  } catch (error) {
536
- // Trigger chat-level error handler
537
- triggerChatError(error, "processClipboardImages", error);
538
- console.error("Error processing pasted images:", error);
603
+ triggerChatError(error, "pasteUpload", error);
539
604
  }
540
605
  };
541
606
 
542
607
  document.addEventListener("paste", handlePaste);
543
608
  return () => document.removeEventListener("paste", handlePaste);
544
- }, [imageUploadsEnabled, triggerChatError]);
609
+ }, [
610
+ attachmentsEnabled,
611
+ attachmentsAccept,
612
+ attachmentsMaxSize,
613
+ triggerChatError,
614
+ ]);
545
615
 
546
616
  useEffect(() => {
547
617
  if (!additionalInstructions?.length) {
@@ -598,10 +668,28 @@ export function CopilotChat({
598
668
  }
599
669
  }, [isLoading, triggerObservabilityHook]);
600
670
 
601
- // Wrapper for sendMessage to clear selected images
671
+ // Wrapper for sendMessage to clear selected attachments and build multimodal content
602
672
  const handleSendMessage = (text: string) => {
603
- const images = selectedImages;
604
- setSelectedImages([]);
673
+ const hasUploading = selectedAttachments.some(
674
+ (a) => a.status === "uploading",
675
+ );
676
+ if (hasUploading) {
677
+ triggerChatError(
678
+ new Error("Attachment(s) still uploading. Please wait."),
679
+ "sendMessage",
680
+ );
681
+ // Return a promise that resolves to a dummy message to satisfy the return type
682
+ return Promise.resolve({
683
+ id: randomUUID(),
684
+ content: text,
685
+ role: "user" as const,
686
+ } as Message);
687
+ }
688
+
689
+ const currentAttachments = selectedAttachments.filter(
690
+ (a) => a.status === "ready",
691
+ );
692
+ setSelectedAttachments([]);
605
693
  if (fileInputRef.current) {
606
694
  fileInputRef.current.value = "";
607
695
  }
@@ -609,7 +697,33 @@ export function CopilotChat({
609
697
  // Trigger message sent event
610
698
  triggerObservabilityHook("onMessageSent", text);
611
699
 
612
- // TODO: send images?
700
+ // Build content: if we have attachments, use InputContent[]
701
+ if (currentAttachments.length > 0) {
702
+ const contentParts: InputContent[] = [];
703
+
704
+ if (text.trim()) {
705
+ contentParts.push({ type: "text", text });
706
+ }
707
+
708
+ for (const attachment of currentAttachments) {
709
+ contentParts.push({
710
+ type: attachment.type,
711
+ source: attachment.source,
712
+ metadata: {
713
+ ...(attachment.filename ? { filename: attachment.filename } : {}),
714
+ ...attachment.metadata,
715
+ },
716
+ } as InputContent);
717
+ }
718
+
719
+ return sendMessage({
720
+ id: randomUUID(),
721
+ content: contentParts,
722
+ role: "user",
723
+ });
724
+ }
725
+
726
+ // Plain text message
613
727
  return sendMessage({
614
728
  id: randomUUID(),
615
729
  content: text,
@@ -640,50 +754,143 @@ export function CopilotChat({
640
754
  triggerObservabilityHook("onMessageCopied", message);
641
755
  };
642
756
 
643
- const handleImageUpload = async (
644
- event: React.ChangeEvent<HTMLInputElement>,
645
- ) => {
646
- if (!event.target.files || event.target.files.length === 0) {
647
- return;
757
+ const processFiles = async (files: File[]) => {
758
+ const validFiles = files.filter((file) =>
759
+ matchesAcceptFilter(file, attachmentsAccept),
760
+ );
761
+ const rejectedFiles = files.filter(
762
+ (file) => !matchesAcceptFilter(file, attachmentsAccept),
763
+ );
764
+ for (const file of rejectedFiles) {
765
+ const message = `File "${file.name}" is not accepted. Supported types: ${attachmentsAccept}`;
766
+ triggerChatError(new Error(message), "fileUpload");
767
+ resolvedAttachments?.onUploadFailed?.({
768
+ reason: "invalid-type",
769
+ file,
770
+ message,
771
+ });
648
772
  }
649
773
 
650
- const files = Array.from(event.target.files).filter((file) =>
651
- file.type.startsWith("image/"),
652
- );
653
- if (files.length === 0) return;
654
-
655
- const fileReadPromises = files.map((file) => {
656
- return new Promise<{ contentType: string; bytes: string }>(
657
- (resolve, reject) => {
658
- const reader = new FileReader();
659
- reader.onload = (e) => {
660
- const base64String =
661
- (e.target?.result as string)?.split(",")[1] || "";
662
- if (base64String) {
663
- resolve({
664
- contentType: file.type,
665
- bytes: base64String,
666
- });
667
- }
668
- };
669
- reader.onerror = reject;
670
- reader.readAsDataURL(file);
671
- },
672
- );
673
- });
774
+ for (const file of validFiles) {
775
+ if (exceedsMaxSize(file, attachmentsMaxSize)) {
776
+ const message = `File "${file.name}" exceeds the maximum size of ${formatFileSize(attachmentsMaxSize)}`;
777
+ triggerChatError(new Error(message), "fileUpload");
778
+ resolvedAttachments?.onUploadFailed?.({
779
+ reason: "file-too-large",
780
+ file,
781
+ message,
782
+ });
783
+ continue;
784
+ }
785
+
786
+ const modality = getModalityFromMimeType(file.type);
787
+
788
+ // Use a unique ID to track this placeholder across concurrent uploads
789
+ const placeholderId = randomUUID();
790
+ const placeholder: Attachment = {
791
+ id: placeholderId,
792
+ type: modality,
793
+ source: { type: "data", value: "", mimeType: file.type },
794
+ filename: file.name,
795
+ size: file.size,
796
+ status: "uploading",
797
+ };
798
+
799
+ setSelectedAttachments((prev) => [...prev, placeholder]);
800
+
801
+ try {
802
+ let source: Attachment["source"];
803
+ let uploadMetadata: Record<string, unknown> | undefined;
804
+
805
+ if (resolvedAttachments?.onUpload) {
806
+ const { metadata: meta, ...uploadSource } =
807
+ await resolvedAttachments.onUpload(file);
808
+ source = uploadSource;
809
+ uploadMetadata = meta;
810
+ } else {
811
+ const base64 = await readFileAsBase64(file);
812
+ source = { type: "data", value: base64, mimeType: file.type };
813
+ }
814
+
815
+ // Generate video thumbnail if applicable
816
+ let thumbnail: string | undefined;
817
+ if (modality === "video") {
818
+ thumbnail = await generateVideoThumbnail(file);
819
+ }
820
+
821
+ setSelectedAttachments((prev) =>
822
+ prev.map((att) =>
823
+ att.id === placeholderId
824
+ ? {
825
+ ...att,
826
+ source,
827
+ status: "ready" as const,
828
+ thumbnail,
829
+ metadata: uploadMetadata,
830
+ }
831
+ : att,
832
+ ),
833
+ );
834
+ } catch (error) {
835
+ // Remove the failed placeholder
836
+ setSelectedAttachments((prev) =>
837
+ prev.filter((att) => att.id !== placeholderId),
838
+ );
839
+ const message = error instanceof Error ? error.message : String(error);
840
+ triggerChatError(
841
+ new Error(`Failed to upload "${file.name}": ${message}`),
842
+ "fileUpload",
843
+ error,
844
+ );
845
+ resolvedAttachments?.onUploadFailed?.({
846
+ reason: "upload-failed",
847
+ file,
848
+ message: `Failed to upload "${file.name}": ${message}`,
849
+ });
850
+ }
851
+ }
852
+ };
853
+ processFilesRef.current = processFiles;
674
854
 
855
+ const handleFileUpload = async (
856
+ event: React.ChangeEvent<HTMLInputElement>,
857
+ ) => {
858
+ if (!event.target.files || event.target.files.length === 0) return;
675
859
  try {
676
- const loadedImages = await Promise.all(fileReadPromises);
677
- setSelectedImages((prev) => [...prev, ...loadedImages]);
860
+ await processFiles(Array.from(event.target.files));
678
861
  } catch (error) {
679
- // Trigger chat-level error handler
680
- triggerChatError(error, "processUploadedImages", error);
681
- console.error("Error reading files:", error);
862
+ triggerChatError(error, "fileUpload", error);
682
863
  }
683
864
  };
684
865
 
685
- const removeSelectedImage = (index: number) => {
686
- setSelectedImages((prev) => prev.filter((_, i) => i !== index));
866
+ // Drag-and-drop handlers
867
+ const handleDragOver = (e: React.DragEvent) => {
868
+ if (!attachmentsEnabled) return;
869
+ e.preventDefault();
870
+ e.stopPropagation();
871
+ setDragOver(true);
872
+ };
873
+
874
+ const handleDragLeave = (e: React.DragEvent) => {
875
+ e.preventDefault();
876
+ e.stopPropagation();
877
+ setDragOver(false);
878
+ };
879
+
880
+ const handleDrop = async (e: React.DragEvent) => {
881
+ e.preventDefault();
882
+ e.stopPropagation();
883
+ setDragOver(false);
884
+ if (!attachmentsEnabled) return;
885
+
886
+ const files = Array.from(e.dataTransfer.files);
887
+ if (files.length > 0) {
888
+ try {
889
+ await processFiles(files);
890
+ } catch (error) {
891
+ triggerChatError(error, "dropUpload", error);
892
+ }
893
+ }
687
894
  };
688
895
 
689
896
  const handleThumbsUp = (message: Message) => {
@@ -718,78 +925,89 @@ export function CopilotChat({
718
925
 
719
926
  return (
720
927
  <WrappedCopilotChat icons={icons} labels={labels} className={className}>
721
- {/* Render error above messages if present */}
722
- {chatError &&
723
- renderError &&
724
- renderError({
725
- ...chatError,
726
- onDismiss: () => setChatError(null),
727
- onRetry: () => {
728
- // Clear error and potentially retry based on operation
729
- setChatError(null);
730
- // TODO: Implement specific retry logic based on operation type
731
- },
732
- })}
733
-
734
- <Messages
735
- AssistantMessage={AssistantMessage}
736
- UserMessage={UserMessage}
737
- RenderMessage={RenderMessage}
738
- messages={messages}
739
- inProgress={isLoading}
740
- onRegenerate={handleRegenerate}
741
- onCopy={handleCopy}
742
- onThumbsUp={handleThumbsUp}
743
- onThumbsDown={handleThumbsDown}
744
- messageFeedback={messageFeedback}
745
- markdownTagRenderers={markdownTagRenderers}
746
- ImageRenderer={ImageRenderer}
747
- ErrorMessage={ErrorMessage}
748
- chatError={chatError}
749
- // Legacy props - passed through to Messages component
750
- RenderTextMessage={RenderTextMessage}
751
- RenderActionExecutionMessage={RenderActionExecutionMessage}
752
- RenderAgentStateMessage={RenderAgentStateMessage}
753
- RenderResultMessage={RenderResultMessage}
754
- RenderImageMessage={RenderImageMessage}
928
+ <div
929
+ onDragOver={handleDragOver}
930
+ onDragLeave={handleDragLeave}
931
+ onDrop={handleDrop}
932
+ className={dragOver ? "copilotKitDragOver" : ""}
755
933
  >
756
- {currentSuggestions.length > 0 && (
757
- <RenderSuggestionsList
758
- onSuggestionClick={handleSendMessage}
759
- suggestions={currentSuggestions}
760
- isLoading={isLoadingSuggestions}
761
- />
934
+ {/* Render error above messages if present */}
935
+ {chatError &&
936
+ renderError &&
937
+ renderError({
938
+ ...chatError,
939
+ onDismiss: () => setChatError(null),
940
+ onRetry: () => {
941
+ // Clear error and potentially retry based on operation
942
+ setChatError(null);
943
+ // TODO: Implement specific retry logic based on operation type
944
+ },
945
+ })}
946
+
947
+ <Messages
948
+ AssistantMessage={AssistantMessage}
949
+ UserMessage={UserMessage}
950
+ RenderMessage={RenderMessage}
951
+ messages={messages}
952
+ inProgress={isLoading}
953
+ onRegenerate={handleRegenerate}
954
+ onCopy={handleCopy}
955
+ onThumbsUp={handleThumbsUp}
956
+ onThumbsDown={handleThumbsDown}
957
+ messageFeedback={messageFeedback}
958
+ markdownTagRenderers={markdownTagRenderers}
959
+ ImageRenderer={ImageRenderer}
960
+ ErrorMessage={ErrorMessage}
961
+ chatError={chatError}
962
+ // Legacy props - passed through to Messages component
963
+ RenderTextMessage={RenderTextMessage}
964
+ RenderActionExecutionMessage={RenderActionExecutionMessage}
965
+ RenderAgentStateMessage={RenderAgentStateMessage}
966
+ RenderResultMessage={RenderResultMessage}
967
+ RenderImageMessage={RenderImageMessage}
968
+ >
969
+ {currentSuggestions.length > 0 && (
970
+ <RenderSuggestionsList
971
+ onSuggestionClick={handleSendMessage}
972
+ suggestions={currentSuggestions}
973
+ isLoading={isLoadingSuggestions}
974
+ />
975
+ )}
976
+ </Messages>
977
+
978
+ {attachmentsEnabled && (
979
+ <>
980
+ <AttachmentQueue
981
+ attachments={selectedAttachments}
982
+ onRemoveAttachment={(id) =>
983
+ setSelectedAttachments((prev) =>
984
+ prev.filter((att) => att.id !== id),
985
+ )
986
+ }
987
+ />
988
+ <input
989
+ type="file"
990
+ multiple
991
+ ref={fileInputRef}
992
+ onChange={handleFileUpload}
993
+ accept={attachmentsAccept}
994
+ style={{ display: "none" }}
995
+ />
996
+ </>
762
997
  )}
763
- </Messages>
764
-
765
- {imageUploadsEnabled && (
766
- <>
767
- <ImageUploadQueue
768
- images={selectedImages}
769
- onRemoveImage={removeSelectedImage}
770
- />
771
- <input
772
- type="file"
773
- multiple
774
- ref={fileInputRef}
775
- onChange={handleImageUpload}
776
- accept={inputFileAccept}
777
- style={{ display: "none" }}
778
- />
779
- </>
780
- )}
781
- <Input
782
- inProgress={isLoading}
783
- chatReady={Boolean(agent)}
784
- // @ts-ignore
785
- onSend={handleSendMessage}
786
- isVisible={isVisible}
787
- onStop={stopGeneration}
788
- onUpload={
789
- imageUploadsEnabled ? () => fileInputRef.current?.click() : undefined
790
- }
791
- hideStopButton={hideStopButton}
792
- />
998
+ <Input
999
+ inProgress={isLoading}
1000
+ chatReady={Boolean(agent)}
1001
+ // @ts-ignore
1002
+ onSend={handleSendMessage}
1003
+ isVisible={isVisible}
1004
+ onStop={stopGeneration}
1005
+ onUpload={
1006
+ attachmentsEnabled ? () => fileInputRef.current?.click() : undefined
1007
+ }
1008
+ hideStopButton={hideStopButton}
1009
+ />
1010
+ </div>
793
1011
  </WrappedCopilotChat>
794
1012
  );
795
1013
  }
@@ -140,7 +140,7 @@ const CopilotModalInner = ({
140
140
 
141
141
  return (
142
142
  <>
143
- {memoizedChildren}
143
+ <div className="copilotKitModalChildrenWrapper">{memoizedChildren}</div>
144
144
  <div className={className}>
145
145
  <Button></Button>
146
146
  <Window
@@ -0,0 +1,32 @@
1
+ // Re-export utilities from shared
2
+ export {
3
+ getModalityFromMimeType,
4
+ formatFileSize,
5
+ exceedsMaxSize,
6
+ readFileAsBase64,
7
+ generateVideoThumbnail,
8
+ matchesAcceptFilter,
9
+ } from "@copilotkit/shared";
10
+
11
+ // Deprecation warning helpers — react-ui specific
12
+ const suppressedWarnings = new Set<string>();
13
+ let globalSuppress = false;
14
+
15
+ /**
16
+ * Issue a deprecation warning once per key per session.
17
+ * Suppressed entirely if the user calls suppressDeprecationWarnings().
18
+ */
19
+ export function deprecationWarning(key: string, message: string) {
20
+ if (globalSuppress || suppressedWarnings.has(key)) return;
21
+ if (typeof process !== "undefined" && process.env?.NODE_ENV === "production")
22
+ return;
23
+ suppressedWarnings.add(key);
24
+ console.warn(`[CopilotKit] Deprecation: ${message}`);
25
+ }
26
+
27
+ /**
28
+ * Suppress all CopilotKit deprecation warnings.
29
+ */
30
+ export function suppressDeprecationWarnings() {
31
+ globalSuppress = true;
32
+ }
@@ -9,3 +9,4 @@ export { ImageRenderer } from "./messages/ImageRenderer";
9
9
  export { useChatContext } from "./ChatContext";
10
10
  export { Suggestions as RenderSuggestionsList } from "./Suggestions";
11
11
  export { Suggestion as RenderSuggestion } from "./Suggestion";
12
+ export { suppressDeprecationWarnings } from "./attachment-utils";