@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
@@ -1,10 +1,10 @@
1
1
  import * as React$1 from "react";
2
- import React, { createContext, forwardRef, useCallback, useContext, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
2
+ import React, { createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from "react";
3
3
  import { CopilotKitCore, CopilotKitCoreRuntimeConnectionStatus, ProxiedCopilotRuntimeAgent, ToolCallStatus, ɵcreateThreadStore, ɵselectHasNextPage, ɵselectIsFetchingNextPage, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
4
4
  import { HttpAgent } from "@ag-ui/client";
5
5
  import { extendTailwindMerge, twMerge } from "tailwind-merge";
6
- import { ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronRightIcon, Copy, Edit, Loader2, MessageCircle, Mic, Plus, RefreshCw, Square, ThumbsDown, ThumbsUp, Volume2, X } from "lucide-react";
7
- import { COPILOT_CLOUD_API_URL, COPILOT_CLOUD_CHAT_URL, COPILOT_CLOUD_PUBLIC_API_KEY_HEADER, ConfigurationError, CopilotKitAgentDiscoveryError, CopilotKitApiDiscoveryError, CopilotKitError, CopilotKitErrorCode, CopilotKitLowLevelError, CopilotKitRemoteEndpointDiscoveryError, DEFAULT_AGENT_ID, ErrorVisibility, MissingPublicApiKeyError, Severity, TranscriptionErrorCode, TranscriptionErrorCode as TranscriptionErrorCode$1, createLicenseContextValue, dataToUUID, parseJson, partialJSONParse, randomId, randomUUID } from "@copilotkit/shared";
6
+ import { ArrowUp, Check, ChevronDown, ChevronLeft, ChevronRight, ChevronRightIcon, Copy, Edit, Loader2, MessageCircle, Mic, Play, Plus, RefreshCw, Square, ThumbsDown, ThumbsUp, Upload, Volume2, X } from "lucide-react";
7
+ import { A2UI_DEFAULT_DESIGN_GUIDELINES, A2UI_DEFAULT_GENERATION_GUIDELINES, COPILOT_CLOUD_API_URL, COPILOT_CLOUD_CHAT_URL, COPILOT_CLOUD_PUBLIC_API_KEY_HEADER, ConfigurationError, CopilotKitAgentDiscoveryError, CopilotKitApiDiscoveryError, CopilotKitError, CopilotKitErrorCode, CopilotKitLowLevelError, CopilotKitRemoteEndpointDiscoveryError, DEFAULT_AGENT_ID, ErrorVisibility, MissingPublicApiKeyError, Severity, TranscriptionErrorCode, TranscriptionErrorCode as TranscriptionErrorCode$1, createLicenseContextValue, dataToUUID, exceedsMaxSize, formatFileSize, generateVideoThumbnail, getDocumentIcon, getModalityFromMimeType, getSourceUrl, matchesAcceptFilter, parseJson, partialJSONParse, randomId, randomUUID, readFileAsBase64, schemaToJsonSchema } from "@copilotkit/shared";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  import { Slot } from "@radix-ui/react-slot";
10
10
  import { cva } from "class-variance-authority";
@@ -14,19 +14,120 @@ import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
14
14
  import { Streamdown } from "streamdown";
15
15
  import { z } from "zod";
16
16
  import { createComponent } from "@lit-labs/react";
17
- import { A2UIProvider, A2UIRenderer, DEFAULT_SURFACE_ID, initializeDefaultCatalog, injectStyles, useA2UIActions, viewerTheme } from "@copilotkit/a2ui-renderer";
17
+ import { A2UIProvider, A2UIRenderer, A2UI_SCHEMA_CONTEXT_DESCRIPTION, DEFAULT_SURFACE_ID, buildCatalogContextValue, extractCatalogComponentSchemas, initializeDefaultCatalog, injectStyles, useA2UIActions, useA2UIError, viewerTheme } from "@copilotkit/a2ui-renderer";
18
+ import { zodToJsonSchema } from "zod-to-json-schema";
19
+ import { useVirtualizer } from "@tanstack/react-virtual";
20
+ import { createPortal, flushSync } from "react-dom";
18
21
  import { StickToBottom, useStickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
19
- import { merge } from "ts-deepmerge";
20
- import { flushSync } from "react-dom";
21
22
  import ReactMarkdown from "react-markdown";
22
23
 
24
+ //#region src/v2/lib/slots.tsx
25
+ /**
26
+ * Shallow equality comparison for objects.
27
+ */
28
+ function shallowEqual(obj1, obj2) {
29
+ const keys1 = Object.keys(obj1);
30
+ const keys2 = Object.keys(obj2);
31
+ if (keys1.length !== keys2.length) return false;
32
+ for (const key of keys1) if (obj1[key] !== obj2[key]) return false;
33
+ return true;
34
+ }
35
+ /**
36
+ * Returns true only for plain JS objects (`{}`), excluding arrays, Dates,
37
+ * class instances, and other exotic objects that happen to have typeof "object".
38
+ */
39
+ function isPlainObject(obj) {
40
+ return obj !== null && typeof obj === "object" && Object.prototype.toString.call(obj) === "[object Object]";
41
+ }
42
+ /**
43
+ * Returns the same reference as long as the value is shallowly equal to the
44
+ * previous render's value.
45
+ *
46
+ * - Identical references bail out immediately (O(1)).
47
+ * - Plain objects ({}) are shallow-compared key-by-key.
48
+ * - Arrays, Dates, class instances, functions, and primitives are compared by
49
+ * reference only — shallowEqual is never called on non-plain objects, which
50
+ * avoids incorrect equality for e.g. [1,2] vs [1,2] (different arrays).
51
+ *
52
+ * Typical use: stabilize inline slot props so MemoizedSlotWrapper's shallow
53
+ * equality check isn't defeated by a new object reference on every render.
54
+ */
55
+ function useShallowStableRef(value) {
56
+ const ref = useRef(value);
57
+ if (ref.current === value) return ref.current;
58
+ if (isPlainObject(ref.current) && isPlainObject(value)) {
59
+ if (shallowEqual(ref.current, value)) return ref.current;
60
+ }
61
+ ref.current = value;
62
+ return ref.current;
63
+ }
64
+ /**
65
+ * Check if a value is a React component type (function, class, forwardRef, memo, etc.)
66
+ */
67
+ function isReactComponentType(value) {
68
+ if (typeof value === "function") return true;
69
+ if (value && typeof value === "object" && "$$typeof" in value && !React.isValidElement(value)) return true;
70
+ return false;
71
+ }
72
+ /**
73
+ * Internal function to render a slot value as a React element (non-memoized).
74
+ */
75
+ function renderSlotElement(slot, DefaultComponent, props) {
76
+ if (typeof slot === "string") {
77
+ const existingClassName = props.className;
78
+ return React.createElement(DefaultComponent, {
79
+ ...props,
80
+ className: twMerge(existingClassName, slot)
81
+ });
82
+ }
83
+ if (isReactComponentType(slot)) return React.createElement(slot, props);
84
+ if (slot && typeof slot === "object" && !React.isValidElement(slot)) return React.createElement(DefaultComponent, {
85
+ ...props,
86
+ ...slot
87
+ });
88
+ return React.createElement(DefaultComponent, props);
89
+ }
90
+ /**
91
+ * Internal memoized wrapper component for renderSlot.
92
+ * Uses forwardRef to support ref forwarding.
93
+ */
94
+ const MemoizedSlotWrapper = React.memo(React.forwardRef(function MemoizedSlotWrapper(props, ref) {
95
+ const { $slot, $component, ...rest } = props;
96
+ return renderSlotElement($slot, $component, ref !== null ? {
97
+ ...rest,
98
+ ref
99
+ } : rest);
100
+ }), (prev, next) => {
101
+ if (prev.$slot !== next.$slot) return false;
102
+ if (prev.$component !== next.$component) return false;
103
+ const { $slot: _ps, $component: _pc, ...prevRest } = prev;
104
+ const { $slot: _ns, $component: _nc, ...nextRest } = next;
105
+ return shallowEqual(prevRest, nextRest);
106
+ });
107
+ /**
108
+ * Renders a slot value as a memoized React element.
109
+ * Automatically prevents unnecessary re-renders using shallow prop comparison.
110
+ * Supports ref forwarding.
111
+ *
112
+ * @example
113
+ * renderSlot(customInput, CopilotChatInput, { onSubmit: handleSubmit })
114
+ */
115
+ function renderSlot(slot, DefaultComponent, props) {
116
+ return React.createElement(MemoizedSlotWrapper, {
117
+ ...props,
118
+ $slot: slot,
119
+ $component: DefaultComponent
120
+ });
121
+ }
122
+
123
+ //#endregion
23
124
  //#region src/v2/providers/CopilotChatConfigurationProvider.tsx
24
125
  const CopilotChatDefaultLabels = {
25
126
  chatInputPlaceholder: "Type a message...",
26
127
  chatInputToolbarStartTranscribeButtonLabel: "Transcribe",
27
128
  chatInputToolbarCancelTranscribeButtonLabel: "Cancel",
28
129
  chatInputToolbarFinishTranscribeButtonLabel: "Finish",
29
- chatInputToolbarAddButtonLabel: "Add photos or files",
130
+ chatInputToolbarAddButtonLabel: "Add attachments",
30
131
  chatInputToolbarToolsButtonLabel: "Tools",
31
132
  assistantMessageToolbarCopyCodeLabel: "Copy",
32
133
  assistantMessageToolbarCopyCodeCopiedLabel: "Copied",
@@ -46,11 +147,12 @@ const CopilotChatDefaultLabels = {
46
147
  const CopilotChatConfiguration = createContext(null);
47
148
  const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, isModalDefaultOpen }) => {
48
149
  const parentConfig = useContext(CopilotChatConfiguration);
150
+ const stableLabels = useShallowStableRef(labels);
49
151
  const mergedLabels = useMemo(() => ({
50
152
  ...CopilotChatDefaultLabels,
51
153
  ...parentConfig?.labels ?? {},
52
- ...labels ?? {}
53
- }), [labels, parentConfig?.labels]);
154
+ ...stableLabels ?? {}
155
+ }), [stableLabels, parentConfig?.labels]);
54
156
  const resolvedAgentId = agentId ?? parentConfig?.agentId ?? DEFAULT_AGENT_ID;
55
157
  const resolvedThreadId = useMemo(() => {
56
158
  if (threadId) return threadId;
@@ -473,82 +575,11 @@ const CopilotChatAudioRecorder = forwardRef((props, ref) => {
473
575
  });
474
576
  CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
475
577
 
476
- //#endregion
477
- //#region src/v2/lib/slots.tsx
478
- /**
479
- * Shallow equality comparison for objects.
480
- */
481
- function shallowEqual(obj1, obj2) {
482
- const keys1 = Object.keys(obj1);
483
- const keys2 = Object.keys(obj2);
484
- if (keys1.length !== keys2.length) return false;
485
- for (const key of keys1) if (obj1[key] !== obj2[key]) return false;
486
- return true;
487
- }
488
- /**
489
- * Check if a value is a React component type (function, class, forwardRef, memo, etc.)
490
- */
491
- function isReactComponentType(value) {
492
- if (typeof value === "function") return true;
493
- if (value && typeof value === "object" && "$$typeof" in value && !React.isValidElement(value)) return true;
494
- return false;
495
- }
496
- /**
497
- * Internal function to render a slot value as a React element (non-memoized).
498
- */
499
- function renderSlotElement(slot, DefaultComponent, props) {
500
- if (typeof slot === "string") {
501
- const existingClassName = props.className;
502
- return React.createElement(DefaultComponent, {
503
- ...props,
504
- className: twMerge(existingClassName, slot)
505
- });
506
- }
507
- if (isReactComponentType(slot)) return React.createElement(slot, props);
508
- if (slot && typeof slot === "object" && !React.isValidElement(slot)) return React.createElement(DefaultComponent, {
509
- ...props,
510
- ...slot
511
- });
512
- return React.createElement(DefaultComponent, props);
513
- }
514
- /**
515
- * Internal memoized wrapper component for renderSlot.
516
- * Uses forwardRef to support ref forwarding.
517
- */
518
- const MemoizedSlotWrapper = React.memo(React.forwardRef(function MemoizedSlotWrapper(props, ref) {
519
- const { $slot, $component, ...rest } = props;
520
- return renderSlotElement($slot, $component, ref !== null ? {
521
- ...rest,
522
- ref
523
- } : rest);
524
- }), (prev, next) => {
525
- if (prev.$slot !== next.$slot) return false;
526
- if (prev.$component !== next.$component) return false;
527
- const { $slot: _ps, $component: _pc, ...prevRest } = prev;
528
- const { $slot: _ns, $component: _nc, ...nextRest } = next;
529
- return shallowEqual(prevRest, nextRest);
530
- });
531
- /**
532
- * Renders a slot value as a memoized React element.
533
- * Automatically prevents unnecessary re-renders using shallow prop comparison.
534
- * Supports ref forwarding.
535
- *
536
- * @example
537
- * renderSlot(customInput, CopilotChatInput, { onSubmit: handleSubmit })
538
- */
539
- function renderSlot(slot, DefaultComponent, props) {
540
- return React.createElement(MemoizedSlotWrapper, {
541
- ...props,
542
- $slot: slot,
543
- $component: DefaultComponent
544
- });
545
- }
546
-
547
578
  //#endregion
548
579
  //#region src/v2/components/chat/CopilotChatInput.tsx
549
580
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
550
581
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
551
- 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 }) {
582
+ 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 }) {
552
583
  const isControlled = value !== void 0;
553
584
  const [internalValue, setInternalValue] = useState(() => value ?? "");
554
585
  useEffect(() => {
@@ -577,6 +608,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
577
608
  paddingLeft: 0,
578
609
  paddingRight: 0
579
610
  });
611
+ const containerCacheRef = useRef(null);
580
612
  const commandItems = useMemo(() => {
581
613
  const entries = [];
582
614
  const seen = /* @__PURE__ */ new Set();
@@ -621,7 +653,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
621
653
  previousModalStateRef.current = config?.isModalOpen;
622
654
  return;
623
655
  }
624
- if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus();
656
+ if (config?.isModalOpen && !previousModalStateRef.current) inputRef.current?.focus({ preventScroll: true });
625
657
  previousModalStateRef.current = config?.isModalOpen;
626
658
  }, [config?.isModalOpen, autoFocus]);
627
659
  useEffect(() => {
@@ -864,6 +896,25 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
864
896
  return nextLayout;
865
897
  });
866
898
  }, []);
899
+ const updateContainerCache = useCallback(() => {
900
+ const grid = gridRef.current;
901
+ const addContainer = addButtonContainerRef.current;
902
+ const actionsContainer = actionsContainerRef.current;
903
+ if (!grid || !addContainer || !actionsContainer) return null;
904
+ const gridStyles = window.getComputedStyle(grid);
905
+ const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0;
906
+ const paddingRight = parseFloat(gridStyles.paddingRight) || 0;
907
+ const columnGap = parseFloat(gridStyles.columnGap) || 0;
908
+ const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight;
909
+ if (gridAvailableWidth <= 0) return null;
910
+ const addWidth = addContainer.getBoundingClientRect().width;
911
+ const actionsWidth = actionsContainer.getBoundingClientRect().width;
912
+ const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0);
913
+ if (compactWidth <= 0) return null;
914
+ const result = { compactWidth };
915
+ containerCacheRef.current = result;
916
+ return result;
917
+ }, []);
867
918
  const evaluateLayout = useCallback(() => {
868
919
  if (mode !== "input") {
869
920
  updateLayout("compact");
@@ -889,31 +940,31 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
889
940
  const renderedMultiline = baseline > 0 ? scrollHeight > baseline + 1 : false;
890
941
  let shouldExpand = hasExplicitBreak || renderedMultiline;
891
942
  if (!shouldExpand) {
892
- const gridStyles = window.getComputedStyle(grid);
893
- const paddingLeft = parseFloat(gridStyles.paddingLeft) || 0;
894
- const paddingRight = parseFloat(gridStyles.paddingRight) || 0;
895
- const columnGap = parseFloat(gridStyles.columnGap) || 0;
896
- const gridAvailableWidth = grid.clientWidth - paddingLeft - paddingRight;
897
- if (gridAvailableWidth > 0) {
898
- const addWidth = addContainer.getBoundingClientRect().width;
899
- const actionsWidth = actionsContainer.getBoundingClientRect().width;
900
- const compactWidth = Math.max(gridAvailableWidth - addWidth - actionsWidth - columnGap * 2, 0);
901
- const canvas = measurementCanvasRef.current ?? document.createElement("canvas");
902
- if (!measurementCanvasRef.current) measurementCanvasRef.current = canvas;
903
- const context = canvas.getContext("2d");
904
- if (context) {
943
+ const cache = containerCacheRef.current ?? updateContainerCache();
944
+ if (cache && cache.compactWidth > 0) {
945
+ const compactInnerWidth = Math.max(cache.compactWidth - (measurementsRef.current.paddingLeft || 0) - (measurementsRef.current.paddingRight || 0), 0);
946
+ if (compactInnerWidth > 0) {
905
947
  const textareaStyles = window.getComputedStyle(textarea);
906
- context.font = textareaStyles.font || `${textareaStyles.fontStyle} ${textareaStyles.fontVariant} ${textareaStyles.fontWeight} ${textareaStyles.fontSize}/${textareaStyles.lineHeight} ${textareaStyles.fontFamily}`;
907
- const compactInnerWidth = Math.max(compactWidth - (measurementsRef.current.paddingLeft || 0) - (measurementsRef.current.paddingRight || 0), 0);
908
- if (compactInnerWidth > 0) {
909
- const lines = resolvedValue.length > 0 ? resolvedValue.split("\n") : [""];
910
- let longestWidth = 0;
911
- for (const line of lines) {
912
- const metrics = context.measureText(line || " ");
913
- if (metrics.width > longestWidth) longestWidth = metrics.width;
914
- }
915
- if (longestWidth > compactInnerWidth) shouldExpand = true;
948
+ let font = textareaStyles.font;
949
+ if (!font) {
950
+ const { fontStyle, fontVariant, fontWeight, fontSize, lineHeight, fontFamily } = textareaStyles;
951
+ if (fontSize && fontFamily) font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`;
916
952
  }
953
+ if (font?.trim()) {
954
+ const canvas = measurementCanvasRef.current ?? document.createElement("canvas");
955
+ if (!measurementCanvasRef.current) measurementCanvasRef.current = canvas;
956
+ const context = canvas.getContext("2d");
957
+ if (context) {
958
+ context.font = font;
959
+ const lines = resolvedValue.length > 0 ? resolvedValue.split("\n") : [""];
960
+ let longestWidth = 0;
961
+ for (const line of lines) {
962
+ const metrics = context.measureText(line || " ");
963
+ if (metrics.width > longestWidth) longestWidth = metrics.width;
964
+ }
965
+ if (longestWidth > compactInnerWidth) shouldExpand = true;
966
+ } else if (process.env.NODE_ENV !== "production") console.warn("[CopilotChatInput] canvas.getContext('2d') returned null. Text-width-based expansion will be unavailable.");
967
+ } 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.");
917
968
  }
918
969
  }
919
970
  }
@@ -923,6 +974,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
923
974
  ensureMeasurements,
924
975
  mode,
925
976
  resolvedValue,
977
+ updateContainerCache,
926
978
  updateLayout
927
979
  ]);
928
980
  useLayoutEffect(() => {
@@ -935,11 +987,17 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
935
987
  const addContainer = addButtonContainerRef.current;
936
988
  const actionsContainer = actionsContainerRef.current;
937
989
  if (!textarea || !grid || !addContainer || !actionsContainer) return;
938
- const scheduleEvaluation = () => {
990
+ const containerTargets = new Set([
991
+ grid,
992
+ addContainer,
993
+ actionsContainer
994
+ ]);
995
+ const scheduleEvaluation = (invalidateCache) => {
939
996
  if (ignoreResizeRef.current) {
940
997
  ignoreResizeRef.current = false;
941
998
  return;
942
999
  }
1000
+ if (invalidateCache) containerCacheRef.current = null;
943
1001
  if (typeof window === "undefined") {
944
1002
  evaluateLayout();
945
1003
  return;
@@ -950,8 +1008,13 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
950
1008
  evaluateLayout();
951
1009
  });
952
1010
  };
953
- const observer = new ResizeObserver(() => {
954
- scheduleEvaluation();
1011
+ const observer = new ResizeObserver((entries) => {
1012
+ let shouldInvalidate = false;
1013
+ for (const entry of entries) if (containerTargets.has(entry.target)) {
1014
+ shouldInvalidate = true;
1015
+ break;
1016
+ }
1017
+ scheduleEvaluation(shouldInvalidate);
955
1018
  });
956
1019
  observer.observe(grid);
957
1020
  observer.observe(addContainer);
@@ -1096,6 +1159,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1096
1159
  });
1097
1160
  _CopilotChatInput.AddMenuButton = ({ className, toolsMenu, onAddFile, disabled, ...props }) => {
1098
1161
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1162
+ const [mounted, setMounted] = useState(false);
1163
+ useEffect(() => setMounted(true), []);
1099
1164
  const menuItems = useMemo(() => {
1100
1165
  const items = [];
1101
1166
  if (onAddFile) items.push({
@@ -1126,26 +1191,28 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1126
1191
  }), []);
1127
1192
  const hasMenuItems = menuItems.length > 0;
1128
1193
  const isDisabled = disabled || !hasMenuItems;
1194
+ const button = /* @__PURE__ */ jsx(Button, {
1195
+ type: "button",
1196
+ "data-testid": "copilot-add-menu-button",
1197
+ variant: "chatInputToolbarSecondary",
1198
+ size: "chatInputToolbarIcon",
1199
+ className: twMerge("cpk:ml-1", className),
1200
+ disabled: isDisabled,
1201
+ ...props,
1202
+ children: /* @__PURE__ */ jsx(Plus, { className: "cpk:size-[20px]" })
1203
+ });
1204
+ if (!mounted) return button;
1129
1205
  return /* @__PURE__ */ jsxs(DropdownMenu, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
1130
1206
  asChild: true,
1131
1207
  children: /* @__PURE__ */ jsx(DropdownMenuTrigger, {
1132
1208
  asChild: true,
1133
- children: /* @__PURE__ */ jsx(Button, {
1134
- type: "button",
1135
- "data-testid": "copilot-add-menu-button",
1136
- variant: "chatInputToolbarSecondary",
1137
- size: "chatInputToolbarIcon",
1138
- className: twMerge("cpk:ml-1", className),
1139
- disabled: isDisabled,
1140
- ...props,
1141
- children: /* @__PURE__ */ jsx(Plus, { className: "cpk:size-[20px]" })
1142
- })
1209
+ children: button
1143
1210
  })
1144
1211
  }), /* @__PURE__ */ jsx(TooltipContent, {
1145
1212
  side: "bottom",
1146
1213
  children: /* @__PURE__ */ jsxs("p", {
1147
1214
  className: "cpk:flex cpk:items-center cpk:gap-1 cpk:text-xs cpk:font-medium",
1148
- children: [/* @__PURE__ */ jsx("span", { children: "Add files and more" }), /* @__PURE__ */ jsx("code", {
1215
+ children: [/* @__PURE__ */ jsx("span", { children: "Add attachments" }), /* @__PURE__ */ jsx("code", {
1149
1216
  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",
1150
1217
  children: "/"
1151
1218
  })]
@@ -1161,21 +1228,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1161
1228
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
1162
1229
  useImperativeHandle(ref, () => internalTextareaRef.current);
1163
1230
  useEffect(() => {
1164
- const textarea = internalTextareaRef.current;
1165
- if (!textarea) return;
1166
- const handleFocus = () => {
1167
- setTimeout(() => {
1168
- textarea.scrollIntoView({
1169
- behavior: "smooth",
1170
- block: "nearest"
1171
- });
1172
- }, 300);
1173
- };
1174
- textarea.addEventListener("focus", handleFocus);
1175
- return () => textarea.removeEventListener("focus", handleFocus);
1176
- }, []);
1177
- useEffect(() => {
1178
- if (autoFocus) internalTextareaRef.current?.focus();
1231
+ if (autoFocus) internalTextareaRef.current?.focus({ preventScroll: true });
1179
1232
  }, [autoFocus]);
1180
1233
  return /* @__PURE__ */ jsx("textarea", {
1181
1234
  ref: internalTextareaRef,
@@ -1883,125 +1936,981 @@ const MCPAppsActivityRenderer = function MCPAppsActivityRenderer({ content, agen
1883
1936
  };
1884
1937
 
1885
1938
  //#endregion
1886
- //#region src/v2/a2ui/A2UIMessageRenderer.tsx
1887
- let initialized = false;
1888
- function ensureInitialized() {
1889
- if (!initialized) {
1890
- initializeDefaultCatalog();
1891
- injectStyles();
1892
- initialized = true;
1893
- }
1894
- }
1895
- function createA2UIMessageRenderer(options) {
1896
- const { theme } = options;
1897
- return {
1898
- activityType: "a2ui-surface",
1899
- content: z.any(),
1900
- render: ({ content, agent }) => {
1901
- ensureInitialized();
1902
- const [operations, setOperations] = useState([]);
1903
- const lastSignatureRef = useRef(null);
1904
- const { copilotkit } = useCopilotKit();
1905
- useEffect(() => {
1906
- if (!content || !Array.isArray(content.operations)) {
1907
- lastSignatureRef.current = null;
1908
- setOperations([]);
1909
- return;
1910
- }
1911
- const incoming = content.operations;
1912
- const signature = stringifyOperations(incoming);
1913
- if (signature && signature === lastSignatureRef.current) return;
1914
- lastSignatureRef.current = signature;
1915
- setOperations(incoming);
1916
- }, [content]);
1917
- const groupedOperations = useMemo(() => {
1918
- const groups = /* @__PURE__ */ new Map();
1919
- for (const operation of operations) {
1920
- const surfaceId = getOperationSurfaceId(operation) ?? DEFAULT_SURFACE_ID;
1921
- if (!groups.has(surfaceId)) groups.set(surfaceId, []);
1922
- groups.get(surfaceId).push(operation);
1923
- }
1924
- return groups;
1925
- }, [operations]);
1926
- if (!groupedOperations.size) return null;
1927
- return /* @__PURE__ */ jsx("div", {
1928
- className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
1929
- children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ jsx(ReactSurfaceHost, {
1930
- surfaceId,
1931
- operations: ops,
1932
- theme,
1933
- agent,
1934
- copilotkit
1935
- }, surfaceId))
1936
- });
1937
- }
1938
- };
1939
+ //#region src/v2/providers/SandboxFunctionsContext.ts
1940
+ const SandboxFunctionsContext = createContext([]);
1941
+ function useSandboxFunctions() {
1942
+ return useContext(SandboxFunctionsContext);
1939
1943
  }
1944
+
1945
+ //#endregion
1946
+ //#region src/v2/lib/processPartialHtml.ts
1940
1947
  /**
1941
- * Renders a single A2UI surface using the React renderer.
1942
- * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
1948
+ * Extracts all complete `<style>` blocks from the raw HTML.
1949
+ * Returns the concatenated style tags, suitable for injection into `<head>`.
1943
1950
  */
1944
- function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit }) {
1945
- return /* @__PURE__ */ jsx("div", {
1946
- className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
1947
- children: /* @__PURE__ */ jsxs(A2UIProvider, {
1948
- onAction: useCallback(async (message) => {
1949
- if (!agent) return;
1950
- try {
1951
- console.info("[A2UI] Action dispatched", message.userAction);
1952
- copilotkit.setProperties({
1953
- ...copilotkit.properties ?? {},
1954
- a2uiAction: message
1955
- });
1956
- await copilotkit.runAgent({ agent });
1957
- } finally {
1958
- if (copilotkit.properties) {
1959
- const { a2uiAction, ...rest } = copilotkit.properties;
1960
- copilotkit.setProperties(rest);
1961
- }
1962
- }
1963
- }, [agent, copilotkit]),
1964
- theme,
1965
- children: [/* @__PURE__ */ jsx(SurfaceMessageProcessor, {
1966
- surfaceId,
1967
- operations
1968
- }), /* @__PURE__ */ jsx(A2UIRenderer, {
1969
- surfaceId,
1970
- className: "cpk:flex cpk:flex-1"
1971
- })]
1972
- })
1973
- });
1951
+ function extractCompleteStyles(html) {
1952
+ const matches = html.match(/<style\b[^>]*>[\s\S]*?<\/style>/gi);
1953
+ return matches ? matches.join("") : "";
1974
1954
  }
1975
1955
  /**
1976
- * Processes A2UI operations into the provider's message processor.
1977
- * Must be a child of A2UIProvider to access the actions context.
1956
+ * Processes raw accumulated HTML for safe preview via innerHTML injection.
1957
+ * Pure function, no DOM dependencies.
1958
+ *
1959
+ * Pipeline (order matters):
1960
+ * 1. Strip incomplete tag at end
1961
+ * 2. Strip complete <style>, <script>, and <head> blocks
1962
+ * 3. Strip incomplete <style>/<script>/<head> blocks
1963
+ * 4. Strip incomplete HTML entities
1964
+ * 5. Extract body content (or use full string if no <body>)
1978
1965
  */
1979
- function SurfaceMessageProcessor({ surfaceId, operations }) {
1980
- const { processMessages } = useA2UIActions();
1981
- const lastProcessedRef = useRef("");
1982
- useEffect(() => {
1983
- const key = `${surfaceId}-${JSON.stringify(operations)}`;
1984
- if (key === lastProcessedRef.current) return;
1985
- lastProcessedRef.current = key;
1986
- processMessages(operations);
1987
- }, [
1988
- processMessages,
1989
- surfaceId,
1990
- operations
1991
- ]);
1992
- return null;
1966
+ function processPartialHtml(html) {
1967
+ let result = html;
1968
+ result = result.replace(/<[^>]*$/, "");
1969
+ result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*?<\/\1>/gi, "");
1970
+ result = result.replace(/<(style|script|head)\b[^>]*>[\s\S]*$/gi, "");
1971
+ result = result.replace(/&[a-zA-Z0-9#]*$/, "");
1972
+ const bodyMatch = result.match(/<body[^>]*>([\s\S]*)/i);
1973
+ if (bodyMatch) {
1974
+ result = bodyMatch[1];
1975
+ result = result.replace(/<\/body>[\s\S]*/i, "");
1976
+ }
1977
+ return result;
1978
+ }
1979
+
1980
+ //#endregion
1981
+ //#region src/v2/components/OpenGenerativeUIRenderer.tsx
1982
+ const OpenGenerativeUIActivityType = "open-generative-ui";
1983
+ const OpenGenerativeUIContentSchema = z.object({
1984
+ initialHeight: z.number().optional(),
1985
+ generating: z.boolean().optional(),
1986
+ css: z.string().optional(),
1987
+ cssComplete: z.boolean().optional(),
1988
+ html: z.array(z.string()).optional(),
1989
+ htmlComplete: z.boolean().optional(),
1990
+ jsFunctions: z.string().optional(),
1991
+ jsFunctionsComplete: z.boolean().optional(),
1992
+ jsExpressions: z.array(z.string()).optional(),
1993
+ jsExpressionsComplete: z.boolean().optional()
1994
+ });
1995
+ /**
1996
+ * Schema for the generateSandboxedUi tool call arguments.
1997
+ * Used by the frontend tool renderer to display placeholder messages.
1998
+ */
1999
+ const GenerateSandboxedUiArgsSchema = z.object({
2000
+ initialHeight: z.number().optional(),
2001
+ placeholderMessages: z.array(z.string()).optional(),
2002
+ css: z.string().optional(),
2003
+ html: z.string().optional(),
2004
+ jsFunctions: z.string().optional(),
2005
+ jsExpressions: z.array(z.string()).optional()
2006
+ });
2007
+ const THROTTLE_MS = 1e3;
2008
+ /**
2009
+ * Returns true when the inner component should re-render immediately
2010
+ * (no throttle delay).
2011
+ */
2012
+ function shouldFlushImmediately(prev, next) {
2013
+ if (next.cssComplete && (!prev || !prev.cssComplete)) return true;
2014
+ if (next.htmlComplete) return true;
2015
+ if (next.generating === false) return true;
2016
+ if (next.jsFunctions && (!prev || !prev.jsFunctions)) return true;
2017
+ if ((next.jsExpressions?.length ?? 0) > (prev?.jsExpressions?.length ?? 0)) return true;
2018
+ if (next.html?.length && (!prev || !prev.html?.length)) return true;
2019
+ return false;
2020
+ }
2021
+ /**
2022
+ * Outer wrapper — absorbs every parent re-render but only forwards
2023
+ * throttled content snapshots to the memoized inner component.
2024
+ */
2025
+ const OpenGenerativeUIActivityRenderer = function OpenGenerativeUIActivityRenderer({ content }) {
2026
+ const latestContentRef = useRef(content);
2027
+ latestContentRef.current = content;
2028
+ const [throttledContent, setThrottledContent] = useState(content);
2029
+ const throttledContentRef = useRef(throttledContent);
2030
+ const timerRef = useRef(null);
2031
+ if (throttledContentRef.current !== content) {
2032
+ if (shouldFlushImmediately(throttledContentRef.current, content)) {
2033
+ if (timerRef.current !== null) {
2034
+ clearTimeout(timerRef.current);
2035
+ timerRef.current = null;
2036
+ }
2037
+ throttledContentRef.current = content;
2038
+ setThrottledContent(content);
2039
+ }
2040
+ }
2041
+ const flush = useCallback(() => {
2042
+ timerRef.current = null;
2043
+ const latest = latestContentRef.current;
2044
+ throttledContentRef.current = latest;
2045
+ setThrottledContent(latest);
2046
+ }, []);
2047
+ useEffect(() => {
2048
+ if (throttledContentRef.current === content) return;
2049
+ if (timerRef.current === null) timerRef.current = setTimeout(flush, THROTTLE_MS);
2050
+ }, [content, flush]);
2051
+ useEffect(() => {
2052
+ return () => {
2053
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
2054
+ };
2055
+ }, []);
2056
+ return /* @__PURE__ */ jsx(OpenGenerativeUIActivityRendererInner, { content: throttledContent });
2057
+ };
2058
+ function ensureHead(html) {
2059
+ if (/<head[\s>]/i.test(html)) return html;
2060
+ return `<head></head>${html}`;
2061
+ }
2062
+ function injectCssIntoHtml(html, css) {
2063
+ const headCloseIdx = html.indexOf("</head>");
2064
+ if (headCloseIdx !== -1) return html.slice(0, headCloseIdx) + `<style>${css}</style>` + html.slice(headCloseIdx);
2065
+ return `<head><style>${css}</style></head>${html}`;
2066
+ }
2067
+ const OpenGenerativeUIActivityRendererInner = React.memo(function OpenGenerativeUIActivityRendererInner({ content }) {
2068
+ const initialHeight = content.initialHeight ?? 200;
2069
+ const [autoHeight, setAutoHeight] = useState(null);
2070
+ const sandboxFunctions = useSandboxFunctions();
2071
+ const localApi = useMemo(() => {
2072
+ const api = {};
2073
+ for (const fn of sandboxFunctions) api[fn.name] = fn.handler;
2074
+ return api;
2075
+ }, [sandboxFunctions]);
2076
+ const fullHtml = content.htmlComplete && content.html?.length ? content.html.join("") : void 0;
2077
+ const css = content.cssComplete ? content.css : void 0;
2078
+ const cssReady = !!content.cssComplete;
2079
+ const partialHtml = !content.htmlComplete && content.html?.length ? content.html.join("") : void 0;
2080
+ const previewBody = partialHtml ? processPartialHtml(partialHtml) : void 0;
2081
+ const previewStyles = partialHtml ? extractCompleteStyles(partialHtml) : "";
2082
+ const hasPreview = cssReady && !!previewBody?.trim();
2083
+ const hasVisibleSandbox = !!fullHtml || hasPreview;
2084
+ const containerRef = useRef(null);
2085
+ const sandboxRef = useRef(null);
2086
+ const previewSandboxRef = useRef(null);
2087
+ const previewReadyRef = useRef(false);
2088
+ const sandboxReadyRef = useRef(false);
2089
+ const executedIndexRef = useRef(0);
2090
+ const pendingQueueRef = useRef([]);
2091
+ const jsFunctionsInjectedRef = useRef(false);
2092
+ useEffect(() => {
2093
+ const container = containerRef.current;
2094
+ if (!container || fullHtml || !hasPreview || previewSandboxRef.current) return;
2095
+ let cancelled = false;
2096
+ import("@jetbrains/websandbox").then((mod) => {
2097
+ if (cancelled) return;
2098
+ const sandbox = (mod.default?.default ?? mod.default).create({}, {
2099
+ frameContainer: container,
2100
+ frameContent: "<head></head><body></body>",
2101
+ allowAdditionalAttributes: ""
2102
+ });
2103
+ previewSandboxRef.current = sandbox;
2104
+ sandbox.iframe.style.width = "100%";
2105
+ sandbox.iframe.style.height = "100%";
2106
+ sandbox.iframe.style.border = "none";
2107
+ sandbox.iframe.style.backgroundColor = "transparent";
2108
+ sandbox.promise.then(() => {
2109
+ if (cancelled) return;
2110
+ previewReadyRef.current = true;
2111
+ sandbox.run(`
2112
+ var s = document.createElement('style');
2113
+ s.textContent = 'html, body { overflow: hidden !important; }';
2114
+ document.head.appendChild(s);
2115
+ `);
2116
+ const headParts = [];
2117
+ if (css) headParts.push(`<style>${css}</style>`);
2118
+ if (previewStyles) headParts.push(previewStyles);
2119
+ if (headParts.length) sandbox.run(`document.head.innerHTML = ${JSON.stringify(headParts.join(""))}`);
2120
+ if (previewBody) sandbox.run(`document.body.innerHTML = ${JSON.stringify(previewBody)}`);
2121
+ });
2122
+ }).catch((err) => {
2123
+ console.error("[OpenGenerativeUI] Failed to load sandbox module:", err);
2124
+ });
2125
+ return () => {
2126
+ cancelled = true;
2127
+ };
2128
+ }, [hasPreview, fullHtml]);
2129
+ useEffect(() => {
2130
+ if (!previewSandboxRef.current || !previewReadyRef.current) return;
2131
+ const headParts = [];
2132
+ if (css) headParts.push(`<style>${css}</style>`);
2133
+ if (previewStyles) headParts.push(previewStyles);
2134
+ if (headParts.length) previewSandboxRef.current.run(`document.head.innerHTML = ${JSON.stringify(headParts.join(""))}`);
2135
+ if (!previewBody) return;
2136
+ previewSandboxRef.current.run(`document.body.innerHTML = ${JSON.stringify(previewBody)}`);
2137
+ }, [
2138
+ previewBody,
2139
+ previewStyles,
2140
+ css
2141
+ ]);
2142
+ useEffect(() => {
2143
+ const container = containerRef.current;
2144
+ if (!container || !fullHtml) return;
2145
+ if (previewSandboxRef.current) {
2146
+ previewSandboxRef.current.destroy();
2147
+ previewSandboxRef.current = null;
2148
+ previewReadyRef.current = false;
2149
+ }
2150
+ let cancelled = false;
2151
+ executedIndexRef.current = 0;
2152
+ jsFunctionsInjectedRef.current = false;
2153
+ sandboxReadyRef.current = false;
2154
+ pendingQueueRef.current = [];
2155
+ const htmlContent = css ? injectCssIntoHtml(fullHtml, css) : fullHtml;
2156
+ import("@jetbrains/websandbox").then((mod) => {
2157
+ if (cancelled) return;
2158
+ const sandbox = (mod.default?.default ?? mod.default).create(localApi, {
2159
+ frameContainer: container,
2160
+ frameContent: ensureHead(htmlContent),
2161
+ allowAdditionalAttributes: ""
2162
+ });
2163
+ sandboxRef.current = sandbox;
2164
+ sandbox.iframe.style.width = "100%";
2165
+ sandbox.iframe.style.height = "100%";
2166
+ sandbox.iframe.style.border = "none";
2167
+ sandbox.iframe.style.backgroundColor = "transparent";
2168
+ sandbox.promise.then(() => {
2169
+ if (cancelled) return;
2170
+ sandboxReadyRef.current = true;
2171
+ sandbox.run(`
2172
+ var s = document.createElement('style');
2173
+ s.textContent = 'html, body { overflow: hidden !important; }';
2174
+ document.head.appendChild(s);
2175
+ `);
2176
+ const queue = pendingQueueRef.current;
2177
+ pendingQueueRef.current = [];
2178
+ for (const code of queue) sandbox.run(code);
2179
+ });
2180
+ }).catch((err) => {
2181
+ console.error("[OpenGenerativeUI] Failed to load sandbox module:", err);
2182
+ });
2183
+ return () => {
2184
+ cancelled = true;
2185
+ if (previewSandboxRef.current) {
2186
+ previewSandboxRef.current.destroy();
2187
+ previewSandboxRef.current = null;
2188
+ previewReadyRef.current = false;
2189
+ }
2190
+ if (sandboxRef.current) {
2191
+ sandboxRef.current.destroy();
2192
+ sandboxRef.current = null;
2193
+ }
2194
+ sandboxReadyRef.current = false;
2195
+ setAutoHeight(null);
2196
+ };
2197
+ }, [
2198
+ fullHtml,
2199
+ css,
2200
+ localApi
2201
+ ]);
2202
+ useEffect(() => {
2203
+ if (!content.jsFunctions || jsFunctionsInjectedRef.current) return;
2204
+ jsFunctionsInjectedRef.current = true;
2205
+ const sandbox = sandboxRef.current;
2206
+ if (sandboxReadyRef.current && sandbox) sandbox.run(content.jsFunctions);
2207
+ else pendingQueueRef.current.push(content.jsFunctions);
2208
+ }, [content.jsFunctions]);
2209
+ useEffect(() => {
2210
+ const expressions = content.jsExpressions;
2211
+ if (!expressions || expressions.length === 0) return;
2212
+ const startIndex = executedIndexRef.current;
2213
+ if (startIndex >= expressions.length) return;
2214
+ const newExprs = expressions.slice(startIndex);
2215
+ executedIndexRef.current = expressions.length;
2216
+ const sandbox = sandboxRef.current;
2217
+ if (sandboxReadyRef.current && sandbox) (async () => {
2218
+ for (const expr of newExprs) await sandbox.run(expr);
2219
+ })();
2220
+ else pendingQueueRef.current.push(...newExprs);
2221
+ }, [content.jsExpressions?.length]);
2222
+ const generationDone = content.generating === false;
2223
+ useEffect(() => {
2224
+ const sandbox = sandboxRef.current;
2225
+ if (!generationDone || !sandbox) return;
2226
+ let handled = false;
2227
+ const onMessage = (e) => {
2228
+ if (handled) return;
2229
+ if (e.source === sandbox.iframe.contentWindow && e.data?.type === "__ck_resize") {
2230
+ handled = true;
2231
+ setAutoHeight(e.data.height);
2232
+ window.removeEventListener("message", onMessage);
2233
+ }
2234
+ };
2235
+ window.addEventListener("message", onMessage);
2236
+ const measureOnce = `
2237
+ (function() {
2238
+ var s = document.createElement('style');
2239
+ s.textContent = 'body { height: auto !important; min-height: 0 !important; }';
2240
+ document.head.appendChild(s);
2241
+ var h = document.body.scrollHeight;
2242
+ var cs = getComputedStyle(document.body);
2243
+ h += parseFloat(cs.marginTop) || 0;
2244
+ h += parseFloat(cs.marginBottom) || 0;
2245
+ s.remove();
2246
+ parent.postMessage({ type: "__ck_resize", height: Math.ceil(h) }, "*");
2247
+ })();
2248
+ `;
2249
+ if (sandboxReadyRef.current) sandbox.run(measureOnce);
2250
+ else pendingQueueRef.current.push(measureOnce);
2251
+ return () => {
2252
+ window.removeEventListener("message", onMessage);
2253
+ };
2254
+ }, [generationDone]);
2255
+ const height = autoHeight ?? initialHeight;
2256
+ const isGenerating = content.generating !== false;
2257
+ return /* @__PURE__ */ jsx("div", {
2258
+ ref: containerRef,
2259
+ style: {
2260
+ position: "relative",
2261
+ width: "100%",
2262
+ height: `${height}px`,
2263
+ borderRadius: "8px",
2264
+ backgroundColor: hasVisibleSandbox ? "transparent" : "#f5f5f5",
2265
+ border: hasVisibleSandbox ? "none" : "1px solid #e0e0e0",
2266
+ display: hasVisibleSandbox ? "block" : "flex",
2267
+ alignItems: hasVisibleSandbox ? void 0 : "center",
2268
+ justifyContent: hasVisibleSandbox ? void 0 : "center",
2269
+ overflow: "hidden"
2270
+ },
2271
+ children: isGenerating && /* @__PURE__ */ jsxs("div", {
2272
+ style: {
2273
+ position: "absolute",
2274
+ inset: 0,
2275
+ zIndex: 10,
2276
+ pointerEvents: "all",
2277
+ backgroundColor: "rgba(255, 255, 255, 0.5)",
2278
+ display: "flex",
2279
+ alignItems: "center",
2280
+ justifyContent: "center"
2281
+ },
2282
+ children: [/* @__PURE__ */ jsxs("svg", {
2283
+ width: "48",
2284
+ height: "48",
2285
+ viewBox: "0 0 24 24",
2286
+ fill: "none",
2287
+ style: { animation: "ck-spin 1s linear infinite" },
2288
+ children: [/* @__PURE__ */ jsx("circle", {
2289
+ cx: "12",
2290
+ cy: "12",
2291
+ r: "10",
2292
+ stroke: "#e0e0e0",
2293
+ strokeWidth: "3"
2294
+ }), /* @__PURE__ */ jsx("path", {
2295
+ d: "M12 2a10 10 0 0 1 10 10",
2296
+ stroke: "#999",
2297
+ strokeWidth: "3",
2298
+ strokeLinecap: "round"
2299
+ })]
2300
+ }), /* @__PURE__ */ jsx("style", { children: `@keyframes ck-spin { to { transform: rotate(360deg) } }` })]
2301
+ })
2302
+ });
2303
+ }, (prev, next) => prev.content === next.content);
2304
+ /**
2305
+ * Frontend tool renderer for generateSandboxedUi.
2306
+ * Displays placeholder messages while the UI is being generated.
2307
+ */
2308
+ const OpenGenerativeUIToolRenderer = function OpenGenerativeUIToolRenderer(props) {
2309
+ const [visibleMessageIndex, setVisibleMessageIndex] = useState(0);
2310
+ const prevMessageCountRef = useRef(0);
2311
+ const messages = props.args.placeholderMessages;
2312
+ useEffect(() => {
2313
+ if (!messages || messages.length === 0) return;
2314
+ if (messages.length !== prevMessageCountRef.current) {
2315
+ prevMessageCountRef.current = messages.length;
2316
+ setVisibleMessageIndex(messages.length - 1);
2317
+ }
2318
+ if (props.status === ToolCallStatus.Complete) return;
2319
+ const timer = setInterval(() => {
2320
+ setVisibleMessageIndex((i) => (i + 1) % messages.length);
2321
+ }, 5e3);
2322
+ return () => clearInterval(timer);
2323
+ }, [messages?.length, props.status]);
2324
+ if (props.status === ToolCallStatus.Complete) return null;
2325
+ if (!messages || messages.length === 0) return null;
2326
+ return /* @__PURE__ */ jsx("div", {
2327
+ style: {
2328
+ padding: "8px 12px",
2329
+ color: "#999",
2330
+ fontSize: "14px"
2331
+ },
2332
+ children: messages[visibleMessageIndex] ?? messages[0]
2333
+ });
2334
+ };
2335
+
2336
+ //#endregion
2337
+ //#region src/v2/a2ui/A2UIMessageRenderer.tsx
2338
+ /**
2339
+ * The container key used to wrap A2UI operations for explicit detection.
2340
+ * Must match A2UI_OPERATIONS_KEY in @ag-ui/a2ui-middleware and copilotkit.a2ui (Python).
2341
+ */
2342
+ const A2UI_OPERATIONS_KEY = "a2ui_operations";
2343
+ let initialized = false;
2344
+ function ensureInitialized() {
2345
+ if (!initialized) {
2346
+ initializeDefaultCatalog();
2347
+ injectStyles();
2348
+ initialized = true;
2349
+ }
2350
+ }
2351
+ function createA2UIMessageRenderer(options) {
2352
+ const { theme, catalog, loadingComponent } = options;
2353
+ return {
2354
+ activityType: "a2ui-surface",
2355
+ content: z.any(),
2356
+ render: ({ content, agent }) => {
2357
+ ensureInitialized();
2358
+ const [operations, setOperations] = useState([]);
2359
+ const { copilotkit } = useCopilotKit();
2360
+ const lastContentRef = useRef(null);
2361
+ useEffect(() => {
2362
+ if (content === lastContentRef.current) return;
2363
+ lastContentRef.current = content;
2364
+ const incoming = content?.[A2UI_OPERATIONS_KEY];
2365
+ if (!content || !Array.isArray(incoming)) {
2366
+ setOperations([]);
2367
+ return;
2368
+ }
2369
+ setOperations(incoming);
2370
+ }, [content]);
2371
+ const groupedOperations = useMemo(() => {
2372
+ const groups = /* @__PURE__ */ new Map();
2373
+ for (const operation of operations) {
2374
+ const surfaceId = getOperationSurfaceId(operation) ?? DEFAULT_SURFACE_ID;
2375
+ if (!groups.has(surfaceId)) groups.set(surfaceId, []);
2376
+ groups.get(surfaceId).push(operation);
2377
+ }
2378
+ return groups;
2379
+ }, [operations]);
2380
+ if (!groupedOperations.size) return /* @__PURE__ */ jsx(loadingComponent ?? DefaultA2UILoading, {});
2381
+ return /* @__PURE__ */ jsx("div", {
2382
+ className: "cpk:flex cpk:min-h-0 cpk:flex-1 cpk:flex-col cpk:gap-6 cpk:overflow-auto cpk:py-6",
2383
+ children: Array.from(groupedOperations.entries()).map(([surfaceId, ops]) => /* @__PURE__ */ jsx(ReactSurfaceHost, {
2384
+ surfaceId,
2385
+ operations: ops,
2386
+ theme,
2387
+ agent,
2388
+ copilotkit,
2389
+ catalog
2390
+ }, surfaceId))
2391
+ });
2392
+ }
2393
+ };
2394
+ }
2395
+ /**
2396
+ * Renders a single A2UI surface using the React renderer.
2397
+ * Wraps A2UIProvider + A2UIRenderer and bridges actions back to CopilotKit.
2398
+ */
2399
+ function ReactSurfaceHost({ surfaceId, operations, theme, agent, copilotkit, catalog }) {
2400
+ return /* @__PURE__ */ jsx("div", {
2401
+ className: "cpk:flex cpk:w-full cpk:flex-none cpk:flex-col cpk:gap-4",
2402
+ children: /* @__PURE__ */ jsxs(A2UIProvider, {
2403
+ onAction: useCallback(async (message) => {
2404
+ if (!agent) return;
2405
+ message.userAction;
2406
+ try {
2407
+ copilotkit.setProperties({
2408
+ ...copilotkit.properties ?? {},
2409
+ a2uiAction: message
2410
+ });
2411
+ await copilotkit.runAgent({ agent });
2412
+ } finally {
2413
+ if (copilotkit.properties) {
2414
+ const { a2uiAction, ...rest } = copilotkit.properties;
2415
+ copilotkit.setProperties(rest);
2416
+ }
2417
+ }
2418
+ }, [agent, copilotkit]),
2419
+ theme,
2420
+ catalog,
2421
+ children: [/* @__PURE__ */ jsx(SurfaceMessageProcessor, {
2422
+ surfaceId,
2423
+ operations
2424
+ }), /* @__PURE__ */ jsx(A2UISurfaceOrError, { surfaceId })]
2425
+ })
2426
+ });
2427
+ }
2428
+ /**
2429
+ * Renders the A2UI surface, or an error message if processing failed.
2430
+ * Must be a child of A2UIProvider to access the error state.
2431
+ */
2432
+ function A2UISurfaceOrError({ surfaceId }) {
2433
+ const error = useA2UIError();
2434
+ if (error) return /* @__PURE__ */ jsxs("div", {
2435
+ className: "cpk:rounded-lg cpk:border cpk:border-red-200 cpk:bg-red-50 cpk:p-3 cpk:text-sm cpk:text-red-700",
2436
+ children: ["A2UI render error: ", error]
2437
+ });
2438
+ return /* @__PURE__ */ jsx(A2UIRenderer, {
2439
+ surfaceId,
2440
+ className: "cpk:flex cpk:flex-1"
2441
+ });
2442
+ }
2443
+ /**
2444
+ * Processes A2UI operations into the provider's message processor.
2445
+ * Must be a child of A2UIProvider to access the actions context.
2446
+ */
2447
+ function SurfaceMessageProcessor({ surfaceId, operations }) {
2448
+ const { processMessages, getSurface } = useA2UIActions();
2449
+ const lastHashRef = useRef("");
2450
+ useEffect(() => {
2451
+ const hash = JSON.stringify(operations);
2452
+ if (hash === lastHashRef.current) return;
2453
+ lastHashRef.current = hash;
2454
+ processMessages(getSurface(surfaceId) ? operations.filter((op) => !op?.createSurface) : operations);
2455
+ }, [
2456
+ processMessages,
2457
+ getSurface,
2458
+ surfaceId,
2459
+ operations
2460
+ ]);
2461
+ return null;
2462
+ }
2463
+ /**
2464
+ * Default loading component shown while an A2UI surface is generating.
2465
+ * Displays an animated shimmer skeleton.
2466
+ */
2467
+ function DefaultA2UILoading() {
2468
+ return /* @__PURE__ */ jsxs("div", {
2469
+ 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",
2470
+ style: { minHeight: 120 },
2471
+ children: [
2472
+ /* @__PURE__ */ jsxs("div", {
2473
+ className: "cpk:flex cpk:items-center cpk:gap-2",
2474
+ children: [/* @__PURE__ */ jsx("div", {
2475
+ className: "cpk:h-3 cpk:w-3 cpk:rounded-full cpk:bg-gray-200",
2476
+ style: { animation: "cpk-a2ui-pulse 1.5s ease-in-out infinite" }
2477
+ }), /* @__PURE__ */ jsx("span", {
2478
+ className: "cpk:text-xs cpk:font-medium cpk:text-gray-400",
2479
+ children: "Generating UI..."
2480
+ })]
2481
+ }),
2482
+ /* @__PURE__ */ jsx("div", {
2483
+ className: "cpk:flex cpk:flex-col cpk:gap-2",
2484
+ children: [
2485
+ .8,
2486
+ .6,
2487
+ .4
2488
+ ].map((width, i) => /* @__PURE__ */ jsx("div", {
2489
+ className: "cpk:h-3 cpk:rounded cpk:bg-gray-200/70",
2490
+ style: {
2491
+ width: `${width * 100}%`,
2492
+ animation: `cpk-a2ui-pulse 1.5s ease-in-out ${i * .15}s infinite`
2493
+ }
2494
+ }, i))
2495
+ }),
2496
+ /* @__PURE__ */ jsx("style", { children: `
2497
+ @keyframes cpk-a2ui-pulse {
2498
+ 0%, 100% { opacity: 0.4; }
2499
+ 50% { opacity: 1; }
2500
+ }
2501
+ ` })
2502
+ ]
2503
+ });
1993
2504
  }
1994
2505
  function getOperationSurfaceId(operation) {
1995
2506
  if (!operation || typeof operation !== "object") return null;
1996
2507
  if (typeof operation.surfaceId === "string") return operation.surfaceId;
1997
- return operation?.beginRendering?.surfaceId ?? operation?.surfaceUpdate?.surfaceId ?? operation?.dataModelUpdate?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
2508
+ return operation?.createSurface?.surfaceId ?? operation?.updateComponents?.surfaceId ?? operation?.updateDataModel?.surfaceId ?? operation?.deleteSurface?.surfaceId ?? null;
2509
+ }
2510
+
2511
+ //#endregion
2512
+ //#region src/v2/types/defineToolCallRenderer.ts
2513
+ function defineToolCallRenderer(def) {
2514
+ const argsSchema = def.name === "*" && !def.args ? z.any() : def.args;
2515
+ return {
2516
+ name: def.name,
2517
+ args: argsSchema,
2518
+ render: def.render,
2519
+ ...def.agentId ? { agentId: def.agentId } : {}
2520
+ };
2521
+ }
2522
+
2523
+ //#endregion
2524
+ //#region src/v2/a2ui/A2UIToolCallRenderer.tsx
2525
+ /**
2526
+ * Tool name used by the dynamic A2UI generation secondary LLM.
2527
+ * This renderer is auto-registered when A2UI is enabled.
2528
+ */
2529
+ const RENDER_A2UI_TOOL_NAME = "render_a2ui";
2530
+ /**
2531
+ * Built-in progress indicator for dynamic A2UI generation.
2532
+ * Shows a skeleton wireframe that progressively reveals as tokens stream in.
2533
+ *
2534
+ * Registered automatically when A2UI is enabled. Users can override by
2535
+ * providing their own `useRenderTool({ name: "render_a2ui", ... })`.
2536
+ */
2537
+ function A2UIProgressIndicator({ parameters }) {
2538
+ const lastRef = useRef({
2539
+ time: 0,
2540
+ tokens: 0
2541
+ });
2542
+ const now = Date.now();
2543
+ let { tokens } = lastRef.current;
2544
+ if (now - lastRef.current.time > 200) {
2545
+ const chars = JSON.stringify(parameters ?? {}).length;
2546
+ tokens = Math.round(chars / 4);
2547
+ lastRef.current = {
2548
+ time: now,
2549
+ tokens
2550
+ };
2551
+ }
2552
+ const phase = tokens < 50 ? 0 : tokens < 200 ? 1 : tokens < 400 ? 2 : 3;
2553
+ return /* @__PURE__ */ jsxs("div", {
2554
+ style: {
2555
+ margin: "12px 0",
2556
+ maxWidth: 320
2557
+ },
2558
+ children: [
2559
+ /* @__PURE__ */ jsxs("div", {
2560
+ style: {
2561
+ position: "relative",
2562
+ overflow: "hidden",
2563
+ borderRadius: 12,
2564
+ border: "1px solid rgba(228,228,231,0.8)",
2565
+ backgroundColor: "#fff",
2566
+ boxShadow: "0 1px 2px rgba(0,0,0,0.04)",
2567
+ padding: "16px 18px 14px"
2568
+ },
2569
+ children: [
2570
+ /* @__PURE__ */ jsxs("div", {
2571
+ style: {
2572
+ display: "flex",
2573
+ alignItems: "center",
2574
+ gap: 8,
2575
+ marginBottom: 12
2576
+ },
2577
+ children: [/* @__PURE__ */ jsxs("div", {
2578
+ style: {
2579
+ display: "flex",
2580
+ gap: 4
2581
+ },
2582
+ children: [
2583
+ /* @__PURE__ */ jsx(Dot, {}),
2584
+ /* @__PURE__ */ jsx(Dot, {}),
2585
+ /* @__PURE__ */ jsx(Dot, {})
2586
+ ]
2587
+ }), /* @__PURE__ */ jsx(Bar, {
2588
+ w: 64,
2589
+ h: 6,
2590
+ bg: "#e4e4e7",
2591
+ opacity: phase >= 1 ? 1 : .4,
2592
+ transition: "opacity 0.5s"
2593
+ })]
2594
+ }),
2595
+ /* @__PURE__ */ jsxs("div", {
2596
+ style: {
2597
+ display: "grid",
2598
+ gap: 7
2599
+ },
2600
+ children: [
2601
+ /* @__PURE__ */ jsxs(Row, {
2602
+ show: phase >= 0,
2603
+ children: [/* @__PURE__ */ jsx(Bar, {
2604
+ w: 36,
2605
+ h: 7,
2606
+ bg: "rgba(147,197,253,0.7)",
2607
+ anim: 0
2608
+ }), /* @__PURE__ */ jsx(Bar, {
2609
+ w: 80,
2610
+ h: 7,
2611
+ bg: "rgba(219,234,254,0.8)",
2612
+ anim: .2
2613
+ })]
2614
+ }),
2615
+ /* @__PURE__ */ jsxs(Row, {
2616
+ show: phase >= 0,
2617
+ delay: .1,
2618
+ children: [
2619
+ /* @__PURE__ */ jsx(Spacer, {}),
2620
+ /* @__PURE__ */ jsx(Dot, {}),
2621
+ /* @__PURE__ */ jsx(Bar, {
2622
+ w: 100,
2623
+ h: 7,
2624
+ bg: "rgba(24,24,27,0.2)",
2625
+ anim: .3
2626
+ })
2627
+ ]
2628
+ }),
2629
+ /* @__PURE__ */ jsxs(Row, {
2630
+ show: phase >= 1,
2631
+ delay: .15,
2632
+ children: [
2633
+ /* @__PURE__ */ jsx(Spacer, {}),
2634
+ /* @__PURE__ */ jsx(Bar, {
2635
+ w: 48,
2636
+ h: 7,
2637
+ bg: "rgba(24,24,27,0.15)",
2638
+ anim: .1
2639
+ }),
2640
+ /* @__PURE__ */ jsx(Bar, {
2641
+ w: 40,
2642
+ h: 7,
2643
+ bg: "rgba(153,246,228,0.6)",
2644
+ anim: .5
2645
+ }),
2646
+ /* @__PURE__ */ jsx(Bar, {
2647
+ w: 56,
2648
+ h: 7,
2649
+ bg: "rgba(147,197,253,0.6)",
2650
+ anim: .3
2651
+ })
2652
+ ]
2653
+ }),
2654
+ /* @__PURE__ */ jsxs(Row, {
2655
+ show: phase >= 1,
2656
+ delay: .2,
2657
+ children: [
2658
+ /* @__PURE__ */ jsx(Spacer, {}),
2659
+ /* @__PURE__ */ jsx(Dot, {}),
2660
+ /* @__PURE__ */ jsx(Bar, {
2661
+ w: 60,
2662
+ h: 7,
2663
+ bg: "rgba(24,24,27,0.15)",
2664
+ anim: .4
2665
+ })
2666
+ ]
2667
+ }),
2668
+ /* @__PURE__ */ jsxs(Row, {
2669
+ show: phase >= 2,
2670
+ delay: .25,
2671
+ children: [
2672
+ /* @__PURE__ */ jsx(Bar, {
2673
+ w: 40,
2674
+ h: 7,
2675
+ bg: "rgba(153,246,228,0.5)",
2676
+ anim: .2
2677
+ }),
2678
+ /* @__PURE__ */ jsx(Dot, {}),
2679
+ /* @__PURE__ */ jsx(Bar, {
2680
+ w: 48,
2681
+ h: 7,
2682
+ bg: "rgba(24,24,27,0.15)",
2683
+ anim: .6
2684
+ }),
2685
+ /* @__PURE__ */ jsx(Bar, {
2686
+ w: 64,
2687
+ h: 7,
2688
+ bg: "rgba(147,197,253,0.5)",
2689
+ anim: .1
2690
+ })
2691
+ ]
2692
+ }),
2693
+ /* @__PURE__ */ jsxs(Row, {
2694
+ show: phase >= 2,
2695
+ delay: .3,
2696
+ children: [/* @__PURE__ */ jsx(Bar, {
2697
+ w: 36,
2698
+ h: 7,
2699
+ bg: "rgba(147,197,253,0.6)",
2700
+ anim: .5
2701
+ }), /* @__PURE__ */ jsx(Bar, {
2702
+ w: 36,
2703
+ h: 7,
2704
+ bg: "rgba(24,24,27,0.12)",
2705
+ anim: .7
2706
+ })]
2707
+ }),
2708
+ /* @__PURE__ */ jsxs(Row, {
2709
+ show: phase >= 3,
2710
+ delay: .35,
2711
+ children: [
2712
+ /* @__PURE__ */ jsx(Dot, {}),
2713
+ /* @__PURE__ */ jsx(Bar, {
2714
+ w: 44,
2715
+ h: 7,
2716
+ bg: "rgba(24,24,27,0.18)",
2717
+ anim: .3
2718
+ }),
2719
+ /* @__PURE__ */ jsx(Dot, {}),
2720
+ /* @__PURE__ */ jsx(Bar, {
2721
+ w: 56,
2722
+ h: 7,
2723
+ bg: "rgba(153,246,228,0.5)",
2724
+ anim: .8
2725
+ }),
2726
+ /* @__PURE__ */ jsx(Bar, {
2727
+ w: 48,
2728
+ h: 7,
2729
+ bg: "rgba(147,197,253,0.5)",
2730
+ anim: .4
2731
+ })
2732
+ ]
2733
+ })
2734
+ ]
2735
+ }),
2736
+ /* @__PURE__ */ jsx("div", { style: {
2737
+ pointerEvents: "none",
2738
+ position: "absolute",
2739
+ inset: 0,
2740
+ background: "linear-gradient(105deg, transparent 0%, transparent 40%, rgba(255,255,255,0.6) 50%, transparent 60%, transparent 100%)",
2741
+ backgroundSize: "250% 100%",
2742
+ animation: "cpk-a2ui-sweep 3s ease-in-out infinite"
2743
+ } })
2744
+ ]
2745
+ }),
2746
+ /* @__PURE__ */ jsxs("div", {
2747
+ style: {
2748
+ display: "flex",
2749
+ alignItems: "center",
2750
+ justifyContent: "center",
2751
+ gap: 8,
2752
+ marginTop: 8
2753
+ },
2754
+ children: [/* @__PURE__ */ jsx("span", {
2755
+ style: {
2756
+ fontSize: 12,
2757
+ color: "#a1a1aa",
2758
+ letterSpacing: "0.025em"
2759
+ },
2760
+ children: "Building interface"
2761
+ }), tokens > 0 && /* @__PURE__ */ jsxs("span", {
2762
+ style: {
2763
+ fontSize: 11,
2764
+ color: "#d4d4d8",
2765
+ fontVariantNumeric: "tabular-nums"
2766
+ },
2767
+ children: [
2768
+ "~",
2769
+ tokens.toLocaleString(),
2770
+ " tokens"
2771
+ ]
2772
+ })]
2773
+ }),
2774
+ /* @__PURE__ */ jsx("style", { children: `
2775
+ @keyframes cpk-a2ui-fade {
2776
+ 0%, 100% { opacity: 1; }
2777
+ 50% { opacity: 0.5; }
2778
+ }
2779
+ @keyframes cpk-a2ui-sweep {
2780
+ 0% { background-position: 250% 0; }
2781
+ 100% { background-position: -250% 0; }
2782
+ }
2783
+ ` })
2784
+ ]
2785
+ });
2786
+ }
2787
+ function Dot() {
2788
+ return /* @__PURE__ */ jsx("div", { style: {
2789
+ width: 7,
2790
+ height: 7,
2791
+ borderRadius: "50%",
2792
+ backgroundColor: "#d4d4d8",
2793
+ flexShrink: 0
2794
+ } });
2795
+ }
2796
+ function Spacer() {
2797
+ return /* @__PURE__ */ jsx("div", { style: { width: 12 } });
2798
+ }
2799
+ function Bar({ w, h, bg, anim, opacity, transition }) {
2800
+ return /* @__PURE__ */ jsx("div", { style: {
2801
+ width: w,
2802
+ height: h,
2803
+ borderRadius: 9999,
2804
+ backgroundColor: bg,
2805
+ ...anim !== void 0 ? { animation: `cpk-a2ui-fade 2.4s ease-in-out ${anim}s infinite` } : {},
2806
+ ...opacity !== void 0 ? { opacity } : {},
2807
+ ...transition ? { transition } : {}
2808
+ } });
2809
+ }
2810
+ function Row({ children, show, delay = 0 }) {
2811
+ return /* @__PURE__ */ jsx("div", {
2812
+ style: {
2813
+ display: "flex",
2814
+ alignItems: "center",
2815
+ gap: 6,
2816
+ opacity: show ? 1 : 0,
2817
+ transition: `opacity 0.4s ${delay}s`
2818
+ },
2819
+ children
2820
+ });
1998
2821
  }
1999
- function stringifyOperations(ops) {
2000
- try {
2001
- return JSON.stringify(ops);
2002
- } catch (error) {
2003
- return null;
2004
- }
2822
+ /**
2823
+ * Registers the built-in `render_a2ui` tool call renderer via the props-based
2824
+ * `setRenderToolCalls` mechanism (not `useRenderTool`).
2825
+ *
2826
+ * This ensures user-registered `useRenderTool({ name: "render_a2ui", ... })`
2827
+ * hooks automatically override the built-in, since the merge logic in
2828
+ * react-core.ts gives hook-based entries priority over prop-based entries.
2829
+ */
2830
+ function A2UIBuiltInToolCallRenderer() {
2831
+ const { copilotkit } = useCopilotKit();
2832
+ useEffect(() => {
2833
+ const renderer = defineToolCallRenderer({
2834
+ name: RENDER_A2UI_TOOL_NAME,
2835
+ args: z.any(),
2836
+ render: ({ status, args: parameters }) => {
2837
+ if (status === "complete") return /* @__PURE__ */ jsx(Fragment$1, {});
2838
+ const params = parameters;
2839
+ const items = params?.items;
2840
+ if (Array.isArray(items) && items.length > 0) return /* @__PURE__ */ jsx(Fragment$1, {});
2841
+ const components = params?.components;
2842
+ if (Array.isArray(components) && components.length > 2) return /* @__PURE__ */ jsx(Fragment$1, {});
2843
+ return /* @__PURE__ */ jsx(A2UIProgressIndicator, { parameters });
2844
+ }
2845
+ });
2846
+ const existing = copilotkit._renderToolCalls ?? [];
2847
+ copilotkit.setRenderToolCalls([...existing.filter((rc) => rc.name !== RENDER_A2UI_TOOL_NAME), renderer]);
2848
+ }, [copilotkit]);
2849
+ return null;
2850
+ }
2851
+
2852
+ //#endregion
2853
+ //#region src/v2/hooks/use-agent-context.tsx
2854
+ function useAgentContext(context) {
2855
+ const { description, value } = context;
2856
+ const { copilotkit } = useCopilotKit();
2857
+ const stringValue = useMemo(() => {
2858
+ if (typeof value === "string") return value;
2859
+ return JSON.stringify(value);
2860
+ }, [value]);
2861
+ useLayoutEffect(() => {
2862
+ if (!copilotkit) return;
2863
+ const id = copilotkit.addContext({
2864
+ description,
2865
+ value: stringValue
2866
+ });
2867
+ return () => {
2868
+ copilotkit.removeContext(id);
2869
+ };
2870
+ }, [
2871
+ description,
2872
+ stringValue,
2873
+ copilotkit
2874
+ ]);
2875
+ }
2876
+
2877
+ //#endregion
2878
+ //#region src/v2/a2ui/A2UICatalogContext.tsx
2879
+ /**
2880
+ * Renders agent context describing the available A2UI catalog and custom components.
2881
+ * Only mount this component when A2UI is enabled.
2882
+ *
2883
+ * When `includeSchema` is true, the full component schemas (JSON Schema) are also
2884
+ * sent as context using the same description key as the A2UI middleware, so the
2885
+ * middleware can optionally overwrite it with a server-side schema.
2886
+ */
2887
+ function A2UICatalogContext({ catalog, includeSchema }) {
2888
+ useAgentContext({
2889
+ description: "A2UI catalog capabilities: available catalog IDs and custom component definitions the client can render.",
2890
+ value: buildCatalogContextValue(catalog)
2891
+ });
2892
+ const { copilotkit } = useCopilotKit();
2893
+ const schemaValue = useMemo(() => includeSchema !== false ? JSON.stringify(extractCatalogComponentSchemas(catalog)) : null, [catalog, includeSchema]);
2894
+ useLayoutEffect(() => {
2895
+ if (!copilotkit || !schemaValue) return;
2896
+ const ids = [];
2897
+ ids.push(copilotkit.addContext({
2898
+ description: A2UI_SCHEMA_CONTEXT_DESCRIPTION,
2899
+ value: schemaValue
2900
+ }));
2901
+ ids.push(copilotkit.addContext({
2902
+ description: "A2UI generation guidelines — protocol rules, tool arguments, path rules, data model format, and form/two-way-binding instructions.",
2903
+ value: A2UI_DEFAULT_GENERATION_GUIDELINES
2904
+ }));
2905
+ ids.push(copilotkit.addContext({
2906
+ description: "A2UI design guidelines — visual design rules, component hierarchy tips, and action handler patterns.",
2907
+ value: A2UI_DEFAULT_DESIGN_GUIDELINES
2908
+ }));
2909
+ return () => {
2910
+ for (const id of ids) copilotkit.removeContext(id);
2911
+ };
2912
+ }, [copilotkit, schemaValue]);
2913
+ return null;
2005
2914
  }
2006
2915
 
2007
2916
  //#endregion
@@ -2104,6 +3013,17 @@ var CopilotKitCoreReact = class extends CopilotKitCore {
2104
3013
  //#region src/v2/providers/CopilotKitProvider.tsx
2105
3014
  const HEADER_NAME = "X-CopilotCloud-Public-Api-Key";
2106
3015
  const COPILOT_CLOUD_CHAT_URL$1 = "https://api.cloud.copilotkit.ai/copilotkit/v1";
3016
+ const DEFAULT_DESIGN_SKILL = `When generating UI with generateSandboxedUi, follow these design principles inspired by shadcn/ui:
3017
+
3018
+ - Use a minimal, flat aesthetic. Avoid drop shadows and gradients — rely on subtle borders (1px solid, light gray like #e5e7eb) to define surfaces.
3019
+ - Neutral base palette: white backgrounds, zinc/slate gray text (#09090b for headings, #71717a for secondary text). One accent color for interactive elements.
3020
+ - Use system font stacks (system-ui, -apple-system, sans-serif) at readable sizes (14px body, 600 weight for headings). Tight line-heights.
3021
+ - Small, consistent border-radius (6–8px). Cards and containers use border, not shadow, for separation.
3022
+ - Buttons: solid fill for primary (dark bg, white text), outline for secondary (border + transparent bg). Subtle hover state (slight opacity or background shift).
3023
+ - Use CSS Grid or Flexbox for layout. Ensure the UI looks good at any width.
3024
+ - Minimal transitions (150ms) for hover/focus states only. No decorative animations.
3025
+ - Keep the UI focused and dense — avoid excessive padding. Use compact spacing (8–12px gaps, 10–14px padding in controls).`;
3026
+ 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)";
2107
3027
  const CopilotKitContext = createContext({
2108
3028
  copilotkit: null,
2109
3029
  executingToolCallIds: /* @__PURE__ */ new Set()
@@ -2119,9 +3039,11 @@ function useStableArrayProp(prop, warningMessage, isMeaningfulChange) {
2119
3039
  }, [value, warningMessage]);
2120
3040
  return value;
2121
3041
  }
2122
- 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 }) => {
3042
+ 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 }) => {
2123
3043
  const [shouldRenderInspector, setShouldRenderInspector] = useState(false);
2124
3044
  const [runtimeA2UIEnabled, setRuntimeA2UIEnabled] = useState(false);
3045
+ const [runtimeOpenGenUIEnabled, setRuntimeOpenGenUIEnabled] = useState(false);
3046
+ const openGenUIActive = runtimeOpenGenUIEnabled || !!openGenerativeUI;
2125
3047
  const [runtimeLicenseStatus, setRuntimeLicenseStatus] = useState(void 0);
2126
3048
  useEffect(() => {
2127
3049
  if (typeof window === "undefined") return;
@@ -2147,9 +3069,22 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2147
3069
  content: MCPAppsActivityContentSchema,
2148
3070
  render: MCPAppsActivityRenderer
2149
3071
  }];
2150
- if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({ theme: a2ui?.theme ?? viewerTheme }));
3072
+ if (openGenUIActive) renderers.push({
3073
+ activityType: OpenGenerativeUIActivityType,
3074
+ content: OpenGenerativeUIContentSchema,
3075
+ render: OpenGenerativeUIActivityRenderer
3076
+ });
3077
+ if (runtimeA2UIEnabled) renderers.unshift(createA2UIMessageRenderer({
3078
+ theme: a2ui?.theme ?? viewerTheme,
3079
+ catalog: a2ui?.catalog,
3080
+ loadingComponent: a2ui?.loadingComponent
3081
+ }));
2151
3082
  return renderers;
2152
- }, [runtimeA2UIEnabled, a2ui]);
3083
+ }, [
3084
+ runtimeA2UIEnabled,
3085
+ openGenUIActive,
3086
+ a2ui
3087
+ ]);
2153
3088
  const allActivityRenderers = useMemo(() => {
2154
3089
  return [...renderActivityMessagesList, ...builtInActivityRenderers];
2155
3090
  }, [renderActivityMessagesList, builtInActivityRenderers]);
@@ -2175,6 +3110,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2175
3110
  const chatApiEndpoint = runtimeUrl ?? (resolvedPublicKey ? COPILOT_CLOUD_CHAT_URL$1 : void 0);
2176
3111
  const frontendToolsList = useStableArrayProp(frontendTools, "frontendTools must be a stable array. If you want to dynamically add or remove tools, use `useFrontendTool` instead.");
2177
3112
  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.");
3113
+ const sandboxFunctionsList = useStableArrayProp(openGenerativeUI?.sandboxFunctions, "openGenerativeUI.sandboxFunctions must be a stable array.");
2178
3114
  const processedHumanInTheLoopTools = useMemo(() => {
2179
3115
  const processedTools = [];
2180
3116
  const processedRenderToolCalls = [];
@@ -2205,15 +3141,31 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2205
3141
  renderToolCalls: processedRenderToolCalls
2206
3142
  };
2207
3143
  }, [humanInTheLoopList]);
3144
+ const builtInFrontendTools = useMemo(() => {
3145
+ if (!openGenUIActive) return [];
3146
+ return [{
3147
+ name: "generateSandboxedUi",
3148
+ description: GENERATE_SANDBOXED_UI_DESCRIPTION,
3149
+ parameters: GenerateSandboxedUiArgsSchema,
3150
+ handler: async () => "UI generated",
3151
+ followUp: true,
3152
+ render: OpenGenerativeUIToolRenderer
3153
+ }];
3154
+ }, [openGenUIActive]);
2208
3155
  const allTools = useMemo(() => {
2209
3156
  const tools = [];
2210
3157
  tools.push(...frontendToolsList);
3158
+ tools.push(...builtInFrontendTools);
2211
3159
  tools.push(...processedHumanInTheLoopTools.tools);
2212
3160
  return tools;
2213
- }, [frontendToolsList, processedHumanInTheLoopTools]);
3161
+ }, [
3162
+ frontendToolsList,
3163
+ builtInFrontendTools,
3164
+ processedHumanInTheLoopTools
3165
+ ]);
2214
3166
  const allRenderToolCalls = useMemo(() => {
2215
3167
  const combined = [...renderToolCallsList];
2216
- frontendToolsList.forEach((tool) => {
3168
+ [...frontendToolsList, ...builtInFrontendTools].forEach((tool) => {
2217
3169
  if (tool.render) {
2218
3170
  const args = tool.parameters || (tool.name === "*" ? z.any() : void 0);
2219
3171
  if (args) combined.push({
@@ -2228,25 +3180,31 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2228
3180
  }, [
2229
3181
  renderToolCallsList,
2230
3182
  frontendToolsList,
3183
+ builtInFrontendTools,
2231
3184
  processedHumanInTheLoopTools
2232
3185
  ]);
2233
3186
  const copilotkitRef = useRef(null);
2234
- if (copilotkitRef.current === null) copilotkitRef.current = new CopilotKitCoreReact({
2235
- runtimeUrl: chatApiEndpoint,
2236
- runtimeTransport: useSingleEndpoint ? "single" : "rest",
2237
- headers: mergedHeaders,
2238
- credentials,
2239
- properties,
2240
- agents__unsafe_dev_only: mergedAgents,
2241
- tools: allTools,
2242
- renderToolCalls: allRenderToolCalls,
2243
- renderActivityMessages: allActivityRenderers,
2244
- renderCustomMessages: renderCustomMessagesList
2245
- });
3187
+ if (copilotkitRef.current === null) {
3188
+ copilotkitRef.current = new CopilotKitCoreReact({
3189
+ runtimeUrl: chatApiEndpoint,
3190
+ runtimeTransport: useSingleEndpoint === true ? "single" : useSingleEndpoint === false ? "rest" : "auto",
3191
+ headers: mergedHeaders,
3192
+ credentials,
3193
+ properties,
3194
+ agents__unsafe_dev_only: mergedAgents,
3195
+ tools: allTools,
3196
+ renderToolCalls: allRenderToolCalls,
3197
+ renderActivityMessages: allActivityRenderers,
3198
+ renderCustomMessages: renderCustomMessagesList
3199
+ });
3200
+ if (defaultThrottleMs !== void 0) copilotkitRef.current.setDefaultThrottleMs(defaultThrottleMs);
3201
+ }
2246
3202
  const copilotkit = copilotkitRef.current;
2247
3203
  useEffect(() => {
3204
+ setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
2248
3205
  const subscription = copilotkit.subscribe({ onRuntimeConnectionStatusChanged: () => {
2249
3206
  setRuntimeA2UIEnabled(copilotkit.a2uiEnabled);
3207
+ setRuntimeOpenGenUIEnabled(copilotkit.openGenerativeUIEnabled);
2250
3208
  setRuntimeLicenseStatus(copilotkit.licenseStatus);
2251
3209
  } });
2252
3210
  return () => {
@@ -2305,7 +3263,7 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2305
3263
  }, [copilotkit]);
2306
3264
  useEffect(() => {
2307
3265
  copilotkit.setRuntimeUrl(chatApiEndpoint);
2308
- copilotkit.setRuntimeTransport(useSingleEndpoint ? "single" : "rest");
3266
+ copilotkit.setRuntimeTransport(useSingleEndpoint === true ? "single" : useSingleEndpoint === false ? "rest" : "auto");
2309
3267
  copilotkit.setHeaders(mergedHeaders);
2310
3268
  copilotkit.setCredentials(credentials);
2311
3269
  copilotkit.setProperties(properties);
@@ -2339,23 +3297,75 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers = {}, credentials, p
2339
3297
  useEffect(() => {
2340
3298
  didMountRef.current = true;
2341
3299
  }, []);
3300
+ useEffect(() => {
3301
+ 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.`);
3302
+ copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3303
+ }, [copilotkit, defaultThrottleMs]);
3304
+ const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
3305
+ useLayoutEffect(() => {
3306
+ if (!copilotkit || !openGenUIActive) return;
3307
+ const id = copilotkit.addContext({
3308
+ description: "Design guidelines for the generateSandboxedUi tool. Follow these when building UI.",
3309
+ value: designSkill
3310
+ });
3311
+ return () => {
3312
+ copilotkit.removeContext(id);
3313
+ };
3314
+ }, [
3315
+ copilotkit,
3316
+ designSkill,
3317
+ openGenUIActive
3318
+ ]);
3319
+ const sandboxFunctionsDescriptors = useMemo(() => {
3320
+ if (sandboxFunctionsList.length === 0) return null;
3321
+ return JSON.stringify(sandboxFunctionsList.map((fn) => ({
3322
+ name: fn.name,
3323
+ description: fn.description,
3324
+ parameters: schemaToJsonSchema(fn.parameters, { zodToJsonSchema })
3325
+ })));
3326
+ }, [sandboxFunctionsList]);
3327
+ useLayoutEffect(() => {
3328
+ if (!copilotkit || !sandboxFunctionsDescriptors || !openGenUIActive) return;
3329
+ const id = copilotkit.addContext({
3330
+ description: "Sandbox functions available in generated sandboxed UI code. Call via: await Websandbox.connection.remote.<functionName>(args)",
3331
+ value: sandboxFunctionsDescriptors
3332
+ });
3333
+ return () => {
3334
+ copilotkit.removeContext(id);
3335
+ };
3336
+ }, [
3337
+ copilotkit,
3338
+ sandboxFunctionsDescriptors,
3339
+ openGenUIActive
3340
+ ]);
2342
3341
  const contextValue = useMemo(() => ({
2343
3342
  copilotkit,
2344
3343
  executingToolCallIds
2345
3344
  }), [copilotkit, executingToolCallIds]);
2346
3345
  const licenseContextValue = useMemo(() => createLicenseContextValue(null), []);
2347
- return /* @__PURE__ */ jsx(CopilotKitContext.Provider, {
2348
- value: contextValue,
2349
- children: /* @__PURE__ */ jsxs(LicenseContext.Provider, {
2350
- value: licenseContextValue,
2351
- children: [
2352
- children,
2353
- shouldRenderInspector ? /* @__PURE__ */ jsx(CopilotKitInspector, { core: copilotkit }) : null,
2354
- runtimeLicenseStatus === "none" && !resolvedPublicKey && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "no_license" }),
2355
- runtimeLicenseStatus === "expired" && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "expired" }),
2356
- runtimeLicenseStatus === "invalid" && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "invalid" }),
2357
- runtimeLicenseStatus === "expiring" && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "expiring" })
2358
- ]
3346
+ return /* @__PURE__ */ jsx(SandboxFunctionsContext.Provider, {
3347
+ value: sandboxFunctionsList,
3348
+ children: /* @__PURE__ */ jsx(CopilotKitContext.Provider, {
3349
+ value: contextValue,
3350
+ children: /* @__PURE__ */ jsxs(LicenseContext.Provider, {
3351
+ value: licenseContextValue,
3352
+ children: [
3353
+ runtimeA2UIEnabled && /* @__PURE__ */ jsx(A2UIBuiltInToolCallRenderer, {}),
3354
+ runtimeA2UIEnabled && /* @__PURE__ */ jsx(A2UICatalogContext, {
3355
+ catalog: a2ui?.catalog,
3356
+ includeSchema: a2ui?.includeSchema
3357
+ }),
3358
+ children,
3359
+ shouldRenderInspector ? /* @__PURE__ */ jsx(CopilotKitInspector, {
3360
+ core: copilotkit,
3361
+ defaultAnchor: inspectorDefaultAnchor
3362
+ }) : null,
3363
+ runtimeLicenseStatus === "none" && !resolvedPublicKey && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "no_license" }),
3364
+ runtimeLicenseStatus === "expired" && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "expired" }),
3365
+ runtimeLicenseStatus === "invalid" && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "invalid" }),
3366
+ runtimeLicenseStatus === "expiring" && /* @__PURE__ */ jsx(LicenseWarningBanner, { type: "expiring" })
3367
+ ]
3368
+ })
2359
3369
  })
2360
3370
  });
2361
3371
  };
@@ -2370,75 +3380,258 @@ const useCopilotKit = () => {
2370
3380
  return () => {
2371
3381
  subscription.unsubscribe();
2372
3382
  };
2373
- }, []);
2374
- return context;
2375
- };
2376
-
2377
- //#endregion
2378
- //#region src/v2/hooks/use-render-tool-call.tsx
2379
- /**
2380
- * Memoized component that renders a single tool call.
2381
- * This prevents unnecessary re-renders when parent components update
2382
- * but the tool call data hasn't changed.
2383
- */
2384
- const ToolCallRenderer = React.memo(function ToolCallRenderer({ toolCall, toolMessage, RenderComponent, isExecuting }) {
2385
- const args = useMemo(() => partialJSONParse(toolCall.function.arguments), [toolCall.function.arguments]);
2386
- const toolName = toolCall.function.name;
2387
- if (toolMessage) return /* @__PURE__ */ jsx(RenderComponent, {
2388
- name: toolName,
2389
- args,
2390
- status: ToolCallStatus.Complete,
2391
- result: toolMessage.content
2392
- });
2393
- else if (isExecuting) return /* @__PURE__ */ jsx(RenderComponent, {
2394
- name: toolName,
2395
- args,
2396
- status: ToolCallStatus.Executing,
2397
- result: void 0
2398
- });
2399
- else return /* @__PURE__ */ jsx(RenderComponent, {
2400
- name: toolName,
2401
- args,
2402
- status: ToolCallStatus.InProgress,
2403
- result: void 0
2404
- });
2405
- }, (prevProps, nextProps) => {
2406
- if (prevProps.toolCall.id !== nextProps.toolCall.id) return false;
2407
- if (prevProps.toolCall.function.name !== nextProps.toolCall.function.name) return false;
2408
- if (prevProps.toolCall.function.arguments !== nextProps.toolCall.function.arguments) return false;
2409
- if (prevProps.toolMessage?.content !== nextProps.toolMessage?.content) return false;
2410
- if (prevProps.isExecuting !== nextProps.isExecuting) return false;
2411
- if (prevProps.RenderComponent !== nextProps.RenderComponent) return false;
2412
- return true;
2413
- });
2414
- /**
2415
- * Hook that returns a function to render tool calls based on the render functions
2416
- * defined in CopilotKitProvider.
2417
- *
2418
- * @returns A function that takes a tool call and optional tool message and returns the rendered component
2419
- */
2420
- function useRenderToolCall() {
2421
- const { copilotkit, executingToolCallIds } = useCopilotKit();
2422
- const agentId = useCopilotChatConfiguration()?.agentId ?? DEFAULT_AGENT_ID;
2423
- const renderToolCalls = useSyncExternalStore((callback) => {
2424
- return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
2425
- }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
2426
- return useCallback(({ toolCall, toolMessage }) => {
2427
- const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
2428
- const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
2429
- if (!renderConfig) return null;
2430
- const RenderComponent = renderConfig.render;
2431
- return /* @__PURE__ */ jsx(ToolCallRenderer, {
2432
- toolCall,
2433
- toolMessage,
2434
- RenderComponent,
2435
- isExecuting: executingToolCallIds.has(toolCall.id)
2436
- }, toolCall.id);
3383
+ }, []);
3384
+ return context;
3385
+ };
3386
+
3387
+ //#endregion
3388
+ //#region src/v2/hooks/use-render-tool-call.tsx
3389
+ /**
3390
+ * Memoized component that renders a single tool call.
3391
+ * This prevents unnecessary re-renders when parent components update
3392
+ * but the tool call data hasn't changed.
3393
+ */
3394
+ const ToolCallRenderer = React.memo(function ToolCallRenderer({ toolCall, toolMessage, RenderComponent, isExecuting }) {
3395
+ const args = useMemo(() => partialJSONParse(toolCall.function.arguments), [toolCall.function.arguments]);
3396
+ const toolName = toolCall.function.name;
3397
+ if (toolMessage) return /* @__PURE__ */ jsx(RenderComponent, {
3398
+ name: toolName,
3399
+ args,
3400
+ status: ToolCallStatus.Complete,
3401
+ result: toolMessage.content
3402
+ });
3403
+ else if (isExecuting) return /* @__PURE__ */ jsx(RenderComponent, {
3404
+ name: toolName,
3405
+ args,
3406
+ status: ToolCallStatus.Executing,
3407
+ result: void 0
3408
+ });
3409
+ else return /* @__PURE__ */ jsx(RenderComponent, {
3410
+ name: toolName,
3411
+ args,
3412
+ status: ToolCallStatus.InProgress,
3413
+ result: void 0
3414
+ });
3415
+ }, (prevProps, nextProps) => {
3416
+ if (prevProps.toolCall.id !== nextProps.toolCall.id) return false;
3417
+ if (prevProps.toolCall.function.name !== nextProps.toolCall.function.name) return false;
3418
+ if (prevProps.toolCall.function.arguments !== nextProps.toolCall.function.arguments) return false;
3419
+ if (prevProps.toolMessage?.content !== nextProps.toolMessage?.content) return false;
3420
+ if (prevProps.isExecuting !== nextProps.isExecuting) return false;
3421
+ if (prevProps.RenderComponent !== nextProps.RenderComponent) return false;
3422
+ return true;
3423
+ });
3424
+ /**
3425
+ * Hook that returns a function to render tool calls based on the render functions
3426
+ * defined in CopilotKitProvider.
3427
+ *
3428
+ * @returns A function that takes a tool call and optional tool message and returns the rendered component
3429
+ */
3430
+ function useRenderToolCall() {
3431
+ const { copilotkit, executingToolCallIds } = useCopilotKit();
3432
+ const agentId = useCopilotChatConfiguration()?.agentId ?? DEFAULT_AGENT_ID;
3433
+ const renderToolCalls = useSyncExternalStore((callback) => {
3434
+ return copilotkit.subscribe({ onRenderToolCallsChanged: callback }).unsubscribe;
3435
+ }, () => copilotkit.renderToolCalls, () => copilotkit.renderToolCalls);
3436
+ return useCallback(({ toolCall, toolMessage }) => {
3437
+ const exactMatches = renderToolCalls.filter((rc) => rc.name === toolCall.function.name);
3438
+ const renderConfig = exactMatches.find((rc) => rc.agentId === agentId) || exactMatches.find((rc) => !rc.agentId) || exactMatches[0] || renderToolCalls.find((rc) => rc.name === "*");
3439
+ if (!renderConfig) return null;
3440
+ const RenderComponent = renderConfig.render;
3441
+ return /* @__PURE__ */ jsx(ToolCallRenderer, {
3442
+ toolCall,
3443
+ toolMessage,
3444
+ RenderComponent,
3445
+ isExecuting: executingToolCallIds.has(toolCall.id)
3446
+ }, toolCall.id);
3447
+ }, [
3448
+ renderToolCalls,
3449
+ executingToolCallIds,
3450
+ agentId
3451
+ ]);
3452
+ }
3453
+
3454
+ //#endregion
3455
+ //#region src/v2/hooks/use-agent.tsx
3456
+ let UseAgentUpdate = /* @__PURE__ */ function(UseAgentUpdate) {
3457
+ UseAgentUpdate["OnMessagesChanged"] = "OnMessagesChanged";
3458
+ UseAgentUpdate["OnStateChanged"] = "OnStateChanged";
3459
+ UseAgentUpdate["OnRunStatusChanged"] = "OnRunStatusChanged";
3460
+ return UseAgentUpdate;
3461
+ }({});
3462
+ const ALL_UPDATES = [
3463
+ UseAgentUpdate.OnMessagesChanged,
3464
+ UseAgentUpdate.OnStateChanged,
3465
+ UseAgentUpdate.OnRunStatusChanged
3466
+ ];
3467
+ /**
3468
+ * Clone a registry agent for per-thread isolation.
3469
+ * Copies agent configuration (transport, headers, etc.) but resets conversation
3470
+ * state (messages, threadId, state) so each thread starts fresh.
3471
+ */
3472
+ function cloneForThread(source, threadId, headers) {
3473
+ const clone = source.clone();
3474
+ if (clone === source) throw new Error(`useAgent: ${source.constructor.name}.clone() returned the same instance. clone() must return a new, independent object.`);
3475
+ clone.threadId = threadId;
3476
+ clone.setMessages([]);
3477
+ clone.setState({});
3478
+ if (clone instanceof HttpAgent) clone.headers = { ...headers };
3479
+ return clone;
3480
+ }
3481
+ /**
3482
+ * Module-level WeakMap: registryAgent → (threadId → clone).
3483
+ * Shared across all useAgent() calls so that every component using the same
3484
+ * (agentId, threadId) pair receives the same agent instance. Using WeakMap
3485
+ * ensures the clone map is garbage-collected when the registry agent is
3486
+ * replaced (e.g. after reconnect or hot-reload).
3487
+ */
3488
+ const globalThreadCloneMap = /* @__PURE__ */ new WeakMap();
3489
+ /**
3490
+ * Look up an existing per-thread clone without creating one.
3491
+ * Returns undefined when no clone has been created yet for this pair.
3492
+ */
3493
+ function getThreadClone(registryAgent, threadId) {
3494
+ if (!registryAgent || !threadId) return void 0;
3495
+ return globalThreadCloneMap.get(registryAgent)?.get(threadId);
3496
+ }
3497
+ function getOrCreateThreadClone(existing, threadId, headers) {
3498
+ let byThread = globalThreadCloneMap.get(existing);
3499
+ if (!byThread) {
3500
+ byThread = /* @__PURE__ */ new Map();
3501
+ globalThreadCloneMap.set(existing, byThread);
3502
+ }
3503
+ const cached = byThread.get(threadId);
3504
+ if (cached) return cached;
3505
+ const clone = cloneForThread(existing, threadId, headers);
3506
+ byThread.set(threadId, clone);
3507
+ return clone;
3508
+ }
3509
+ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3510
+ agentId ??= DEFAULT_AGENT_ID;
3511
+ const { copilotkit } = useCopilotKit();
3512
+ const providerThrottleMs = copilotkit.defaultThrottleMs;
3513
+ const chatConfig = useCopilotChatConfiguration();
3514
+ threadId ??= chatConfig?.threadId;
3515
+ const effectiveThrottleMs = useMemo(() => {
3516
+ const resolved = throttleMs ?? providerThrottleMs ?? 0;
3517
+ if (!Number.isFinite(resolved) || resolved < 0) {
3518
+ const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3519
+ console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3520
+ return 0;
3521
+ }
3522
+ return resolved;
3523
+ }, [throttleMs, providerThrottleMs]);
3524
+ const [, forceUpdate] = useReducer((x) => x + 1, 0);
3525
+ const updateFlags = useMemo(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3526
+ const provisionalAgentCache = useRef(/* @__PURE__ */ new Map());
3527
+ const agent = useMemo(() => {
3528
+ const cacheKey = threadId ? `${agentId}:${threadId}` : agentId;
3529
+ const existing = copilotkit.getAgent(agentId);
3530
+ if (existing) {
3531
+ provisionalAgentCache.current.delete(cacheKey);
3532
+ provisionalAgentCache.current.delete(agentId);
3533
+ if (!threadId) return existing;
3534
+ return getOrCreateThreadClone(existing, threadId, copilotkit.headers);
3535
+ }
3536
+ const isRuntimeConfigured = copilotkit.runtimeUrl !== void 0;
3537
+ const status = copilotkit.runtimeConnectionStatus;
3538
+ if (isRuntimeConfigured && (status === CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
3539
+ const cached = provisionalAgentCache.current.get(cacheKey);
3540
+ if (cached) {
3541
+ cached.headers = { ...copilotkit.headers };
3542
+ return cached;
3543
+ }
3544
+ const provisional = new ProxiedCopilotRuntimeAgent({
3545
+ runtimeUrl: copilotkit.runtimeUrl,
3546
+ agentId,
3547
+ transport: copilotkit.runtimeTransport,
3548
+ runtimeMode: "pending"
3549
+ });
3550
+ provisional.headers = { ...copilotkit.headers };
3551
+ if (threadId) provisional.threadId = threadId;
3552
+ provisionalAgentCache.current.set(cacheKey, provisional);
3553
+ return provisional;
3554
+ }
3555
+ if (isRuntimeConfigured && status === CopilotKitCoreRuntimeConnectionStatus.Error) {
3556
+ const cached = provisionalAgentCache.current.get(cacheKey);
3557
+ if (cached) {
3558
+ cached.headers = { ...copilotkit.headers };
3559
+ return cached;
3560
+ }
3561
+ const provisional = new ProxiedCopilotRuntimeAgent({
3562
+ runtimeUrl: copilotkit.runtimeUrl,
3563
+ agentId,
3564
+ transport: copilotkit.runtimeTransport,
3565
+ runtimeMode: "pending"
3566
+ });
3567
+ provisional.headers = { ...copilotkit.headers };
3568
+ if (threadId) provisional.threadId = threadId;
3569
+ provisionalAgentCache.current.set(cacheKey, provisional);
3570
+ return provisional;
3571
+ }
3572
+ const knownAgents = Object.keys(copilotkit.agents ?? {});
3573
+ const runtimePart = isRuntimeConfigured ? `runtimeUrl=${copilotkit.runtimeUrl}` : "no runtimeUrl";
3574
+ throw new Error(`useAgent: Agent '${agentId}' not found after runtime sync (${runtimePart}). ` + (knownAgents.length ? `Known agents: [${knownAgents.join(", ")}]` : "No agents registered.") + " Verify your runtime /info and/or agents__unsafe_dev_only.");
3575
+ }, [
3576
+ agentId,
3577
+ threadId,
3578
+ copilotkit.agents,
3579
+ copilotkit.runtimeConnectionStatus,
3580
+ copilotkit.runtimeUrl,
3581
+ copilotkit.runtimeTransport,
3582
+ JSON.stringify(copilotkit.headers)
3583
+ ]);
3584
+ useEffect(() => {
3585
+ if (updateFlags.length === 0) return;
3586
+ const handlers = {};
3587
+ let timerId = null;
3588
+ let active = true;
3589
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3590
+ const ms = effectiveThrottleMs;
3591
+ if (ms > 0) {
3592
+ let throttleActive = false;
3593
+ let pending = false;
3594
+ const throttledNotify = () => {
3595
+ if (!active) return;
3596
+ if (!throttleActive) {
3597
+ throttleActive = true;
3598
+ pending = false;
3599
+ forceUpdate();
3600
+ timerId = setTimeout(function trailingEdge() {
3601
+ timerId = null;
3602
+ if (active && pending) {
3603
+ pending = false;
3604
+ forceUpdate();
3605
+ timerId = setTimeout(trailingEdge, ms);
3606
+ } else throttleActive = false;
3607
+ }, ms);
3608
+ } else pending = true;
3609
+ };
3610
+ handlers.onMessagesChanged = throttledNotify;
3611
+ } else handlers.onMessagesChanged = forceUpdate;
3612
+ }
3613
+ if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = forceUpdate;
3614
+ if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3615
+ handlers.onRunInitialized = forceUpdate;
3616
+ handlers.onRunFinalized = forceUpdate;
3617
+ handlers.onRunFailed = forceUpdate;
3618
+ }
3619
+ const subscription = agent.subscribe(handlers);
3620
+ return () => {
3621
+ active = false;
3622
+ if (timerId !== null) clearTimeout(timerId);
3623
+ subscription.unsubscribe();
3624
+ };
2437
3625
  }, [
2438
- renderToolCalls,
2439
- executingToolCallIds,
2440
- agentId
3626
+ agent,
3627
+ forceUpdate,
3628
+ effectiveThrottleMs,
3629
+ updateFlags
2441
3630
  ]);
3631
+ useEffect(() => {
3632
+ if (agent instanceof HttpAgent) agent.headers = { ...copilotkit.headers };
3633
+ }, [agent, JSON.stringify(copilotkit.headers)]);
3634
+ return { agent };
2442
3635
  }
2443
3636
 
2444
3637
  //#endregion
@@ -2458,7 +3651,8 @@ function useRenderCustomMessages() {
2458
3651
  const { message, position } = params;
2459
3652
  const resolvedRunId = copilotkit.getRunIdForMessage(agentId, threadId, message.id) ?? copilotkit.getRunIdsForThread(agentId, threadId).slice(-1)[0];
2460
3653
  const runId = resolvedRunId ?? `missing-run-id:${message.id}`;
2461
- const agent = copilotkit.getAgent(agentId);
3654
+ const registryAgent = copilotkit.getAgent(agentId);
3655
+ const agent = getThreadClone(registryAgent, threadId) ?? registryAgent;
2462
3656
  if (!agent) throw new Error("Agent not found");
2463
3657
  const messagesIdsInRun = resolvedRunId ? agent.messages.filter((msg) => copilotkit.getRunIdForMessage(agentId, threadId, msg.id) === resolvedRunId).map((msg) => msg.id) : [message.id];
2464
3658
  const rawMessageIndex = agent.messages.findIndex((msg) => msg.id === message.id);
@@ -2490,7 +3684,8 @@ function useRenderCustomMessages() {
2490
3684
  //#region src/v2/hooks/use-render-activity-message.tsx
2491
3685
  function useRenderActivityMessage() {
2492
3686
  const { copilotkit } = useCopilotKit();
2493
- const agentId = useCopilotChatConfiguration()?.agentId ?? DEFAULT_AGENT_ID;
3687
+ const config = useCopilotChatConfiguration();
3688
+ const agentId = config?.agentId ?? DEFAULT_AGENT_ID;
2494
3689
  const renderers = copilotkit.renderActivityMessages;
2495
3690
  const findRenderer = useCallback((activityType) => {
2496
3691
  if (!renderers.length) return null;
@@ -2506,7 +3701,8 @@ function useRenderActivityMessage() {
2506
3701
  return null;
2507
3702
  }
2508
3703
  const Component = renderer.render;
2509
- const agent = copilotkit.getAgent(agentId);
3704
+ const registryAgent = copilotkit.getAgent(agentId);
3705
+ const agent = getThreadClone(registryAgent, config?.threadId) ?? registryAgent;
2510
3706
  return /* @__PURE__ */ jsx(Component, {
2511
3707
  activityType: message.activityType,
2512
3708
  content: parseResult.data,
@@ -2515,6 +3711,7 @@ function useRenderActivityMessage() {
2515
3711
  }, message.id);
2516
3712
  }, [
2517
3713
  agentId,
3714
+ config?.threadId,
2518
3715
  copilotkit,
2519
3716
  findRenderer
2520
3717
  ]);
@@ -2540,7 +3737,7 @@ function useFrontendTool(tool, deps) {
2540
3737
  copilotkit.removeTool(name, tool.agentId);
2541
3738
  }
2542
3739
  copilotkit.addTool(tool);
2543
- if (tool.render && tool.parameters) copilotkit.addHookRenderToolCall({
3740
+ if (tool.render) copilotkit.addHookRenderToolCall({
2544
3741
  name,
2545
3742
  args: tool.parameters,
2546
3743
  agentId: tool.agentId,
@@ -2625,18 +3822,6 @@ function useComponent(config, deps) {
2625
3822
  }, deps);
2626
3823
  }
2627
3824
 
2628
- //#endregion
2629
- //#region src/v2/types/defineToolCallRenderer.ts
2630
- function defineToolCallRenderer(def) {
2631
- const argsSchema = def.name === "*" && !def.args ? z.any() : def.args;
2632
- return {
2633
- name: def.name,
2634
- args: argsSchema,
2635
- render: def.render,
2636
- ...def.agentId ? { agentId: def.agentId } : {}
2637
- };
2638
- }
2639
-
2640
3825
  //#endregion
2641
3826
  //#region src/v2/hooks/use-render-tool.tsx
2642
3827
  const EMPTY_DEPS = [];
@@ -2874,206 +4059,95 @@ function DefaultToolCallRenderer({ name, parameters, status, result }) {
2874
4059
  })] }), result !== void 0 && /* @__PURE__ */ jsxs("div", { children: [/* @__PURE__ */ jsx("div", {
2875
4060
  style: {
2876
4061
  fontSize: "10px",
2877
- textTransform: "uppercase",
2878
- letterSpacing: "0.05em",
2879
- color: "#71717a"
2880
- },
2881
- children: "Result"
2882
- }), /* @__PURE__ */ jsx("pre", {
2883
- style: {
2884
- marginTop: "6px",
2885
- maxHeight: "200px",
2886
- overflow: "auto",
2887
- borderRadius: "6px",
2888
- backgroundColor: "#f4f4f5",
2889
- padding: "10px",
2890
- fontSize: "11px",
2891
- lineHeight: 1.6,
2892
- color: "#27272a",
2893
- whiteSpace: "pre-wrap",
2894
- wordBreak: "break-word"
2895
- },
2896
- children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
2897
- })] })]
2898
- })]
2899
- })
2900
- });
2901
- }
2902
-
2903
- //#endregion
2904
- //#region src/v2/hooks/use-human-in-the-loop.tsx
2905
- function useHumanInTheLoop(tool, deps) {
2906
- const { copilotkit } = useCopilotKit();
2907
- const resolvePromiseRef = useRef(null);
2908
- const respond = useCallback(async (result) => {
2909
- if (resolvePromiseRef.current) {
2910
- resolvePromiseRef.current(result);
2911
- resolvePromiseRef.current = null;
2912
- }
2913
- }, []);
2914
- const handler = useCallback(async () => {
2915
- return new Promise((resolve) => {
2916
- resolvePromiseRef.current = resolve;
2917
- });
2918
- }, []);
2919
- const RenderComponent = useCallback((props) => {
2920
- const ToolComponent = tool.render;
2921
- if (props.status === "inProgress") {
2922
- const enhancedProps = {
2923
- ...props,
2924
- name: tool.name,
2925
- description: tool.description || "",
2926
- respond: void 0
2927
- };
2928
- return React.createElement(ToolComponent, enhancedProps);
2929
- } else if (props.status === "executing") {
2930
- const enhancedProps = {
2931
- ...props,
2932
- name: tool.name,
2933
- description: tool.description || "",
2934
- respond
2935
- };
2936
- return React.createElement(ToolComponent, enhancedProps);
2937
- } else if (props.status === "complete") {
2938
- const enhancedProps = {
2939
- ...props,
2940
- name: tool.name,
2941
- description: tool.description || "",
2942
- respond: void 0
2943
- };
2944
- return React.createElement(ToolComponent, enhancedProps);
2945
- }
2946
- return React.createElement(ToolComponent, props);
2947
- }, [
2948
- tool.render,
2949
- tool.name,
2950
- tool.description,
2951
- respond
2952
- ]);
2953
- useFrontendTool({
2954
- ...tool,
2955
- handler,
2956
- render: RenderComponent
2957
- }, deps);
2958
- useEffect(() => {
2959
- return () => {
2960
- copilotkit.removeHookRenderToolCall(tool.name, tool.agentId);
2961
- };
2962
- }, [
2963
- copilotkit,
2964
- tool.name,
2965
- tool.agentId
2966
- ]);
2967
- }
2968
-
2969
- //#endregion
2970
- //#region src/v2/hooks/use-agent.tsx
2971
- let UseAgentUpdate = /* @__PURE__ */ function(UseAgentUpdate) {
2972
- UseAgentUpdate["OnMessagesChanged"] = "OnMessagesChanged";
2973
- UseAgentUpdate["OnStateChanged"] = "OnStateChanged";
2974
- UseAgentUpdate["OnRunStatusChanged"] = "OnRunStatusChanged";
2975
- return UseAgentUpdate;
2976
- }({});
2977
- const ALL_UPDATES = [
2978
- UseAgentUpdate.OnMessagesChanged,
2979
- UseAgentUpdate.OnStateChanged,
2980
- UseAgentUpdate.OnRunStatusChanged
2981
- ];
2982
- function useAgent({ agentId, updates } = {}) {
2983
- agentId ??= DEFAULT_AGENT_ID;
2984
- const { copilotkit } = useCopilotKit();
2985
- const [, forceUpdate] = useReducer((x) => x + 1, 0);
2986
- const updateFlags = useMemo(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
2987
- const provisionalAgentCache = useRef(/* @__PURE__ */ new Map());
2988
- const agent = useMemo(() => {
2989
- const existing = copilotkit.getAgent(agentId);
2990
- if (existing) {
2991
- provisionalAgentCache.current.delete(agentId);
2992
- return existing;
2993
- }
2994
- const isRuntimeConfigured = copilotkit.runtimeUrl !== void 0;
2995
- const status = copilotkit.runtimeConnectionStatus;
2996
- if (isRuntimeConfigured && (status === CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
2997
- const cached = provisionalAgentCache.current.get(agentId);
2998
- if (cached) {
2999
- cached.headers = { ...copilotkit.headers };
3000
- return cached;
3001
- }
3002
- const provisional = new ProxiedCopilotRuntimeAgent({
3003
- runtimeUrl: copilotkit.runtimeUrl,
3004
- agentId,
3005
- transport: copilotkit.runtimeTransport,
3006
- runtimeMode: "pending"
3007
- });
3008
- provisional.headers = { ...copilotkit.headers };
3009
- provisionalAgentCache.current.set(agentId, provisional);
3010
- return provisional;
3011
- }
3012
- if (isRuntimeConfigured && status === CopilotKitCoreRuntimeConnectionStatus.Error) {
3013
- const provisional = new ProxiedCopilotRuntimeAgent({
3014
- runtimeUrl: copilotkit.runtimeUrl,
3015
- agentId,
3016
- transport: copilotkit.runtimeTransport,
3017
- runtimeMode: "pending"
3018
- });
3019
- provisional.headers = { ...copilotkit.headers };
3020
- return provisional;
3021
- }
3022
- const knownAgents = Object.keys(copilotkit.agents ?? {});
3023
- const runtimePart = isRuntimeConfigured ? `runtimeUrl=${copilotkit.runtimeUrl}` : "no runtimeUrl";
3024
- throw new Error(`useAgent: Agent '${agentId}' not found after runtime sync (${runtimePart}). ` + (knownAgents.length ? `Known agents: [${knownAgents.join(", ")}]` : "No agents registered.") + " Verify your runtime /info and/or agents__unsafe_dev_only.");
3025
- }, [
3026
- agentId,
3027
- copilotkit.agents,
3028
- copilotkit.runtimeConnectionStatus,
3029
- copilotkit.runtimeUrl,
3030
- copilotkit.runtimeTransport,
3031
- JSON.stringify(copilotkit.headers)
3032
- ]);
3033
- useEffect(() => {
3034
- if (updateFlags.length === 0) return;
3035
- const handlers = {};
3036
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = () => {
3037
- forceUpdate();
3038
- };
3039
- if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = forceUpdate;
3040
- if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3041
- handlers.onRunInitialized = forceUpdate;
3042
- handlers.onRunFinalized = forceUpdate;
3043
- handlers.onRunFailed = forceUpdate;
3044
- }
3045
- const subscription = agent.subscribe(handlers);
3046
- return () => subscription.unsubscribe();
3047
- }, [
3048
- agent,
3049
- forceUpdate,
3050
- JSON.stringify(updateFlags)
3051
- ]);
3052
- return { agent };
4062
+ textTransform: "uppercase",
4063
+ letterSpacing: "0.05em",
4064
+ color: "#71717a"
4065
+ },
4066
+ children: "Result"
4067
+ }), /* @__PURE__ */ jsx("pre", {
4068
+ style: {
4069
+ marginTop: "6px",
4070
+ maxHeight: "200px",
4071
+ overflow: "auto",
4072
+ borderRadius: "6px",
4073
+ backgroundColor: "#f4f4f5",
4074
+ padding: "10px",
4075
+ fontSize: "11px",
4076
+ lineHeight: 1.6,
4077
+ color: "#27272a",
4078
+ whiteSpace: "pre-wrap",
4079
+ wordBreak: "break-word"
4080
+ },
4081
+ children: typeof result === "string" ? result : JSON.stringify(result, null, 2)
4082
+ })] })]
4083
+ })]
4084
+ })
4085
+ });
3053
4086
  }
3054
4087
 
3055
4088
  //#endregion
3056
- //#region src/v2/hooks/use-agent-context.tsx
3057
- function useAgentContext(context) {
3058
- const { description, value } = context;
4089
+ //#region src/v2/hooks/use-human-in-the-loop.tsx
4090
+ function useHumanInTheLoop(tool, deps) {
3059
4091
  const { copilotkit } = useCopilotKit();
3060
- const stringValue = useMemo(() => {
3061
- if (typeof value === "string") return value;
3062
- return JSON.stringify(value);
3063
- }, [value]);
3064
- useLayoutEffect(() => {
3065
- if (!copilotkit) return;
3066
- const id = copilotkit.addContext({
3067
- description,
3068
- value: stringValue
4092
+ const resolvePromiseRef = useRef(null);
4093
+ const respond = useCallback(async (result) => {
4094
+ if (resolvePromiseRef.current) {
4095
+ resolvePromiseRef.current(result);
4096
+ resolvePromiseRef.current = null;
4097
+ }
4098
+ }, []);
4099
+ const handler = useCallback(async () => {
4100
+ return new Promise((resolve) => {
4101
+ resolvePromiseRef.current = resolve;
3069
4102
  });
4103
+ }, []);
4104
+ const RenderComponent = useCallback((props) => {
4105
+ const ToolComponent = tool.render;
4106
+ if (props.status === "inProgress") {
4107
+ const enhancedProps = {
4108
+ ...props,
4109
+ name: tool.name,
4110
+ description: tool.description || "",
4111
+ respond: void 0
4112
+ };
4113
+ return React.createElement(ToolComponent, enhancedProps);
4114
+ } else if (props.status === "executing") {
4115
+ const enhancedProps = {
4116
+ ...props,
4117
+ name: tool.name,
4118
+ description: tool.description || "",
4119
+ respond
4120
+ };
4121
+ return React.createElement(ToolComponent, enhancedProps);
4122
+ } else if (props.status === "complete") {
4123
+ const enhancedProps = {
4124
+ ...props,
4125
+ name: tool.name,
4126
+ description: tool.description || "",
4127
+ respond: void 0
4128
+ };
4129
+ return React.createElement(ToolComponent, enhancedProps);
4130
+ }
4131
+ return React.createElement(ToolComponent, props);
4132
+ }, [
4133
+ tool.render,
4134
+ tool.name,
4135
+ tool.description,
4136
+ respond
4137
+ ]);
4138
+ useFrontendTool({
4139
+ ...tool,
4140
+ handler,
4141
+ render: RenderComponent
4142
+ }, deps);
4143
+ useEffect(() => {
3070
4144
  return () => {
3071
- copilotkit.removeContext(id);
4145
+ copilotkit.removeHookRenderToolCall(tool.name, tool.agentId);
3072
4146
  };
3073
4147
  }, [
3074
- description,
3075
- stringValue,
3076
- copilotkit
4148
+ copilotkit,
4149
+ tool.name,
4150
+ tool.agentId
3077
4151
  ]);
3078
4152
  }
3079
4153
 
@@ -3486,11 +4560,19 @@ function useThreadStoreSelector(store, selector) {
3486
4560
  function useThreads$1({ agentId, includeArchived, limit }) {
3487
4561
  const { copilotkit } = useCopilotKit();
3488
4562
  const [store] = useState(() => ɵcreateThreadStore({ fetch: globalThis.fetch }));
3489
- const threads = useThreadStoreSelector(store, ɵselectThreads);
4563
+ const coreThreads = useThreadStoreSelector(store, ɵselectThreads);
4564
+ const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4565
+ id,
4566
+ agentId,
4567
+ name,
4568
+ archived,
4569
+ createdAt,
4570
+ updatedAt
4571
+ })), [coreThreads]);
3490
4572
  const storeIsLoading = useThreadStoreSelector(store, ɵselectThreadsIsLoading);
3491
4573
  const storeError = useThreadStoreSelector(store, ɵselectThreadsError);
3492
- const hasNextPage = useThreadStoreSelector(store, ɵselectHasNextPage);
3493
- const isFetchingNextPage = useThreadStoreSelector(store, ɵselectIsFetchingNextPage);
4574
+ const hasMoreThreads = useThreadStoreSelector(store, ɵselectHasNextPage);
4575
+ const isFetchingMoreThreads = useThreadStoreSelector(store, ɵselectIsFetchingNextPage);
3494
4576
  const headersKey = useMemo(() => {
3495
4577
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
3496
4578
  }, [copilotkit.headers]);
@@ -3533,15 +4615,173 @@ function useThreads$1({ agentId, includeArchived, limit }) {
3533
4615
  threads,
3534
4616
  isLoading,
3535
4617
  error,
3536
- hasNextPage,
3537
- isFetchingNextPage,
3538
- fetchNextPage: useCallback(() => store.fetchNextPage(), [store]),
4618
+ hasMoreThreads,
4619
+ isFetchingMoreThreads,
4620
+ fetchMoreThreads: useCallback(() => store.fetchNextPage(), [store]),
3539
4621
  renameThread,
3540
4622
  archiveThread,
3541
4623
  deleteThread
3542
4624
  };
3543
4625
  }
3544
4626
 
4627
+ //#endregion
4628
+ //#region src/v2/hooks/use-attachments.tsx
4629
+ /**
4630
+ * Hook that manages file attachment state — uploads, drag-and-drop, paste,
4631
+ * and lifecycle. All returned callbacks are referentially stable across
4632
+ * renders (via useCallback) to avoid destabilizing downstream memoization.
4633
+ */
4634
+ function useAttachments({ config }) {
4635
+ const enabled = config?.enabled ?? false;
4636
+ const [attachments, setAttachments] = useState([]);
4637
+ const [dragOver, setDragOver] = useState(false);
4638
+ const fileInputRef = useRef(null);
4639
+ const containerRef = useRef(null);
4640
+ const configRef = useRef(config);
4641
+ configRef.current = config;
4642
+ const attachmentsRef = useRef([]);
4643
+ attachmentsRef.current = attachments;
4644
+ const processFiles = useCallback(async (files) => {
4645
+ const cfg = configRef.current;
4646
+ const accept = cfg?.accept ?? "*/*";
4647
+ const maxSize = cfg?.maxSize ?? 20 * 1024 * 1024;
4648
+ const rejectedFiles = files.filter((file) => !matchesAcceptFilter(file, accept));
4649
+ for (const file of rejectedFiles) cfg?.onUploadFailed?.({
4650
+ reason: "invalid-type",
4651
+ file,
4652
+ message: `File "${file.name}" is not accepted. Supported types: ${accept}`
4653
+ });
4654
+ const validFiles = files.filter((file) => matchesAcceptFilter(file, accept));
4655
+ for (const file of validFiles) {
4656
+ if (exceedsMaxSize(file, maxSize)) {
4657
+ cfg?.onUploadFailed?.({
4658
+ reason: "file-too-large",
4659
+ file,
4660
+ message: `File "${file.name}" exceeds the maximum size of ${formatFileSize(maxSize)}`
4661
+ });
4662
+ continue;
4663
+ }
4664
+ const modality = getModalityFromMimeType(file.type);
4665
+ const placeholderId = randomUUID();
4666
+ const placeholder = {
4667
+ id: placeholderId,
4668
+ type: modality,
4669
+ source: {
4670
+ type: "data",
4671
+ value: "",
4672
+ mimeType: file.type
4673
+ },
4674
+ filename: file.name,
4675
+ size: file.size,
4676
+ status: "uploading"
4677
+ };
4678
+ setAttachments((prev) => [...prev, placeholder]);
4679
+ try {
4680
+ let source;
4681
+ let uploadMetadata;
4682
+ if (cfg?.onUpload) {
4683
+ const { metadata: meta, ...uploadSource } = await cfg.onUpload(file);
4684
+ source = uploadSource;
4685
+ uploadMetadata = meta;
4686
+ } else source = {
4687
+ type: "data",
4688
+ value: await readFileAsBase64(file),
4689
+ mimeType: file.type
4690
+ };
4691
+ let thumbnail;
4692
+ if (modality === "video") thumbnail = await generateVideoThumbnail(file);
4693
+ setAttachments((prev) => prev.map((att) => att.id === placeholderId ? {
4694
+ ...att,
4695
+ source,
4696
+ status: "ready",
4697
+ thumbnail,
4698
+ metadata: uploadMetadata
4699
+ } : att));
4700
+ } catch (error) {
4701
+ setAttachments((prev) => prev.filter((att) => att.id !== placeholderId));
4702
+ console.error(`[CopilotKit] Failed to upload "${file.name}":`, error);
4703
+ cfg?.onUploadFailed?.({
4704
+ reason: "upload-failed",
4705
+ file,
4706
+ message: error instanceof Error ? error.message : `Failed to upload "${file.name}"`
4707
+ });
4708
+ }
4709
+ }
4710
+ }, []);
4711
+ const handleFileUpload = useCallback(async (e) => {
4712
+ if (!e.target.files?.length) return;
4713
+ try {
4714
+ await processFiles(Array.from(e.target.files));
4715
+ } catch (error) {
4716
+ console.error("[CopilotKit] Upload error:", error);
4717
+ }
4718
+ }, [processFiles]);
4719
+ const handleDragOver = useCallback((e) => {
4720
+ if (!configRef.current?.enabled) return;
4721
+ e.preventDefault();
4722
+ e.stopPropagation();
4723
+ setDragOver(true);
4724
+ }, []);
4725
+ const handleDragLeave = useCallback((e) => {
4726
+ e.preventDefault();
4727
+ e.stopPropagation();
4728
+ setDragOver(false);
4729
+ }, []);
4730
+ const handleDrop = useCallback(async (e) => {
4731
+ e.preventDefault();
4732
+ e.stopPropagation();
4733
+ setDragOver(false);
4734
+ if (!configRef.current?.enabled) return;
4735
+ const files = Array.from(e.dataTransfer.files);
4736
+ if (files.length > 0) try {
4737
+ await processFiles(files);
4738
+ } catch (error) {
4739
+ console.error("[CopilotKit] Drop error:", error);
4740
+ }
4741
+ }, [processFiles]);
4742
+ useEffect(() => {
4743
+ if (!enabled) return;
4744
+ const handlePaste = async (e) => {
4745
+ const target = e.target;
4746
+ if (!target || !containerRef.current?.contains(target)) return;
4747
+ const accept = configRef.current?.accept ?? "*/*";
4748
+ const fileItems = Array.from(e.clipboardData?.items || []).filter((item) => item.kind === "file" && item.getAsFile() !== null && matchesAcceptFilter(item.getAsFile(), accept));
4749
+ if (fileItems.length === 0) return;
4750
+ e.preventDefault();
4751
+ const files = fileItems.map((item) => item.getAsFile()).filter((f) => f !== null);
4752
+ try {
4753
+ await processFiles(files);
4754
+ } catch (error) {
4755
+ console.error("[CopilotKit] Paste error:", error);
4756
+ }
4757
+ };
4758
+ document.addEventListener("paste", handlePaste);
4759
+ return () => document.removeEventListener("paste", handlePaste);
4760
+ }, [enabled, processFiles]);
4761
+ return {
4762
+ attachments,
4763
+ enabled,
4764
+ dragOver,
4765
+ fileInputRef,
4766
+ containerRef,
4767
+ processFiles,
4768
+ handleFileUpload,
4769
+ handleDragOver,
4770
+ handleDragLeave,
4771
+ handleDrop,
4772
+ removeAttachment: useCallback((id) => {
4773
+ setAttachments((prev) => prev.filter((a) => a.id !== id));
4774
+ }, []),
4775
+ consumeAttachments: useCallback(() => {
4776
+ const ready = attachmentsRef.current.filter((a) => a.status === "ready");
4777
+ if (ready.length === 0) return ready;
4778
+ setAttachments((prev) => prev.filter((a) => a.status !== "ready"));
4779
+ if (fileInputRef.current) fileInputRef.current.value = "";
4780
+ return ready;
4781
+ }, [])
4782
+ };
4783
+ }
4784
+
3545
4785
  //#endregion
3546
4786
  //#region src/v2/components/chat/CopilotChatToolCallsView.tsx
3547
4787
  function CopilotChatToolCallsView({ message, messages = [] }) {
@@ -3658,9 +4898,19 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, onThumbsUp,
3658
4898
  _CopilotChatAssistantMessage.CopyButton = ({ className, title, onClick, ...props }) => {
3659
4899
  const labels = useCopilotChatConfiguration()?.labels ?? CopilotChatDefaultLabels;
3660
4900
  const [copied, setCopied] = useState(false);
4901
+ const timerRef = useRef(null);
4902
+ useEffect(() => {
4903
+ return () => {
4904
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
4905
+ };
4906
+ }, []);
3661
4907
  const handleClick = (event) => {
3662
4908
  setCopied(true);
3663
- setTimeout(() => setCopied(false), 2e3);
4909
+ if (timerRef.current !== null) clearTimeout(timerRef.current);
4910
+ timerRef.current = setTimeout(() => {
4911
+ timerRef.current = null;
4912
+ setCopied(false);
4913
+ }, 2e3);
3664
4914
  if (onClick) onClick(event);
3665
4915
  };
3666
4916
  return /* @__PURE__ */ jsx(ToolbarButton, {
@@ -3718,6 +4968,79 @@ CopilotChatAssistantMessage.ReadAloudButton.displayName = "CopilotChatAssistantM
3718
4968
  CopilotChatAssistantMessage.RegenerateButton.displayName = "CopilotChatAssistantMessage.RegenerateButton";
3719
4969
  var CopilotChatAssistantMessage_default = CopilotChatAssistantMessage;
3720
4970
 
4971
+ //#endregion
4972
+ //#region src/v2/components/chat/CopilotChatAttachmentRenderer.tsx
4973
+ const ImageAttachment = memo(function ImageAttachment({ src, className }) {
4974
+ const [error, setError] = useState(false);
4975
+ if (error) return /* @__PURE__ */ jsx("div", {
4976
+ 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),
4977
+ children: /* @__PURE__ */ jsx("span", { children: "Failed to load image" })
4978
+ });
4979
+ return /* @__PURE__ */ jsx("img", {
4980
+ src,
4981
+ alt: "Image attachment",
4982
+ className: cn("cpk:max-w-full cpk:h-auto cpk:rounded-lg", className),
4983
+ onError: () => setError(true)
4984
+ });
4985
+ });
4986
+ const AudioAttachment = memo(function AudioAttachment({ src, filename, className }) {
4987
+ return /* @__PURE__ */ jsxs("div", {
4988
+ className: cn("cpk:flex cpk:flex-col cpk:gap-1", className),
4989
+ children: [/* @__PURE__ */ jsx("audio", {
4990
+ src,
4991
+ controls: true,
4992
+ preload: "metadata",
4993
+ className: "cpk:max-w-[300px] cpk:w-full cpk:h-10"
4994
+ }), filename && /* @__PURE__ */ jsx("span", {
4995
+ className: "cpk:text-xs cpk:text-muted-foreground cpk:truncate cpk:max-w-[300px]",
4996
+ children: filename
4997
+ })]
4998
+ });
4999
+ });
5000
+ const VideoAttachment = memo(function VideoAttachment({ src, className }) {
5001
+ return /* @__PURE__ */ jsx("video", {
5002
+ src,
5003
+ controls: true,
5004
+ preload: "metadata",
5005
+ className: cn("cpk:max-w-[400px] cpk:w-full cpk:rounded-lg", className)
5006
+ });
5007
+ });
5008
+ const DocumentAttachment = memo(function DocumentAttachment({ source, filename, className }) {
5009
+ return /* @__PURE__ */ jsxs("div", {
5010
+ 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),
5011
+ children: [/* @__PURE__ */ jsx("span", {
5012
+ className: "cpk:text-xs cpk:font-bold cpk:uppercase",
5013
+ children: getDocumentIcon(source.mimeType ?? "")
5014
+ }), /* @__PURE__ */ jsx("span", {
5015
+ className: "cpk:text-sm cpk:text-muted-foreground cpk:truncate",
5016
+ children: filename || source.mimeType || "Unknown type"
5017
+ })]
5018
+ });
5019
+ });
5020
+ const CopilotChatAttachmentRenderer = ({ type, source, filename, className }) => {
5021
+ const src = getSourceUrl(source);
5022
+ switch (type) {
5023
+ case "image": return /* @__PURE__ */ jsx(ImageAttachment, {
5024
+ src,
5025
+ className
5026
+ });
5027
+ case "audio": return /* @__PURE__ */ jsx(AudioAttachment, {
5028
+ src,
5029
+ filename,
5030
+ className
5031
+ });
5032
+ case "video": return /* @__PURE__ */ jsx(VideoAttachment, {
5033
+ src,
5034
+ className
5035
+ });
5036
+ case "document": return /* @__PURE__ */ jsx(DocumentAttachment, {
5037
+ source,
5038
+ filename,
5039
+ className
5040
+ });
5041
+ }
5042
+ };
5043
+
3721
5044
  //#endregion
3722
5045
  //#region src/v2/components/chat/CopilotChatUserMessage.tsx
3723
5046
  function flattenUserMessageContent(content) {
@@ -3728,8 +5051,17 @@ function flattenUserMessageContent(content) {
3728
5051
  return "";
3729
5052
  }).filter((text) => text.length > 0).join("\n");
3730
5053
  }
5054
+ function getMediaParts(content) {
5055
+ if (!content || typeof content === "string") return [];
5056
+ return content.filter((part) => part.type === "image" || part.type === "audio" || part.type === "video" || part.type === "document");
5057
+ }
5058
+ function getFilename(part) {
5059
+ const meta = part.metadata;
5060
+ if (meta != null && typeof meta === "object" && "filename" in meta && typeof meta.filename === "string") return meta.filename;
5061
+ }
3731
5062
  function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfBranches, onSwitchToBranch, additionalToolbarItems, messageRenderer, toolbar, copyButton, editButton, branchNavigation, children, className, ...props }) {
3732
5063
  const flattenedContent = useMemo(() => flattenUserMessageContent(message.content), [message.content]);
5064
+ const mediaParts = useMemo(() => getMediaParts(message.content), [message.content]);
3733
5065
  const BoundMessageRenderer = renderSlot(messageRenderer, CopilotChatUserMessage.MessageRenderer, { content: flattenedContent });
3734
5066
  const BoundCopyButton = renderSlot(copyButton, CopilotChatUserMessage.CopyButton, { onClick: async () => {
3735
5067
  if (flattenedContent) try {
@@ -3776,7 +5108,18 @@ function CopilotChatUserMessage({ message, onEditMessage, branchIndex, numberOfB
3776
5108
  className: twMerge("copilotKitMessage copilotKitUserMessage cpk:flex cpk:flex-col cpk:items-end cpk:group cpk:pt-10", className),
3777
5109
  "data-message-id": message.id,
3778
5110
  ...props,
3779
- children: [BoundMessageRenderer, BoundToolbar]
5111
+ children: [
5112
+ BoundMessageRenderer,
5113
+ mediaParts.length > 0 && /* @__PURE__ */ jsx("div", {
5114
+ className: "cpk:flex cpk:flex-col cpk:items-end cpk:gap-2 cpk:mt-2",
5115
+ children: mediaParts.map((part, index) => /* @__PURE__ */ jsx(CopilotChatAttachmentRenderer, {
5116
+ type: part.type,
5117
+ source: part.source,
5118
+ filename: getFilename(part)
5119
+ }, index))
5120
+ }),
5121
+ BoundToolbar
5122
+ ]
3780
5123
  });
3781
5124
  }
3782
5125
  (function(_CopilotChatUserMessage) {
@@ -4074,6 +5417,7 @@ const CopilotChatSuggestionView = React.forwardRef(function CopilotChatSuggestio
4074
5417
  const isLoading = loadingSet.has(index) || suggestion.isLoading === true;
4075
5418
  const pill = renderSlot(suggestionSlot, CopilotChatSuggestionPill, {
4076
5419
  children: suggestion.title,
5420
+ className: suggestion.className,
4077
5421
  isLoading,
4078
5422
  type: "button",
4079
5423
  onClick: () => onSelectSuggestion?.(suggestion, index)
@@ -4110,9 +5454,43 @@ const CopilotChatSuggestionView = React.forwardRef(function CopilotChatSuggestio
4110
5454
  });
4111
5455
  CopilotChatSuggestionView.displayName = "CopilotChatSuggestionView";
4112
5456
 
5457
+ //#endregion
5458
+ //#region src/v2/components/chat/scroll-element-context.ts
5459
+ /**
5460
+ * Provides the scroll container element to child components that need it for
5461
+ * virtualization. Set by CopilotChatView.ScrollView; consumed by
5462
+ * CopilotChatMessageView to feed useVirtualizer's getScrollElement.
5463
+ *
5464
+ * Carries the element itself (not a ref) so that context consumers re-render
5465
+ * reactively when the scroll container is first mounted.
5466
+ */
5467
+ const ScrollElementContext = React.createContext(null);
5468
+
4113
5469
  //#endregion
4114
5470
  //#region src/v2/components/chat/CopilotChatMessageView.tsx
4115
5471
  /**
5472
+ * Resolves a slot value into a { Component, slotProps } pair, handling the three
5473
+ * slot forms: a component type, a className string, or a partial-props object.
5474
+ */
5475
+ function resolveSlotComponent(slot, DefaultComponent) {
5476
+ if (isReactComponentType(slot)) return {
5477
+ Component: slot,
5478
+ slotProps: void 0
5479
+ };
5480
+ if (typeof slot === "string") return {
5481
+ Component: DefaultComponent,
5482
+ slotProps: { className: slot }
5483
+ };
5484
+ if (slot && typeof slot === "object") return {
5485
+ Component: DefaultComponent,
5486
+ slotProps: slot
5487
+ };
5488
+ return {
5489
+ Component: DefaultComponent,
5490
+ slotProps: void 0
5491
+ };
5492
+ }
5493
+ /**
4116
5494
  * Memoized wrapper for assistant messages to prevent re-renders when other messages change.
4117
5495
  */
4118
5496
  const MemoizedAssistantMessage = React.memo(function MemoizedAssistantMessage({ message, messages, isRunning, AssistantMessageComponent, slotProps }) {
@@ -4131,7 +5509,6 @@ const MemoizedAssistantMessage = React.memo(function MemoizedAssistantMessage({
4131
5509
  if (prevToolCalls && nextToolCalls) for (let i = 0; i < prevToolCalls.length; i++) {
4132
5510
  const prevTc = prevToolCalls[i];
4133
5511
  const nextTc = nextToolCalls[i];
4134
- if (!prevTc || !nextTc) return false;
4135
5512
  if (prevTc.id !== nextTc.id) return false;
4136
5513
  if (prevTc.function.arguments !== nextTc.function.arguments) return false;
4137
5514
  }
@@ -4210,6 +5587,7 @@ const MemoizedCustomMessage = React.memo(function MemoizedCustomMessage({ messag
4210
5587
  if (JSON.stringify(prevProps.stateSnapshot) !== JSON.stringify(nextProps.stateSnapshot)) return false;
4211
5588
  return true;
4212
5589
  });
5590
+ const VIRTUALIZE_THRESHOLD = 50;
4213
5591
  function CopilotChatMessageView({ messages = [], assistantMessage, userMessage, reasoningMessage, cursor, isRunning = false, children, className, ...props }) {
4214
5592
  const renderCustomMessage = useRenderCustomMessages();
4215
5593
  const { renderActivityMessage } = useRenderActivityMessage();
@@ -4218,12 +5596,14 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4218
5596
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
4219
5597
  useEffect(() => {
4220
5598
  if (!config?.agentId) return;
4221
- const agent = copilotkit.getAgent(config.agentId);
5599
+ const registryAgent = copilotkit.getAgent(config.agentId);
5600
+ const agent = getThreadClone(registryAgent, config.threadId) ?? registryAgent;
4222
5601
  if (!agent) return;
4223
5602
  const subscription = agent.subscribe({ onStateChanged: forceUpdate });
4224
5603
  return () => subscription.unsubscribe();
4225
5604
  }, [
4226
5605
  config?.agentId,
5606
+ config?.threadId,
4227
5607
  copilotkit,
4228
5608
  forceUpdate
4229
5609
  ]);
@@ -4241,9 +5621,34 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4241
5621
  if (!resolvedRunId) return void 0;
4242
5622
  return copilotkit.getStateByRun(config.agentId, config.threadId, resolvedRunId);
4243
5623
  };
4244
- const deduplicatedMessages = [...new Map(messages.map((m) => [m.id, m])).values()];
5624
+ const deduplicatedMessages = useMemo(() => [...new Map(messages.map((m) => [m.id, m])).values()], [messages]);
4245
5625
  if (process.env.NODE_ENV === "development" && deduplicatedMessages.length < messages.length) console.warn(`CopilotChatMessageView: Deduplicated ${messages.length - deduplicatedMessages.length} message(s) with duplicate IDs.`);
4246
- const messageElements = deduplicatedMessages.flatMap((message) => {
5626
+ const { Component: AssistantComponent, slotProps: assistantSlotProps } = useMemo(() => resolveSlotComponent(assistantMessage, CopilotChatAssistantMessage_default), [assistantMessage]);
5627
+ const { Component: UserComponent, slotProps: userSlotProps } = useMemo(() => resolveSlotComponent(userMessage, CopilotChatUserMessage_default), [userMessage]);
5628
+ const { Component: ReasoningComponent, slotProps: reasoningSlotProps } = useMemo(() => resolveSlotComponent(reasoningMessage, CopilotChatReasoningMessage_default), [reasoningMessage]);
5629
+ const scrollElementFromCtx = useContext(ScrollElementContext);
5630
+ const scrollElement = scrollElementFromCtx && scrollElementFromCtx.clientHeight > 0 ? scrollElementFromCtx : null;
5631
+ useEffect(() => {
5632
+ 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.");
5633
+ }, [scrollElementFromCtx]);
5634
+ const shouldVirtualize = !!scrollElement && !children && deduplicatedMessages.length > VIRTUALIZE_THRESHOLD;
5635
+ const virtualizer = useVirtualizer({
5636
+ count: shouldVirtualize ? deduplicatedMessages.length : 0,
5637
+ getScrollElement: () => scrollElement,
5638
+ estimateSize: () => 100,
5639
+ overscan: 5,
5640
+ measureElement: (el) => el?.getBoundingClientRect().height ?? 0,
5641
+ initialRect: {
5642
+ width: 0,
5643
+ height: 600
5644
+ }
5645
+ });
5646
+ const firstMessageId = deduplicatedMessages[0]?.id;
5647
+ useLayoutEffect(() => {
5648
+ if (!shouldVirtualize || !deduplicatedMessages.length) return;
5649
+ virtualizer.scrollToIndex(deduplicatedMessages.length - 1, { align: "end" });
5650
+ }, [shouldVirtualize, firstMessageId]);
5651
+ const renderMessageBlock = (message) => {
4247
5652
  const elements = [];
4248
5653
  const stateSnapshot = getStateSnapshotForMessage(message.id);
4249
5654
  if (renderCustomMessage) elements.push(/* @__PURE__ */ jsx(MemoizedCustomMessage, {
@@ -4252,58 +5657,38 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4252
5657
  renderCustomMessage,
4253
5658
  stateSnapshot
4254
5659
  }, `${message.id}-custom-before`));
4255
- if (message.role === "assistant") {
4256
- let AssistantComponent = CopilotChatAssistantMessage_default;
4257
- let assistantSlotProps;
4258
- if (isReactComponentType(assistantMessage)) AssistantComponent = assistantMessage;
4259
- else if (typeof assistantMessage === "string") assistantSlotProps = { className: assistantMessage };
4260
- else if (assistantMessage && typeof assistantMessage === "object") assistantSlotProps = assistantMessage;
4261
- elements.push(/* @__PURE__ */ jsx(MemoizedAssistantMessage, {
4262
- message,
4263
- messages,
4264
- isRunning,
4265
- AssistantMessageComponent: AssistantComponent,
4266
- slotProps: assistantSlotProps
4267
- }, message.id));
4268
- } else if (message.role === "user") {
4269
- let UserComponent = CopilotChatUserMessage_default;
4270
- let userSlotProps;
4271
- if (isReactComponentType(userMessage)) UserComponent = userMessage;
4272
- else if (typeof userMessage === "string") userSlotProps = { className: userMessage };
4273
- else if (userMessage && typeof userMessage === "object") userSlotProps = userMessage;
4274
- elements.push(/* @__PURE__ */ jsx(MemoizedUserMessage, {
4275
- message,
4276
- UserMessageComponent: UserComponent,
4277
- slotProps: userSlotProps
4278
- }, message.id));
4279
- } else if (message.role === "activity") {
4280
- const activityMsg = message;
4281
- elements.push(/* @__PURE__ */ jsx(MemoizedActivityMessage, {
4282
- message: activityMsg,
4283
- renderActivityMessage
4284
- }, message.id));
4285
- } else if (message.role === "reasoning") {
4286
- let ReasoningComponent = CopilotChatReasoningMessage_default;
4287
- let reasoningSlotProps;
4288
- if (isReactComponentType(reasoningMessage)) ReasoningComponent = reasoningMessage;
4289
- else if (typeof reasoningMessage === "string") reasoningSlotProps = { className: reasoningMessage };
4290
- else if (reasoningMessage && typeof reasoningMessage === "object") reasoningSlotProps = reasoningMessage;
4291
- elements.push(/* @__PURE__ */ jsx(MemoizedReasoningMessage, {
4292
- message,
4293
- messages,
4294
- isRunning,
4295
- ReasoningMessageComponent: ReasoningComponent,
4296
- slotProps: reasoningSlotProps
4297
- }, message.id));
4298
- }
5660
+ if (message.role === "assistant") elements.push(/* @__PURE__ */ jsx(MemoizedAssistantMessage, {
5661
+ message,
5662
+ messages,
5663
+ isRunning,
5664
+ AssistantMessageComponent: AssistantComponent,
5665
+ slotProps: assistantSlotProps
5666
+ }, message.id));
5667
+ else if (message.role === "user") elements.push(/* @__PURE__ */ jsx(MemoizedUserMessage, {
5668
+ message,
5669
+ UserMessageComponent: UserComponent,
5670
+ slotProps: userSlotProps
5671
+ }, message.id));
5672
+ else if (message.role === "activity") elements.push(/* @__PURE__ */ jsx(MemoizedActivityMessage, {
5673
+ message,
5674
+ renderActivityMessage
5675
+ }, message.id));
5676
+ else if (message.role === "reasoning") elements.push(/* @__PURE__ */ jsx(MemoizedReasoningMessage, {
5677
+ message,
5678
+ messages,
5679
+ isRunning,
5680
+ ReasoningMessageComponent: ReasoningComponent,
5681
+ slotProps: reasoningSlotProps
5682
+ }, message.id));
4299
5683
  if (renderCustomMessage) elements.push(/* @__PURE__ */ jsx(MemoizedCustomMessage, {
4300
5684
  message,
4301
5685
  position: "after",
4302
5686
  renderCustomMessage,
4303
5687
  stateSnapshot
4304
5688
  }, `${message.id}-custom-after`));
4305
- return elements;
4306
- }).filter(Boolean);
5689
+ return elements.filter(Boolean);
5690
+ };
5691
+ const messageElements = shouldVirtualize ? [] : deduplicatedMessages.flatMap(renderMessageBlock);
4307
5692
  if (children) return /* @__PURE__ */ jsx("div", {
4308
5693
  "data-copilotkit": true,
4309
5694
  style: { display: "contents" },
@@ -4314,30 +5699,356 @@ function CopilotChatMessageView({ messages = [], assistantMessage, userMessage,
4314
5699
  interruptElement
4315
5700
  })
4316
5701
  });
4317
- const lastMessage = messages[messages.length - 1];
4318
- const showCursor = isRunning && lastMessage?.role !== "reasoning";
5702
+ const lastMessage = messages[messages.length - 1];
5703
+ const showCursor = isRunning && lastMessage?.role !== "reasoning";
5704
+ return /* @__PURE__ */ jsxs("div", {
5705
+ "data-copilotkit": true,
5706
+ "data-testid": "copilot-message-list",
5707
+ className: twMerge("copilotKitMessages cpk:flex cpk:flex-col", className),
5708
+ ...props,
5709
+ children: [
5710
+ shouldVirtualize ? /* @__PURE__ */ jsx("div", {
5711
+ style: {
5712
+ height: virtualizer.getTotalSize(),
5713
+ position: "relative"
5714
+ },
5715
+ children: virtualizer.getVirtualItems().map((virtualItem) => {
5716
+ const message = deduplicatedMessages[virtualItem.index];
5717
+ return /* @__PURE__ */ jsx("div", {
5718
+ "data-index": virtualItem.index,
5719
+ ref: virtualizer.measureElement,
5720
+ style: {
5721
+ position: "absolute",
5722
+ top: 0,
5723
+ left: 0,
5724
+ width: "100%",
5725
+ transform: `translateY(${virtualItem.start}px)`
5726
+ },
5727
+ children: renderMessageBlock(message)
5728
+ }, message.id);
5729
+ })
5730
+ }) : messageElements,
5731
+ interruptElement,
5732
+ showCursor && /* @__PURE__ */ jsx("div", {
5733
+ className: "cpk:mt-2",
5734
+ children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
5735
+ })
5736
+ ]
5737
+ });
5738
+ }
5739
+ CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
5740
+ return /* @__PURE__ */ jsx("div", {
5741
+ "data-testid": "copilot-loading-cursor",
5742
+ className: twMerge("cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse-cursor cpk:ml-1", className),
5743
+ ...props
5744
+ });
5745
+ };
5746
+
5747
+ //#endregion
5748
+ //#region src/v2/components/chat/CopilotChatAttachmentQueue.tsx
5749
+ const CopilotChatAttachmentQueue = ({ attachments, onRemoveAttachment, className }) => {
5750
+ if (attachments.length === 0) return null;
5751
+ return /* @__PURE__ */ jsx("div", {
5752
+ className: cn("cpk:flex cpk:flex-wrap cpk:gap-2 cpk:p-2", className),
5753
+ children: attachments.map((attachment) => {
5754
+ const isMedia = attachment.type === "image" || attachment.type === "video";
5755
+ return /* @__PURE__ */ jsxs("div", {
5756
+ 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]"),
5757
+ children: [
5758
+ attachment.status === "uploading" && /* @__PURE__ */ jsx(UploadingOverlay, {}),
5759
+ /* @__PURE__ */ jsx(AttachmentPreview, { attachment }),
5760
+ /* @__PURE__ */ jsx("button", {
5761
+ onClick: () => onRemoveAttachment(attachment.id),
5762
+ 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"),
5763
+ "aria-label": "Remove attachment",
5764
+ children: "✕"
5765
+ })
5766
+ ]
5767
+ }, attachment.id);
5768
+ })
5769
+ });
5770
+ };
5771
+ function UploadingOverlay() {
5772
+ return /* @__PURE__ */ jsx("div", {
5773
+ className: "cpk:absolute cpk:inset-0 cpk:flex cpk:items-center cpk:justify-center cpk:bg-black/40 cpk:z-10",
5774
+ children: /* @__PURE__ */ 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" })
5775
+ });
5776
+ }
5777
+ function AttachmentPreview({ attachment }) {
5778
+ if (attachment.status === "uploading") return /* @__PURE__ */ jsx("div", { className: "cpk:w-full cpk:h-full" });
5779
+ switch (attachment.type) {
5780
+ case "image": return /* @__PURE__ */ jsx(ImagePreview, { attachment });
5781
+ case "audio": return /* @__PURE__ */ jsx(AudioPreview, { attachment });
5782
+ case "video": return /* @__PURE__ */ jsx(VideoPreview, { attachment });
5783
+ case "document": return /* @__PURE__ */ jsx(DocumentPreview, { attachment });
5784
+ }
5785
+ }
5786
+ function Lightbox({ onClose, children }) {
5787
+ useEffect(() => {
5788
+ const handleKey = (e) => {
5789
+ if (e.key === "Escape") onClose();
5790
+ };
5791
+ document.addEventListener("keydown", handleKey);
5792
+ return () => document.removeEventListener("keydown", handleKey);
5793
+ }, [onClose]);
5794
+ if (typeof document === "undefined") return null;
5795
+ return createPortal(/* @__PURE__ */ jsxs("div", {
5796
+ 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",
5797
+ onClick: onClose,
5798
+ children: [/* @__PURE__ */ jsx("button", {
5799
+ onClick: onClose,
5800
+ 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",
5801
+ "aria-label": "Close preview",
5802
+ children: /* @__PURE__ */ jsx(X, { className: "cpk:w-5 cpk:h-5" })
5803
+ }), /* @__PURE__ */ jsx("div", {
5804
+ onClick: (e) => e.stopPropagation(),
5805
+ children
5806
+ })]
5807
+ }), document.body);
5808
+ }
5809
+ /**
5810
+ * Hook that manages lightbox open/close and uses the View Transition API to
5811
+ * morph the thumbnail into fullscreen content.
5812
+ *
5813
+ * The trick: `view-transition-name` must live on exactly ONE element at a time.
5814
+ * - Old state (thumbnail visible): name is on the thumbnail.
5815
+ * - New state (lightbox visible): name moves to the lightbox content.
5816
+ * `flushSync` ensures React commits the DOM change synchronously inside the
5817
+ * `startViewTransition` callback so the API can snapshot old → new correctly.
5818
+ */
5819
+ function useLightbox() {
5820
+ const thumbnailRef = useRef(null);
5821
+ const [open, setOpen] = useState(false);
5822
+ const vtName = useId();
5823
+ return {
5824
+ thumbnailRef,
5825
+ vtName,
5826
+ open,
5827
+ openLightbox: useCallback(() => {
5828
+ const thumb = thumbnailRef.current;
5829
+ const doc = document;
5830
+ if (doc.startViewTransition && thumb) {
5831
+ thumb.style.viewTransitionName = vtName;
5832
+ doc.startViewTransition(() => {
5833
+ thumb.style.viewTransitionName = "";
5834
+ flushSync(() => setOpen(true));
5835
+ });
5836
+ } else setOpen(true);
5837
+ }, []),
5838
+ closeLightbox: useCallback(() => {
5839
+ const thumb = thumbnailRef.current;
5840
+ const doc = document;
5841
+ if (doc.startViewTransition && thumb) doc.startViewTransition(() => {
5842
+ flushSync(() => setOpen(false));
5843
+ thumb.style.viewTransitionName = vtName;
5844
+ }).finished.then(() => {
5845
+ thumb.style.viewTransitionName = "";
5846
+ }).catch(() => {
5847
+ thumb.style.viewTransitionName = "";
5848
+ });
5849
+ else setOpen(false);
5850
+ }, [])
5851
+ };
5852
+ }
5853
+ function ImagePreview({ attachment }) {
5854
+ const src = getSourceUrl(attachment.source);
5855
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5856
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("img", {
5857
+ ref: thumbnailRef,
5858
+ src,
5859
+ alt: attachment.filename || "Image attachment",
5860
+ className: "cpk:w-full cpk:h-full cpk:object-cover cpk:cursor-pointer",
5861
+ onClick: openLightbox
5862
+ }), open && /* @__PURE__ */ jsx(Lightbox, {
5863
+ onClose: closeLightbox,
5864
+ children: /* @__PURE__ */ jsx("img", {
5865
+ style: { viewTransitionName: vtName },
5866
+ src,
5867
+ alt: attachment.filename || "Image attachment",
5868
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:object-contain cpk:rounded-lg"
5869
+ })
5870
+ })] });
5871
+ }
5872
+ function AudioPreview({ attachment }) {
5873
+ return /* @__PURE__ */ jsxs("div", {
5874
+ className: "cpk:flex cpk:flex-col cpk:gap-1 cpk:w-full",
5875
+ children: [/* @__PURE__ */ jsx("audio", {
5876
+ src: getSourceUrl(attachment.source),
5877
+ controls: true,
5878
+ preload: "metadata",
5879
+ className: "cpk:w-full cpk:h-8"
5880
+ }), attachment.filename && /* @__PURE__ */ jsx("span", {
5881
+ className: "cpk:text-xs cpk:font-medium cpk:overflow-hidden cpk:text-ellipsis cpk:whitespace-nowrap",
5882
+ children: attachment.filename
5883
+ })]
5884
+ });
5885
+ }
5886
+ function VideoPreview({ attachment }) {
5887
+ const src = getSourceUrl(attachment.source);
5888
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
5889
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [
5890
+ /* @__PURE__ */ jsx("div", {
5891
+ ref: thumbnailRef,
5892
+ className: "cpk:w-full cpk:h-full",
5893
+ children: attachment.thumbnail ? /* @__PURE__ */ jsx("img", {
5894
+ src: attachment.thumbnail,
5895
+ alt: attachment.filename || "Video thumbnail",
5896
+ className: "cpk:w-full cpk:h-full cpk:object-cover"
5897
+ }) : /* @__PURE__ */ jsx("video", {
5898
+ src,
5899
+ preload: "metadata",
5900
+ muted: true,
5901
+ className: "cpk:w-full cpk:h-full cpk:object-cover"
5902
+ })
5903
+ }),
5904
+ /* @__PURE__ */ jsx("button", {
5905
+ onClick: openLightbox,
5906
+ 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",
5907
+ "aria-label": "Play video",
5908
+ children: /* @__PURE__ */ jsx("div", {
5909
+ className: "cpk:w-8 cpk:h-8 cpk:rounded-full cpk:bg-black/60 cpk:flex cpk:items-center cpk:justify-center",
5910
+ children: /* @__PURE__ */ jsx(Play, { className: "cpk:w-4 cpk:h-4 cpk:text-white cpk:ml-0.5" })
5911
+ })
5912
+ }),
5913
+ open && /* @__PURE__ */ jsx(Lightbox, {
5914
+ onClose: closeLightbox,
5915
+ children: /* @__PURE__ */ jsx("video", {
5916
+ style: { viewTransitionName: vtName },
5917
+ src,
5918
+ controls: true,
5919
+ autoPlay: true,
5920
+ className: "cpk:max-w-[90vw] cpk:max-h-[90vh] cpk:rounded-lg"
5921
+ })
5922
+ })
5923
+ ] });
5924
+ }
5925
+ function isPdf(mimeType) {
5926
+ return !!mimeType && mimeType.includes("pdf");
5927
+ }
5928
+ function isText(mimeType) {
5929
+ return !!mimeType && mimeType.startsWith("text/");
5930
+ }
5931
+ function canPreviewInBrowser(mimeType) {
5932
+ return isPdf(mimeType) || isText(mimeType);
5933
+ }
5934
+ /**
5935
+ * Convert a base64-encoded data source to a blob: URL that browsers will
5936
+ * render inside an iframe (data: URLs are blocked for PDFs in most browsers).
5937
+ */
5938
+ function useBlobUrl(attachment) {
5939
+ const [url, setUrl] = useState(null);
5940
+ useEffect(() => {
5941
+ if (attachment.source.type !== "data") return;
5942
+ try {
5943
+ const binary = atob(attachment.source.value);
5944
+ const bytes = new Uint8Array(binary.length);
5945
+ for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
5946
+ const blob = new Blob([bytes], { type: attachment.source.mimeType || "application/octet-stream" });
5947
+ const blobUrl = URL.createObjectURL(blob);
5948
+ setUrl(blobUrl);
5949
+ return () => URL.revokeObjectURL(blobUrl);
5950
+ } catch (error) {
5951
+ console.error("[CopilotKit] Failed to decode attachment data:", error);
5952
+ setUrl(null);
5953
+ }
5954
+ }, [
5955
+ attachment.source.type,
5956
+ attachment.source.value,
5957
+ attachment.source.mimeType
5958
+ ]);
5959
+ if (attachment.source.type === "url") return attachment.source.value;
5960
+ return url;
5961
+ }
5962
+ function DocumentLightboxContent({ attachment, vtName }) {
5963
+ const mimeType = attachment.source.mimeType;
5964
+ const blobUrl = useBlobUrl(attachment);
5965
+ if (isPdf(mimeType)) {
5966
+ if (!blobUrl) return null;
5967
+ return /* @__PURE__ */ jsx("iframe", {
5968
+ style: { viewTransitionName: vtName },
5969
+ src: blobUrl,
5970
+ title: attachment.filename || "PDF preview",
5971
+ className: "cpk:w-[90vw] cpk:h-[90vh] cpk:max-w-[1000px] cpk:rounded-lg cpk:bg-white"
5972
+ });
5973
+ }
5974
+ if (isText(mimeType)) {
5975
+ const textContent = attachment.source.type === "data" ? (() => {
5976
+ try {
5977
+ return atob(attachment.source.value);
5978
+ } catch {
5979
+ return attachment.source.value;
5980
+ }
5981
+ })() : null;
5982
+ return /* @__PURE__ */ jsxs("div", {
5983
+ style: { viewTransitionName: vtName },
5984
+ 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",
5985
+ children: [attachment.filename && /* @__PURE__ */ jsx("div", {
5986
+ 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",
5987
+ children: attachment.filename
5988
+ }), textContent ? /* @__PURE__ */ jsx("pre", {
5989
+ 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",
5990
+ children: textContent
5991
+ }) : blobUrl ? /* @__PURE__ */ jsx("iframe", {
5992
+ src: blobUrl,
5993
+ title: attachment.filename || "Text preview",
5994
+ className: "cpk:w-full cpk:h-[80vh] cpk:border-none"
5995
+ }) : null]
5996
+ });
5997
+ }
4319
5998
  return /* @__PURE__ */ jsxs("div", {
4320
- "data-copilotkit": true,
4321
- "data-testid": "copilot-message-list",
4322
- className: twMerge("copilotKitMessages cpk:flex cpk:flex-col", className),
4323
- ...props,
5999
+ style: { viewTransitionName: vtName },
6000
+ 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",
4324
6001
  children: [
4325
- messageElements,
4326
- interruptElement,
4327
- showCursor && /* @__PURE__ */ jsx("div", {
4328
- className: "cpk:mt-2",
4329
- children: renderSlot(cursor, CopilotChatMessageView.Cursor, {})
6002
+ /* @__PURE__ */ jsx("div", {
6003
+ 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",
6004
+ children: getDocumentIcon(mimeType ?? "")
6005
+ }),
6006
+ /* @__PURE__ */ jsxs("div", {
6007
+ className: "cpk:text-center",
6008
+ children: [/* @__PURE__ */ jsx("div", {
6009
+ className: "cpk:text-base cpk:font-medium cpk:text-gray-800 cpk:dark:text-gray-200",
6010
+ children: attachment.filename || "Document"
6011
+ }), /* @__PURE__ */ jsxs("div", {
6012
+ className: "cpk:text-sm cpk:text-gray-500 cpk:dark:text-gray-400 cpk:mt-1",
6013
+ children: [mimeType || "Unknown type", attachment.size != null && ` · ${formatFileSize(attachment.size)}`]
6014
+ })]
6015
+ }),
6016
+ /* @__PURE__ */ jsx("div", {
6017
+ className: "cpk:text-xs cpk:text-gray-400 cpk:dark:text-gray-500",
6018
+ children: "No preview available for this file type"
4330
6019
  })
4331
6020
  ]
4332
6021
  });
4333
6022
  }
4334
- CopilotChatMessageView.Cursor = function Cursor({ className, ...props }) {
4335
- return /* @__PURE__ */ jsx("div", {
4336
- "data-testid": "copilot-loading-cursor",
4337
- className: twMerge("cpk:w-[11px] cpk:h-[11px] cpk:rounded-full cpk:bg-foreground cpk:animate-pulse-cursor cpk:ml-1", className),
4338
- ...props
4339
- });
4340
- };
6023
+ function DocumentPreview({ attachment }) {
6024
+ const { thumbnailRef, vtName, open, openLightbox, closeLightbox } = useLightbox();
6025
+ const mimeType = attachment.source.mimeType;
6026
+ const previewable = canPreviewInBrowser(mimeType);
6027
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
6028
+ ref: thumbnailRef,
6029
+ className: cn("cpk:flex cpk:items-center cpk:gap-2", previewable && "cpk:cursor-pointer"),
6030
+ onClick: previewable ? openLightbox : void 0,
6031
+ children: [/* @__PURE__ */ jsx("div", {
6032
+ 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",
6033
+ children: getDocumentIcon(mimeType ?? "")
6034
+ }), /* @__PURE__ */ jsxs("div", {
6035
+ className: "cpk:flex cpk:flex-col cpk:min-w-0",
6036
+ children: [/* @__PURE__ */ jsx("span", {
6037
+ className: "cpk:text-xs cpk:font-medium cpk:break-all cpk:leading-tight",
6038
+ children: attachment.filename || "Document"
6039
+ }), attachment.size != null && /* @__PURE__ */ jsx("span", {
6040
+ className: "cpk:text-[11px] cpk:text-muted-foreground",
6041
+ children: formatFileSize(attachment.size)
6042
+ })]
6043
+ })]
6044
+ }), open && /* @__PURE__ */ jsx(Lightbox, {
6045
+ onClose: closeLightbox,
6046
+ children: /* @__PURE__ */ jsx(DocumentLightboxContent, {
6047
+ attachment,
6048
+ vtName
6049
+ })
6050
+ })] });
6051
+ }
4341
6052
 
4342
6053
  //#endregion
4343
6054
  //#region src/v2/hooks/use-keyboard-height.tsx
@@ -4383,7 +6094,19 @@ function useKeyboardHeight() {
4383
6094
  //#endregion
4384
6095
  //#region src/v2/components/chat/CopilotChatView.tsx
4385
6096
  const FEATHER_HEIGHT = 96;
4386
- 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 }) {
6097
+ function DropOverlay() {
6098
+ return /* @__PURE__ */ jsx("div", {
6099
+ 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"),
6100
+ children: /* @__PURE__ */ jsxs("div", {
6101
+ className: "cpk:flex cpk:flex-col cpk:items-center cpk:gap-2 cpk:text-primary/70",
6102
+ children: [/* @__PURE__ */ jsx(Upload, { className: "cpk:w-8 cpk:h-8" }), /* @__PURE__ */ jsx("span", {
6103
+ className: "cpk:text-sm cpk:font-medium",
6104
+ children: "Drop files here"
6105
+ })]
6106
+ })
6107
+ });
6108
+ }
6109
+ 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 }) {
4387
6110
  const inputContainerRef = useRef(null);
4388
6111
  const [inputContainerHeight, setInputContainerHeight] = useState(0);
4389
6112
  const [isResizing, setIsResizing] = useState(false);
@@ -4430,6 +6153,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4430
6153
  onCancelTranscribe,
4431
6154
  onFinishTranscribe,
4432
6155
  onFinishTranscribeWithAudio,
6156
+ onAddFile,
4433
6157
  positioning: "static",
4434
6158
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
4435
6159
  containerRef: inputContainerRef,
@@ -4470,21 +6194,34 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4470
6194
  onCancelTranscribe,
4471
6195
  onFinishTranscribe,
4472
6196
  onFinishTranscribeWithAudio,
6197
+ onAddFile,
4473
6198
  positioning: "static",
4474
6199
  showDisclaimer: true,
4475
6200
  ...disclaimer !== void 0 ? { disclaimer } : {}
4476
6201
  });
4477
- const BoundWelcomeScreen = renderSlot(welcomeScreen === true ? void 0 : welcomeScreen, CopilotChatView.WelcomeScreen, {
4478
- input: BoundInputForWelcome,
6202
+ const welcomeScreenSlot = welcomeScreen === true ? void 0 : welcomeScreen;
6203
+ const inputWithAttachments = /* @__PURE__ */ jsxs("div", {
6204
+ className: "cpk:w-full",
6205
+ children: [attachments && attachments.length > 0 && /* @__PURE__ */ jsx(CopilotChatAttachmentQueue, {
6206
+ attachments,
6207
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
6208
+ className: "cpk:mb-2"
6209
+ }), BoundInputForWelcome]
6210
+ });
6211
+ const BoundWelcomeScreen = renderSlot(welcomeScreenSlot, CopilotChatView.WelcomeScreen, {
6212
+ input: inputWithAttachments,
4479
6213
  suggestionView: BoundSuggestionView ?? /* @__PURE__ */ jsx(Fragment$1, {})
4480
6214
  });
4481
- return /* @__PURE__ */ jsx("div", {
6215
+ return /* @__PURE__ */ jsxs("div", {
4482
6216
  "data-copilotkit": true,
4483
6217
  "data-testid": "copilot-chat",
4484
6218
  "data-copilot-running": isRunning ? "true" : "false",
4485
- className: twMerge("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
6219
+ onDragOver,
6220
+ onDragLeave,
6221
+ onDrop,
6222
+ className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
4486
6223
  ...props,
4487
- children: BoundWelcomeScreen
6224
+ children: [dragOver && /* @__PURE__ */ jsx(DropOverlay, {}), BoundWelcomeScreen]
4488
6225
  });
4489
6226
  }
4490
6227
  if (children) return /* @__PURE__ */ jsx("div", {
@@ -4501,39 +6238,66 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4501
6238
  "data-copilotkit": true,
4502
6239
  "data-testid": "copilot-chat",
4503
6240
  "data-copilot-running": isRunning ? "true" : "false",
4504
- className: twMerge("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
6241
+ onDragOver,
6242
+ onDragLeave,
6243
+ onDrop,
6244
+ className: cn("copilotKitChat cpk:relative cpk:h-full cpk:flex cpk:flex-col", className),
4505
6245
  ...props,
4506
- children: [BoundScrollView, BoundInput]
6246
+ children: [
6247
+ dragOver && /* @__PURE__ */ jsx(DropOverlay, {}),
6248
+ BoundScrollView,
6249
+ /* @__PURE__ */ jsx("div", {
6250
+ className: "cpk:max-w-3xl cpk:mx-auto cpk:w-full",
6251
+ children: attachments && attachments.length > 0 && /* @__PURE__ */ jsx(CopilotChatAttachmentQueue, {
6252
+ attachments,
6253
+ onRemoveAttachment: (id) => onRemoveAttachment?.(id),
6254
+ className: "cpk:px-4"
6255
+ })
6256
+ }),
6257
+ BoundInput
6258
+ ]
4507
6259
  });
4508
6260
  }
4509
6261
  (function(_CopilotChatView) {
4510
6262
  const ScrollContent = ({ children, scrollToBottomButton, feather, inputContainerHeight, isResizing }) => {
4511
- const { isAtBottom, scrollToBottom } = useStickToBottomContext();
6263
+ const { isAtBottom, scrollToBottom, scrollRef } = useStickToBottomContext();
6264
+ const [scrollEl, setScrollEl] = useState(null);
6265
+ useLayoutEffect(() => {
6266
+ setScrollEl(scrollRef.current ?? null);
6267
+ }, []);
4512
6268
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
4513
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [
4514
- /* @__PURE__ */ jsx(StickToBottom.Content, {
4515
- className: "cpk:overflow-y-auto cpk:overflow-x-hidden",
4516
- style: {
4517
- flex: "1 1 0%",
4518
- minHeight: 0
4519
- },
4520
- children: /* @__PURE__ */ jsx("div", {
4521
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
4522
- children
6269
+ return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6270
+ value: scrollEl,
6271
+ children: /* @__PURE__ */ jsxs(Fragment$1, { children: [
6272
+ /* @__PURE__ */ jsx(StickToBottom.Content, {
6273
+ className: "cpk:overflow-y-auto cpk:overflow-x-hidden",
6274
+ style: {
6275
+ flex: "1 1 0%",
6276
+ minHeight: 0
6277
+ },
6278
+ children: /* @__PURE__ */ jsx("div", {
6279
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6280
+ children
6281
+ })
6282
+ }),
6283
+ BoundFeather,
6284
+ !isAtBottom && !isResizing && /* @__PURE__ */ jsx("div", {
6285
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6286
+ style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6287
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4523
6288
  })
4524
- }),
4525
- BoundFeather,
4526
- !isAtBottom && !isResizing && /* @__PURE__ */ jsx("div", {
4527
- className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
4528
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
4529
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4530
- })
4531
- ] });
6289
+ ] })
6290
+ });
4532
6291
  };
4533
6292
  _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
4534
6293
  const [hasMounted, setHasMounted] = useState(false);
4535
6294
  const { scrollRef, contentRef, scrollToBottom } = useStickToBottom();
4536
6295
  const [showScrollButton, setShowScrollButton] = useState(false);
6296
+ const [nonAutoScrollEl, setNonAutoScrollEl] = useState(null);
6297
+ const nonAutoScrollRefCallback = useCallback((el) => {
6298
+ scrollRef.current = el;
6299
+ setNonAutoScrollEl(el);
6300
+ }, []);
4537
6301
  useEffect(() => {
4538
6302
  setHasMounted(true);
4539
6303
  }, []);
@@ -4562,23 +6326,26 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
4562
6326
  });
4563
6327
  if (!autoScroll) {
4564
6328
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
4565
- return /* @__PURE__ */ jsxs("div", {
4566
- ref: scrollRef,
4567
- 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),
4568
- ...props,
4569
- children: [
4570
- /* @__PURE__ */ jsx("div", {
4571
- ref: contentRef,
4572
- className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
4573
- children
4574
- }),
4575
- BoundFeather,
4576
- showScrollButton && !isResizing && /* @__PURE__ */ jsx("div", {
4577
- className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
4578
- style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
4579
- children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
4580
- })
4581
- ]
6329
+ return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6330
+ value: nonAutoScrollEl,
6331
+ children: /* @__PURE__ */ jsxs("div", {
6332
+ ref: nonAutoScrollRefCallback,
6333
+ 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),
6334
+ ...props,
6335
+ children: [
6336
+ /* @__PURE__ */ jsx("div", {
6337
+ ref: contentRef,
6338
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6339
+ children
6340
+ }),
6341
+ BoundFeather,
6342
+ showScrollButton && !isResizing && /* @__PURE__ */ jsx("div", {
6343
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6344
+ style: { bottom: `${inputContainerHeight + FEATHER_HEIGHT + 16}px` },
6345
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6346
+ })
6347
+ ]
6348
+ })
4582
6349
  });
4583
6350
  }
4584
6351
  return /* @__PURE__ */ jsx(StickToBottom, {
@@ -4770,11 +6537,15 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
4770
6537
 
4771
6538
  //#endregion
4772
6539
  //#region src/v2/components/chat/CopilotChat.tsx
4773
- function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, onError, ...props }) {
6540
+ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
4774
6541
  const existingConfig = useCopilotChatConfiguration();
4775
6542
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? DEFAULT_AGENT_ID;
4776
6543
  const resolvedThreadId = useMemo(() => threadId ?? existingConfig?.threadId ?? randomUUID(), [threadId, existingConfig?.threadId]);
4777
- const { agent } = useAgent({ agentId: resolvedAgentId });
6544
+ const { agent } = useAgent({
6545
+ agentId: resolvedAgentId,
6546
+ threadId: resolvedThreadId,
6547
+ throttleMs
6548
+ });
4778
6549
  const { copilotkit } = useCopilotKit();
4779
6550
  const { suggestions: autoSuggestions } = useSuggestions({ agentId: resolvedAgentId });
4780
6551
  const { checkFeature } = useLicenseContext();
@@ -4803,6 +6574,7 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4803
6574
  const [inputValue, setInputValue] = useState("");
4804
6575
  const [transcriptionError, setTranscriptionError] = useState(null);
4805
6576
  const [isTranscribing, setIsTranscribing] = useState(false);
6577
+ const { attachments: selectedAttachments, enabled: attachmentsEnabled, dragOver, fileInputRef, containerRef: chatContainerRef, handleFileUpload, handleDragOver, handleDragLeave, handleDrop, removeAttachment, consumeAttachments } = useAttachments({ config: attachmentsConfig });
4806
6578
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
4807
6579
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
4808
6580
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
@@ -4818,7 +6590,6 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4818
6590
  console.error("CopilotChat: connectAgent failed", error);
4819
6591
  }
4820
6592
  };
4821
- agent.threadId = resolvedThreadId;
4822
6593
  connect(agent);
4823
6594
  return () => {
4824
6595
  detached = true;
@@ -4831,7 +6602,31 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4831
6602
  resolvedAgentId
4832
6603
  ]);
4833
6604
  const onSubmitInput = useCallback(async (value) => {
4834
- agent.addMessage({
6605
+ if (selectedAttachments.some((a) => a.status === "uploading")) {
6606
+ console.error("[CopilotKit] Cannot send while attachments are uploading");
6607
+ return;
6608
+ }
6609
+ const readyAttachments = consumeAttachments();
6610
+ if (readyAttachments.length > 0) {
6611
+ const contentParts = [];
6612
+ if (value.trim()) contentParts.push({
6613
+ type: "text",
6614
+ text: value
6615
+ });
6616
+ for (const att of readyAttachments) contentParts.push({
6617
+ type: att.type,
6618
+ source: att.source,
6619
+ metadata: {
6620
+ ...att.filename ? { filename: att.filename } : {},
6621
+ ...att.metadata
6622
+ }
6623
+ });
6624
+ agent.addMessage({
6625
+ id: randomUUID(),
6626
+ role: "user",
6627
+ content: contentParts
6628
+ });
6629
+ } else agent.addMessage({
4835
6630
  id: randomUUID(),
4836
6631
  role: "user",
4837
6632
  content: value
@@ -4842,7 +6637,11 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4842
6637
  } catch (error) {
4843
6638
  console.error("CopilotChat: runAgent failed", error);
4844
6639
  }
4845
- }, [agent]);
6640
+ }, [
6641
+ agent,
6642
+ selectedAttachments,
6643
+ consumeAttachments
6644
+ ]);
4846
6645
  const handleSelectSuggestion = useCallback(async (suggestion) => {
4847
6646
  agent.addMessage({
4848
6647
  id: randomUUID(),
@@ -4929,21 +6728,33 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4929
6728
  return () => clearTimeout(timer);
4930
6729
  }
4931
6730
  }, [transcriptionError]);
4932
- const mergedProps = merge({
6731
+ const stableMessageView = useShallowStableRef(typeof providedMessageView === "string" ? { className: providedMessageView } : providedMessageView);
6732
+ const stableSuggestionView = useShallowStableRef(providedSuggestionView);
6733
+ const handleAddFile = useCallback(() => {
6734
+ setTimeout(() => {
6735
+ fileInputRef.current?.click();
6736
+ }, 100);
6737
+ }, []);
6738
+ const mergedProps = {
4933
6739
  isRunning: agent.isRunning,
4934
6740
  suggestions: autoSuggestions,
4935
6741
  onSelectSuggestion: handleSelectSuggestion,
4936
- suggestionView: providedSuggestionView
4937
- }, {
4938
- ...restProps,
4939
- ...typeof providedMessageView === "string" ? { messageView: { className: providedMessageView } } : providedMessageView !== void 0 ? { messageView: providedMessageView } : {}
4940
- });
6742
+ suggestionView: stableSuggestionView,
6743
+ ...restProps
6744
+ };
6745
+ if (stableMessageView !== void 0) mergedProps.messageView = stableMessageView;
4941
6746
  const hasMessages = agent.messages.length > 0;
4942
6747
  const effectiveStopHandler = agent.isRunning && hasMessages ? providedStopHandler ?? stopCurrentRun : providedStopHandler;
4943
6748
  const showTranscription = isTranscriptionEnabled && isMediaRecorderSupported;
4944
6749
  const effectiveMode = isTranscribing ? "processing" : transcribeMode;
4945
- const RenderedChatView = renderSlot(chatView, CopilotChatView, merge(mergedProps, {
4946
- messages: useMemo(() => [...agent.messages], [JSON.stringify(agent.messages)]),
6750
+ const messages = useMemo(() => [...agent.messages], [agent.messages.map((m) => {
6751
+ const contentKey = typeof m.content === "string" ? m.content.length : Array.isArray(m.content) ? m.content.length : 0;
6752
+ const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6753
+ return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6754
+ }).join(",")]);
6755
+ const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6756
+ ...mergedProps,
6757
+ messages,
4947
6758
  onSubmitMessage: onSubmitInput,
4948
6759
  onStop: effectiveStopHandler,
4949
6760
  inputMode: effectiveMode,
@@ -4952,32 +6763,51 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
4952
6763
  onStartTranscribe: showTranscription ? handleStartTranscribe : void 0,
4953
6764
  onCancelTranscribe: showTranscription ? handleCancelTranscribe : void 0,
4954
6765
  onFinishTranscribe: showTranscription ? handleFinishTranscribe : void 0,
4955
- onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0
4956
- }));
4957
- return /* @__PURE__ */ jsxs(CopilotChatConfigurationProvider, {
6766
+ onFinishTranscribeWithAudio: showTranscription ? handleFinishTranscribeWithAudio : void 0,
6767
+ attachments: selectedAttachments,
6768
+ onRemoveAttachment: removeAttachment,
6769
+ onAddFile: attachmentsEnabled ? handleAddFile : void 0,
6770
+ dragOver,
6771
+ onDragOver: handleDragOver,
6772
+ onDragLeave: handleDragLeave,
6773
+ onDrop: handleDrop
6774
+ });
6775
+ return /* @__PURE__ */ jsx(CopilotChatConfigurationProvider, {
4958
6776
  agentId: resolvedAgentId,
4959
6777
  threadId: resolvedThreadId,
4960
6778
  labels,
4961
6779
  isModalDefaultOpen,
4962
- children: [
4963
- !isChatLicensed && /* @__PURE__ */ jsx(InlineFeatureWarning, { featureName: "Chat" }),
4964
- transcriptionError && /* @__PURE__ */ jsx("div", {
4965
- style: {
4966
- position: "absolute",
4967
- bottom: "100px",
4968
- left: "50%",
4969
- transform: "translateX(-50%)",
4970
- backgroundColor: "#ef4444",
4971
- color: "white",
4972
- padding: "8px 16px",
4973
- borderRadius: "8px",
4974
- fontSize: "14px",
4975
- zIndex: 50
4976
- },
4977
- children: transcriptionError
4978
- }),
4979
- RenderedChatView
4980
- ]
6780
+ children: /* @__PURE__ */ jsxs("div", {
6781
+ ref: chatContainerRef,
6782
+ style: { display: "contents" },
6783
+ children: [
6784
+ attachmentsEnabled && /* @__PURE__ */ jsx("input", {
6785
+ type: "file",
6786
+ multiple: true,
6787
+ ref: fileInputRef,
6788
+ onChange: handleFileUpload,
6789
+ accept: attachmentsConfig?.accept ?? "*/*",
6790
+ style: { display: "none" }
6791
+ }),
6792
+ !isChatLicensed && /* @__PURE__ */ jsx(InlineFeatureWarning, { featureName: "Chat" }),
6793
+ transcriptionError && /* @__PURE__ */ jsx("div", {
6794
+ style: {
6795
+ position: "absolute",
6796
+ bottom: "100px",
6797
+ left: "50%",
6798
+ transform: "translateX(-50%)",
6799
+ backgroundColor: "#ef4444",
6800
+ color: "white",
6801
+ padding: "8px 16px",
6802
+ borderRadius: "8px",
6803
+ fontSize: "14px",
6804
+ zIndex: 50
6805
+ },
6806
+ children: transcriptionError
6807
+ }),
6808
+ RenderedChatView
6809
+ ]
6810
+ })
4981
6811
  });
4982
6812
  }
4983
6813
  (function(_CopilotChat) {
@@ -5842,6 +7672,227 @@ function useToast() {
5842
7672
  if (!context) throw new Error("useToast must be used within a ToastProvider");
5843
7673
  return context;
5844
7674
  }
7675
+ function formatBannerMessage(message) {
7676
+ const jsonMatch = message.match(/'message':\s*'([^']+)'/);
7677
+ if (jsonMatch) return jsonMatch[1];
7678
+ let cleaned = message.split(" - ")[0];
7679
+ cleaned = cleaned.split(": Error code")[0];
7680
+ cleaned = cleaned.replace(/:\s*\d{3}$/, "");
7681
+ cleaned = cleaned.replace(/See more:.*$/g, "");
7682
+ cleaned = cleaned.trim();
7683
+ return cleaned || "An error occurred.";
7684
+ }
7685
+ function extractUrl(message) {
7686
+ const markdownMatch = /\[([^\]]+)\]\(([^)]+)\)/.exec(message);
7687
+ if (markdownMatch) return {
7688
+ url: markdownMatch[2],
7689
+ text: "See More"
7690
+ };
7691
+ const plainMatch = /(https?:\/\/[^\s)]+)/.exec(message);
7692
+ if (plainMatch) return {
7693
+ url: plainMatch[0].replace(/[.,;:'"]*$/, ""),
7694
+ text: "See More"
7695
+ };
7696
+ return null;
7697
+ }
7698
+ function BannerErrorDisplay({ bannerError, onDismiss }) {
7699
+ const [detailsExpanded, setDetailsExpanded] = useState(false);
7700
+ const colors = getErrorColors(getErrorSeverity(bannerError));
7701
+ const details = bannerError.details;
7702
+ const link = extractUrl(bannerError.message);
7703
+ return /* @__PURE__ */ jsxs("div", {
7704
+ style: {
7705
+ position: "fixed",
7706
+ bottom: "20px",
7707
+ left: "50%",
7708
+ transform: "translateX(-50%)",
7709
+ zIndex: 9999,
7710
+ backgroundColor: colors.background,
7711
+ border: `1px solid ${colors.border}`,
7712
+ borderLeft: `4px solid ${colors.border}`,
7713
+ borderRadius: "8px",
7714
+ padding: "12px 16px",
7715
+ fontSize: "13px",
7716
+ boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
7717
+ backdropFilter: "blur(8px)",
7718
+ maxWidth: "min(90vw, 700px)",
7719
+ width: "100%",
7720
+ boxSizing: "border-box",
7721
+ overflow: "hidden"
7722
+ },
7723
+ children: [/* @__PURE__ */ jsxs("div", {
7724
+ style: {
7725
+ display: "flex",
7726
+ justifyContent: "space-between",
7727
+ alignItems: "center",
7728
+ gap: "10px"
7729
+ },
7730
+ children: [/* @__PURE__ */ jsxs("div", {
7731
+ style: {
7732
+ display: "flex",
7733
+ alignItems: "center",
7734
+ gap: "8px",
7735
+ flex: 1,
7736
+ minWidth: 0
7737
+ },
7738
+ children: [/* @__PURE__ */ jsx("div", { style: {
7739
+ width: "12px",
7740
+ height: "12px",
7741
+ borderRadius: "50%",
7742
+ backgroundColor: colors.border,
7743
+ flexShrink: 0
7744
+ } }), /* @__PURE__ */ jsxs("div", {
7745
+ style: {
7746
+ display: "flex",
7747
+ alignItems: "center",
7748
+ gap: "10px",
7749
+ flex: 1,
7750
+ minWidth: 0
7751
+ },
7752
+ children: [
7753
+ /* @__PURE__ */ jsx("div", {
7754
+ style: {
7755
+ color: colors.text,
7756
+ lineHeight: "1.4",
7757
+ fontWeight: "400",
7758
+ fontSize: "13px",
7759
+ flex: 1,
7760
+ wordBreak: "break-all",
7761
+ overflowWrap: "break-word",
7762
+ maxWidth: "550px",
7763
+ overflow: "hidden",
7764
+ display: "-webkit-box",
7765
+ WebkitLineClamp: 10,
7766
+ WebkitBoxOrient: "vertical"
7767
+ },
7768
+ children: formatBannerMessage(bannerError.message)
7769
+ }),
7770
+ link && /* @__PURE__ */ jsx("button", {
7771
+ onClick: () => window.open(link.url, "_blank", "noopener,noreferrer"),
7772
+ style: {
7773
+ background: colors.border,
7774
+ color: "white",
7775
+ border: "none",
7776
+ borderRadius: "5px",
7777
+ padding: "4px 10px",
7778
+ fontSize: "11px",
7779
+ fontWeight: "500",
7780
+ cursor: "pointer",
7781
+ transition: "all 0.2s ease",
7782
+ flexShrink: 0
7783
+ },
7784
+ onMouseEnter: (e) => {
7785
+ e.currentTarget.style.opacity = "0.9";
7786
+ e.currentTarget.style.transform = "translateY(-1px)";
7787
+ },
7788
+ onMouseLeave: (e) => {
7789
+ e.currentTarget.style.opacity = "1";
7790
+ e.currentTarget.style.transform = "translateY(0)";
7791
+ },
7792
+ children: link.text
7793
+ }),
7794
+ details && /* @__PURE__ */ jsx("button", {
7795
+ onClick: () => setDetailsExpanded(!detailsExpanded),
7796
+ style: {
7797
+ background: "transparent",
7798
+ border: `1px solid ${colors.border}`,
7799
+ borderRadius: "5px",
7800
+ padding: "4px 10px",
7801
+ fontSize: "11px",
7802
+ fontWeight: "500",
7803
+ cursor: "pointer",
7804
+ color: colors.text,
7805
+ flexShrink: 0,
7806
+ transition: "all 0.2s ease"
7807
+ },
7808
+ onMouseEnter: (e) => {
7809
+ e.currentTarget.style.background = "rgba(0, 0, 0, 0.05)";
7810
+ },
7811
+ onMouseLeave: (e) => {
7812
+ e.currentTarget.style.background = "transparent";
7813
+ },
7814
+ children: detailsExpanded ? "Hide Details" : "Show Details"
7815
+ })
7816
+ ]
7817
+ })]
7818
+ }), /* @__PURE__ */ jsx("button", {
7819
+ onClick: onDismiss,
7820
+ style: {
7821
+ background: "transparent",
7822
+ border: "none",
7823
+ color: colors.text,
7824
+ cursor: "pointer",
7825
+ padding: "2px",
7826
+ borderRadius: "3px",
7827
+ fontSize: "14px",
7828
+ lineHeight: "1",
7829
+ opacity: .6,
7830
+ transition: "all 0.2s ease",
7831
+ flexShrink: 0
7832
+ },
7833
+ title: "Dismiss",
7834
+ onMouseEnter: (e) => {
7835
+ e.currentTarget.style.opacity = "1";
7836
+ e.currentTarget.style.background = "rgba(0, 0, 0, 0.05)";
7837
+ },
7838
+ onMouseLeave: (e) => {
7839
+ e.currentTarget.style.opacity = "0.6";
7840
+ e.currentTarget.style.background = "transparent";
7841
+ },
7842
+ children: "x"
7843
+ })]
7844
+ }), detailsExpanded && details && /* @__PURE__ */ jsxs("div", {
7845
+ style: {
7846
+ marginTop: "10px",
7847
+ padding: "10px",
7848
+ background: "rgba(0, 0, 0, 0.04)",
7849
+ borderRadius: "6px",
7850
+ fontSize: "11px",
7851
+ fontFamily: "monospace",
7852
+ color: colors.text,
7853
+ lineHeight: "1.5",
7854
+ maxHeight: "200px",
7855
+ overflowY: "auto",
7856
+ whiteSpace: "pre-wrap",
7857
+ wordBreak: "break-all"
7858
+ },
7859
+ children: [
7860
+ details.code && /* @__PURE__ */ jsxs("div", { children: [
7861
+ /* @__PURE__ */ jsx("strong", { children: "Code:" }),
7862
+ " ",
7863
+ details.code
7864
+ ] }),
7865
+ details.originalMessage && /* @__PURE__ */ jsxs("div", {
7866
+ style: { marginTop: "4px" },
7867
+ children: [
7868
+ /* @__PURE__ */ jsx("strong", { children: "Message:" }),
7869
+ " ",
7870
+ details.originalMessage
7871
+ ]
7872
+ }),
7873
+ details.context && Object.keys(details.context).length > 0 && /* @__PURE__ */ jsxs("div", {
7874
+ style: { marginTop: "4px" },
7875
+ children: [
7876
+ /* @__PURE__ */ jsx("strong", { children: "Context:" }),
7877
+ " ",
7878
+ JSON.stringify(details.context, null, 2)
7879
+ ]
7880
+ }),
7881
+ details.stack && /* @__PURE__ */ jsxs("div", {
7882
+ style: {
7883
+ marginTop: "4px",
7884
+ opacity: .7
7885
+ },
7886
+ children: [
7887
+ /* @__PURE__ */ jsx("strong", { children: "Stack:" }),
7888
+ "\n",
7889
+ details.stack
7890
+ ]
7891
+ })
7892
+ ]
7893
+ })]
7894
+ });
7895
+ }
5845
7896
  function ToastProvider({ enabled, children }) {
5846
7897
  const [toasts, setToasts] = useState([]);
5847
7898
  const [bannerError, setBannerErrorState] = useState(null);
@@ -5879,156 +7930,10 @@ function ToastProvider({ enabled, children }) {
5879
7930
  };
5880
7931
  return /* @__PURE__ */ jsxs(ToastContext.Provider, {
5881
7932
  value,
5882
- children: [bannerError && (() => {
5883
- const colors = getErrorColors(getErrorSeverity(bannerError));
5884
- return /* @__PURE__ */ jsx("div", {
5885
- style: {
5886
- position: "fixed",
5887
- bottom: "20px",
5888
- left: "50%",
5889
- transform: "translateX(-50%)",
5890
- zIndex: 9999,
5891
- backgroundColor: colors.background,
5892
- border: `1px solid ${colors.border}`,
5893
- borderLeft: `4px solid ${colors.border}`,
5894
- borderRadius: "8px",
5895
- padding: "12px 16px",
5896
- fontSize: "13px",
5897
- boxShadow: "0 4px 12px rgba(0, 0, 0, 0.15)",
5898
- backdropFilter: "blur(8px)",
5899
- maxWidth: "min(90vw, 700px)",
5900
- width: "100%",
5901
- boxSizing: "border-box",
5902
- overflow: "hidden"
5903
- },
5904
- children: /* @__PURE__ */ jsxs("div", {
5905
- style: {
5906
- display: "flex",
5907
- justifyContent: "space-between",
5908
- alignItems: "center",
5909
- gap: "10px"
5910
- },
5911
- children: [/* @__PURE__ */ jsxs("div", {
5912
- style: {
5913
- display: "flex",
5914
- alignItems: "center",
5915
- gap: "8px",
5916
- flex: 1,
5917
- minWidth: 0
5918
- },
5919
- children: [/* @__PURE__ */ jsx("div", { style: {
5920
- width: "12px",
5921
- height: "12px",
5922
- borderRadius: "50%",
5923
- backgroundColor: colors.border,
5924
- flexShrink: 0
5925
- } }), /* @__PURE__ */ jsxs("div", {
5926
- style: {
5927
- display: "flex",
5928
- alignItems: "center",
5929
- gap: "10px",
5930
- flex: 1,
5931
- minWidth: 0
5932
- },
5933
- children: [/* @__PURE__ */ jsx("div", {
5934
- style: {
5935
- color: colors.text,
5936
- lineHeight: "1.4",
5937
- fontWeight: "400",
5938
- fontSize: "13px",
5939
- flex: 1,
5940
- wordBreak: "break-all",
5941
- overflowWrap: "break-word",
5942
- maxWidth: "550px",
5943
- overflow: "hidden",
5944
- display: "-webkit-box",
5945
- WebkitLineClamp: 10,
5946
- WebkitBoxOrient: "vertical"
5947
- },
5948
- children: (() => {
5949
- let message = bannerError.message;
5950
- const jsonMatch = message.match(/'message':\s*'([^']+)'/);
5951
- if (jsonMatch) return jsonMatch[1];
5952
- message = message.split(" - ")[0];
5953
- message = message.split(": Error code")[0];
5954
- message = message.replace(/:\s*\d{3}$/, "");
5955
- message = message.replace(/See more:.*$/g, "");
5956
- message = message.trim();
5957
- return message || "Configuration error occurred.";
5958
- })()
5959
- }), (() => {
5960
- const message = bannerError.message;
5961
- const markdownLinkRegex = /\[([^\]]+)\]\(([^)]+)\)/g;
5962
- const plainUrlRegex = /(https?:\/\/[^\s)]+)/g;
5963
- let url = null;
5964
- let buttonText = "See More";
5965
- const markdownMatch = markdownLinkRegex.exec(message);
5966
- if (markdownMatch) {
5967
- url = markdownMatch[2];
5968
- buttonText = "See More";
5969
- } else {
5970
- const urlMatch = plainUrlRegex.exec(message);
5971
- if (urlMatch) {
5972
- url = urlMatch[0].replace(/[.,;:'"]*$/, "");
5973
- buttonText = "See More";
5974
- }
5975
- }
5976
- if (!url) return null;
5977
- return /* @__PURE__ */ jsx("button", {
5978
- onClick: () => window.open(url, "_blank", "noopener,noreferrer"),
5979
- style: {
5980
- background: colors.border,
5981
- color: "white",
5982
- border: "none",
5983
- borderRadius: "5px",
5984
- padding: "4px 10px",
5985
- fontSize: "11px",
5986
- fontWeight: "500",
5987
- cursor: "pointer",
5988
- transition: "all 0.2s ease",
5989
- flexShrink: 0
5990
- },
5991
- onMouseEnter: (e) => {
5992
- e.currentTarget.style.opacity = "0.9";
5993
- e.currentTarget.style.transform = "translateY(-1px)";
5994
- },
5995
- onMouseLeave: (e) => {
5996
- e.currentTarget.style.opacity = "1";
5997
- e.currentTarget.style.transform = "translateY(0)";
5998
- },
5999
- children: buttonText
6000
- });
6001
- })()]
6002
- })]
6003
- }), /* @__PURE__ */ jsx("button", {
6004
- onClick: () => setBannerError(null),
6005
- style: {
6006
- background: "transparent",
6007
- border: "none",
6008
- color: colors.text,
6009
- cursor: "pointer",
6010
- padding: "2px",
6011
- borderRadius: "3px",
6012
- fontSize: "14px",
6013
- lineHeight: "1",
6014
- opacity: .6,
6015
- transition: "all 0.2s ease",
6016
- flexShrink: 0
6017
- },
6018
- title: "Dismiss",
6019
- onMouseEnter: (e) => {
6020
- e.currentTarget.style.opacity = "1";
6021
- e.currentTarget.style.background = "rgba(0, 0, 0, 0.05)";
6022
- },
6023
- onMouseLeave: (e) => {
6024
- e.currentTarget.style.opacity = "0.6";
6025
- e.currentTarget.style.background = "transparent";
6026
- },
6027
- children: "×"
6028
- })]
6029
- })
6030
- });
6031
- })(), children]
7933
+ children: [bannerError && /* @__PURE__ */ jsx(BannerErrorDisplay, {
7934
+ bannerError,
7935
+ onDismiss: () => setBannerError(null)
7936
+ }), children]
6032
7937
  });
6033
7938
  }
6034
7939
 
@@ -7035,12 +8940,21 @@ function CopilotListeners() {
7035
8940
  const { agent } = useAgent({ agentId: resolvedAgentId });
7036
8941
  usePredictStateSubscription(agent);
7037
8942
  useEffect(() => {
7038
- const subscription = copilotkit.subscribe({ onError: ({ error }) => {
7039
- setBannerError(new CopilotKitLowLevelError({
8943
+ const subscription = copilotkit.subscribe({ onError: ({ error, code, context }) => {
8944
+ if (error.name === "AbortError" || error.message === "Fetch is aborted" || error.message === "signal is aborted without reason" || error.message === "component unmounted" || !error.message) return;
8945
+ if (process.env.NODE_ENV === "development") console.error("[CopilotKit] Agent error:", error.message, "\n Code:", code, "\n Context:", context, "\n Stack:", error.stack);
8946
+ const ckError = new CopilotKitLowLevelError({
7040
8947
  error,
7041
8948
  message: error.message,
7042
8949
  url: typeof window !== "undefined" ? window.location.href : ""
7043
- }));
8950
+ });
8951
+ ckError.details = {
8952
+ code,
8953
+ context,
8954
+ stack: error.stack,
8955
+ originalMessage: error.message
8956
+ };
8957
+ setBannerError(ckError);
7044
8958
  } });
7045
8959
  return () => {
7046
8960
  subscription.unsubscribe();
@@ -7514,7 +9428,7 @@ function CopilotKitInternal(cpkProps) {
7514
9428
  children: [
7515
9429
  /* @__PURE__ */ jsx(CopilotListeners, {}),
7516
9430
  /* @__PURE__ */ jsx(CopilotKitErrorBridge, {}),
7517
- /* @__PURE__ */ jsxs(CoAgentStateRendersProvider, { children: [/* @__PURE__ */ jsx(MessagesTapProvider, { children: /* @__PURE__ */ jsxs(CopilotMessages, { children: [memoizedChildren, /* @__PURE__ */ jsx(RegisteredActionsRenderer, {})] }) }), bannerError && showDevConsole && /* @__PURE__ */ jsx(UsageBanner, {
9431
+ /* @__PURE__ */ jsxs(CoAgentStateRendersProvider, { children: [/* @__PURE__ */ jsx(MessagesTapProvider, { children: /* @__PURE__ */ jsxs(CopilotMessages, { children: [/* @__PURE__ */ jsx(React.Fragment, { children: memoizedChildren }, "children"), /* @__PURE__ */ jsx(RegisteredActionsRenderer, {}, "actions")] }) }), bannerError && showDevConsole && /* @__PURE__ */ jsx(UsageBanner, {
7518
9432
  severity: bannerError.severity,
7519
9433
  message: bannerError.message,
7520
9434
  onClose: () => setBannerError(null),
@@ -7558,5 +9472,5 @@ function validateProps(props) {
7558
9472
  }
7559
9473
 
7560
9474
  //#endregion
7561
- export { CopilotKitCoreReact as $, CopilotChatSuggestionPill as A, UseAgentUpdate as B, CopilotChatToggleButton as C, CopilotChatView_default as D, CopilotChat as E, useThreads$1 as F, defineToolCallRenderer as G, useHumanInTheLoop as H, useInterrupt as I, useRenderActivityMessage as J, useComponent as K, useConfigureSuggestions as L, CopilotChatUserMessage_default as M, CopilotChatAssistantMessage_default as N, CopilotChatMessageView as O, CopilotChatToolCallsView as P, useCopilotKit as Q, useSuggestions as R, CopilotModalHeader as S, DefaultOpenIcon as T, useDefaultRenderTool as U, useAgent as V, useRenderTool as W, useRenderToolCall as X, useRenderCustomMessages as Y, CopilotKitProvider as Z, WildcardToolCallRender as _, ThreadsProvider as a, CopilotChatInput_default as at, CopilotPopupView as b, CoAgentStateRendersProvider as c, CopilotChatConfigurationProvider as ct, shouldShowDevConsole as d, createA2UIMessageRenderer as et, useToast as f, useCopilotContext as g, CopilotContext as h, ThreadsContext as i, CopilotKitInspector as it, CopilotChatReasoningMessage_default as j, CopilotChatSuggestionView as k, useCoAgentStateRenders as l, useCopilotChatConfiguration as lt, useCopilotMessagesContext as m, defaultCopilotContextCategories as n, MCPAppsActivityRenderer as nt, useThreads as o, AudioRecorderError as ot, CopilotMessagesContext as p, useFrontendTool as q, CoAgentStateRenderBridge as r, MCPAppsActivityType as rt, CoAgentStateRendersContext as s, CopilotChatAudioRecorder as st, CopilotKit as t, MCPAppsActivityContentSchema as tt, useAsyncCallback as u, CopilotPopup as v, DefaultCloseIcon as w, CopilotSidebarView as x, CopilotSidebar as y, useAgentContext as z };
7562
- //# sourceMappingURL=copilotkit-DNYSFuz5.mjs.map
9475
+ export { useCopilotKit as $, CopilotChatSuggestionView as A, useConfigureSuggestions as B, CopilotChatToggleButton as C, CopilotChatView_default as D, CopilotChat as E, CopilotChatAssistantMessage_default as F, useComponent as G, useHumanInTheLoop as H, CopilotChatToolCallsView as I, useRenderCustomMessages as J, useFrontendTool as K, useAttachments as L, CopilotChatReasoningMessage_default as M, CopilotChatUserMessage_default as N, CopilotChatAttachmentQueue as O, CopilotChatAttachmentRenderer as P, CopilotKitProvider as Q, useThreads$1 as R, CopilotModalHeader as S, DefaultOpenIcon as T, useDefaultRenderTool as U, useSuggestions as V, useRenderTool as W, useAgent as X, UseAgentUpdate as Y, useRenderToolCall as Z, WildcardToolCallRender as _, ThreadsProvider as a, useSandboxFunctions as at, CopilotPopupView as b, CoAgentStateRendersProvider as c, MCPAppsActivityType as ct, shouldShowDevConsole as d, AudioRecorderError as dt, CopilotKitCoreReact as et, useToast as f, CopilotChatAudioRecorder as ft, useCopilotContext as g, CopilotContext as h, ThreadsContext as i, SandboxFunctionsContext as it, CopilotChatSuggestionPill as j, CopilotChatMessageView as k, useCoAgentStateRenders as l, CopilotKitInspector as lt, useCopilotMessagesContext as m, useCopilotChatConfiguration as mt, defaultCopilotContextCategories as n, defineToolCallRenderer as nt, useThreads as o, MCPAppsActivityContentSchema as ot, CopilotMessagesContext as p, CopilotChatConfigurationProvider as pt, useRenderActivityMessage as q, CoAgentStateRenderBridge as r, createA2UIMessageRenderer as rt, CoAgentStateRendersContext as s, MCPAppsActivityRenderer as st, CopilotKit as t, useAgentContext as tt, useAsyncCallback as u, CopilotChatInput_default as ut, CopilotPopup as v, DefaultCloseIcon as w, CopilotSidebarView as x, CopilotSidebar as y, useInterrupt as z };
9476
+ //# sourceMappingURL=copilotkit-BY5S1-0P.mjs.map