@copilotkit/react-core 1.56.0 → 1.56.2-canary.pin-to-send

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 (60) hide show
  1. package/dist/{copilotkit-BebqQrYT.mjs → copilotkit-BBYbekCa.mjs} +265 -76
  2. package/dist/copilotkit-BBYbekCa.mjs.map +1 -0
  3. package/dist/{copilotkit-Cvb6WpAX.cjs → copilotkit-D5JT2Pu3.cjs} +264 -75
  4. package/dist/copilotkit-D5JT2Pu3.cjs.map +1 -0
  5. package/dist/{copilotkit-f2Uq0RwG.d.mts → copilotkit-DArT2Iuw.d.mts} +71 -18
  6. package/dist/copilotkit-DArT2Iuw.d.mts.map +1 -0
  7. package/dist/{copilotkit-Dv8zU8_U.d.cts → copilotkit-KEc28l8G.d.cts} +71 -18
  8. package/dist/copilotkit-KEc28l8G.d.cts.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.cts +1 -1
  11. package/dist/index.d.mts +1 -1
  12. package/dist/index.mjs +1 -1
  13. package/dist/index.umd.js +30 -46
  14. package/dist/index.umd.js.map +1 -1
  15. package/dist/v2/index.cjs +1 -1
  16. package/dist/v2/index.css +1 -1
  17. package/dist/v2/index.d.cts +2 -2
  18. package/dist/v2/index.d.mts +2 -2
  19. package/dist/v2/index.mjs +1 -1
  20. package/dist/v2/index.umd.js +264 -79
  21. package/dist/v2/index.umd.js.map +1 -1
  22. package/package.json +6 -6
  23. package/src/components/CopilotListeners.tsx +15 -4
  24. package/src/components/__tests__/CopilotListeners.test.tsx +38 -0
  25. package/src/v2/components/chat/CopilotChat.tsx +80 -4
  26. package/src/v2/components/chat/CopilotChatAssistantMessage.tsx +4 -4
  27. package/src/v2/components/chat/CopilotChatInput.tsx +43 -2
  28. package/src/v2/components/chat/CopilotChatView.tsx +206 -11
  29. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
  30. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +300 -2
  31. package/src/v2/components/chat/__tests__/CopilotChatAssistantMessage.thumbs.test.tsx +72 -0
  32. package/src/v2/components/chat/__tests__/CopilotChatInput.test.tsx +38 -0
  33. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
  34. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
  35. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
  36. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
  37. package/src/v2/components/chat/index.ts +2 -0
  38. package/src/v2/components/chat/last-user-message-context.ts +21 -0
  39. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  40. package/src/v2/components/license-warning-banner.tsx +20 -1
  41. package/src/v2/components/ui/button.tsx +12 -11
  42. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
  43. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
  44. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
  45. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -0
  46. package/src/v2/hooks/__tests__/use-render-custom-messages.test.tsx +55 -0
  47. package/src/v2/hooks/__tests__/use-threads.test.tsx +68 -0
  48. package/src/v2/hooks/use-agent.tsx +34 -77
  49. package/src/v2/hooks/use-pin-to-send.ts +94 -0
  50. package/src/v2/hooks/use-render-custom-messages.tsx +1 -1
  51. package/src/v2/hooks/use-render-tool-call.tsx +3 -0
  52. package/src/v2/hooks/use-render-tool.tsx +3 -0
  53. package/src/v2/hooks/use-threads.tsx +55 -12
  54. package/src/v2/providers/CopilotKitProvider.tsx +2 -11
  55. package/src/v2/types/defineToolCallRenderer.ts +3 -0
  56. package/src/v2/types/react-tool-call-renderer.ts +3 -0
  57. package/dist/copilotkit-BebqQrYT.mjs.map +0 -1
  58. package/dist/copilotkit-Cvb6WpAX.cjs.map +0 -1
  59. package/dist/copilotkit-Dv8zU8_U.d.cts.map +0 -1
  60. package/dist/copilotkit-f2Uq0RwG.d.mts.map +0 -1
@@ -18,7 +18,7 @@ import { A2UIProvider, A2UIRenderer, A2UI_SCHEMA_CONTEXT_DESCRIPTION, DEFAULT_SU
18
18
  import { zodToJsonSchema } from "zod-to-json-schema";
19
19
  import { useVirtualizer } from "@tanstack/react-virtual";
20
20
  import { createPortal, flushSync } from "react-dom";
21
- import { StickToBottom, useStickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
21
+ import { StickToBottom, useStickToBottomContext } from "use-stick-to-bottom";
22
22
  import ReactMarkdown from "react-markdown";
23
23
 
24
24
  //#region src/v2/lib/slots.tsx
@@ -269,8 +269,9 @@ const buttonVariants = cva("cpk:inline-flex cpk:items-center cpk:justify-center
269
269
  size: "default"
270
270
  }
271
271
  });
272
- function Button({ className, variant, size, asChild = false, ...props }) {
272
+ const Button = React$1.forwardRef(function Button({ className, variant, size, asChild = false, ...props }, ref) {
273
273
  return /* @__PURE__ */ jsx(asChild ? Slot : "button", {
274
+ ref,
274
275
  "data-slot": "button",
275
276
  className: cn(buttonVariants({
276
277
  variant,
@@ -279,7 +280,7 @@ function Button({ className, variant, size, asChild = false, ...props }) {
279
280
  })),
280
281
  ...props
281
282
  });
282
- }
283
+ });
283
284
 
284
285
  //#endregion
285
286
  //#region src/v2/components/ui/tooltip.tsx
@@ -579,7 +580,7 @@ CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
579
580
  //#region src/v2/components/chat/CopilotChatInput.tsx
580
581
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
581
582
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
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 }) {
583
+ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, bottomAnchored = false, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
583
584
  const isControlled = value !== void 0;
584
585
  const [internalValue, setInternalValue] = useState(() => value ?? "");
585
586
  useEffect(() => {
@@ -721,6 +722,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
721
722
  });
722
723
  }, [clearInputValue]);
723
724
  const handleKeyDown = (e) => {
725
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
724
726
  if (commandQuery !== null && mode === "input") {
725
727
  if (e.key === "ArrowDown") {
726
728
  if (filteredCommands.length > 0) {
@@ -768,10 +770,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
768
770
  const trimmed = resolvedValue.trim();
769
771
  if (!trimmed) return;
770
772
  onSubmitMessage(trimmed);
771
- if (!isControlled) {
772
- setInternalValue("");
773
- onChange?.("");
774
- }
773
+ if (!isControlled) setInternalValue("");
774
+ onChange?.("");
775
775
  if (inputRef.current) inputRef.current.focus();
776
776
  };
777
777
  const BoundTextArea = renderSlot(textArea, CopilotChatInput.TextArea, {
@@ -779,6 +779,12 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
779
779
  value: resolvedValue,
780
780
  onChange: handleChange,
781
781
  onKeyDown: handleKeyDown,
782
+ onCompositionStart: () => {
783
+ isComposingRef.current = true;
784
+ },
785
+ onCompositionEnd: () => {
786
+ isComposingRef.current = false;
787
+ },
782
788
  autoFocus,
783
789
  className: twMerge("cpk:w-full cpk:py-3", isExpanded ? "cpk:px-5" : "cpk:pr-5")
784
790
  });
@@ -853,9 +859,10 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
853
859
  const target = e.target;
854
860
  if (target.tagName !== "BUTTON" && !target.closest("button") && inputRef.current && mode === "input") inputRef.current.focus();
855
861
  };
862
+ const isComposingRef = useRef(false);
856
863
  const ensureMeasurements = useCallback(() => {
857
864
  const textarea = inputRef.current;
858
- if (!textarea) return;
865
+ if (!textarea || isComposingRef.current) return;
859
866
  const previousValue = textarea.value;
860
867
  const previousHeight = textarea.style.height;
861
868
  textarea.style.height = "auto";
@@ -1097,7 +1104,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1097
1104
  className: cn("cpk:pointer-events-none cpk:relative cpk:z-20", positioning === "absolute" && "cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-0", className),
1098
1105
  style: {
1099
1106
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1100
- transition: "transform 0.2s ease-out"
1107
+ transition: "transform 0.2s ease-out",
1108
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1101
1109
  },
1102
1110
  ...props,
1103
1111
  children: [/* @__PURE__ */ jsx("div", {
@@ -1312,6 +1320,8 @@ CopilotKitInspector.displayName = "CopilotKitInspector";
1312
1320
 
1313
1321
  //#endregion
1314
1322
  //#region src/v2/components/license-warning-banner.tsx
1323
+ const LICENSE_BANNER_OFFSET_PX = 52;
1324
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1315
1325
  const BANNER_STYLES = {
1316
1326
  base: {
1317
1327
  position: "fixed",
@@ -1353,6 +1363,14 @@ function getSeverityStyle(severity) {
1353
1363
  }
1354
1364
  }
1355
1365
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1366
+ useEffect(() => {
1367
+ if (typeof document === "undefined") return;
1368
+ const root = document.documentElement;
1369
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1370
+ return () => {
1371
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1372
+ };
1373
+ }, []);
1356
1374
  return /* @__PURE__ */ jsxs("div", {
1357
1375
  style: {
1358
1376
  ...BANNER_STYLES.base,
@@ -3305,7 +3323,6 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = {}, c
3305
3323
  didMountRef.current = true;
3306
3324
  }, []);
3307
3325
  useEffect(() => {
3308
- 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.`);
3309
3326
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3310
3327
  }, [copilotkit, defaultThrottleMs]);
3311
3328
  const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
@@ -3403,18 +3420,21 @@ const ToolCallRenderer = React.memo(function ToolCallRenderer({ toolCall, toolMe
3403
3420
  const toolName = toolCall.function.name;
3404
3421
  if (toolMessage) return /* @__PURE__ */ jsx(RenderComponent, {
3405
3422
  name: toolName,
3423
+ toolCallId: toolCall.id,
3406
3424
  args,
3407
3425
  status: ToolCallStatus.Complete,
3408
3426
  result: toolMessage.content
3409
3427
  });
3410
3428
  else if (isExecuting) return /* @__PURE__ */ jsx(RenderComponent, {
3411
3429
  name: toolName,
3430
+ toolCallId: toolCall.id,
3412
3431
  args,
3413
3432
  status: ToolCallStatus.Executing,
3414
3433
  result: void 0
3415
3434
  });
3416
3435
  else return /* @__PURE__ */ jsx(RenderComponent, {
3417
3436
  name: toolName,
3437
+ toolCallId: toolCall.id,
3418
3438
  args,
3419
3439
  status: ToolCallStatus.InProgress,
3420
3440
  result: void 0
@@ -3519,15 +3539,6 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3519
3539
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3520
3540
  const chatConfig = useCopilotChatConfiguration();
3521
3541
  threadId ??= chatConfig?.threadId;
3522
- const effectiveThrottleMs = useMemo(() => {
3523
- const resolved = throttleMs ?? providerThrottleMs ?? 0;
3524
- if (!Number.isFinite(resolved) || resolved < 0) {
3525
- const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3526
- console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3527
- return 0;
3528
- }
3529
- return resolved;
3530
- }, [throttleMs, providerThrottleMs]);
3531
3542
  const [, forceUpdate] = useReducer((x) => x + 1, 0);
3532
3543
  const updateFlags = useMemo(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3533
3544
  const provisionalAgentCache = useRef(/* @__PURE__ */ new Map());
@@ -3590,9 +3601,8 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3590
3601
  ]);
3591
3602
  useEffect(() => {
3592
3603
  if (updateFlags.length === 0) return;
3593
- const handlers = {};
3594
- let timerId = null;
3595
3604
  let active = true;
3605
+ const handlers = {};
3596
3606
  let batchScheduled = false;
3597
3607
  const batchedForceUpdate = () => {
3598
3608
  if (!active) return;
@@ -3604,46 +3614,24 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3604
3614
  });
3605
3615
  }
3606
3616
  };
3607
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3608
- const ms = effectiveThrottleMs;
3609
- if (ms > 0) {
3610
- let throttleActive = false;
3611
- let pending = false;
3612
- const throttledNotify = () => {
3613
- if (!active) return;
3614
- if (!throttleActive) {
3615
- throttleActive = true;
3616
- pending = false;
3617
- forceUpdate();
3618
- timerId = setTimeout(function trailingEdge() {
3619
- timerId = null;
3620
- if (active && pending) {
3621
- pending = false;
3622
- forceUpdate();
3623
- timerId = setTimeout(trailingEdge, ms);
3624
- } else throttleActive = false;
3625
- }, ms);
3626
- } else pending = true;
3627
- };
3628
- handlers.onMessagesChanged = throttledNotify;
3629
- } else handlers.onMessagesChanged = forceUpdate;
3630
- }
3617
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3631
3618
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3632
3619
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3633
3620
  handlers.onRunInitialized = batchedForceUpdate;
3634
3621
  handlers.onRunFinalized = batchedForceUpdate;
3635
3622
  handlers.onRunFailed = batchedForceUpdate;
3623
+ handlers.onRunErrorEvent = batchedForceUpdate;
3636
3624
  }
3637
- const subscription = agent.subscribe(handlers);
3625
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3638
3626
  return () => {
3639
3627
  active = false;
3640
- if (timerId !== null) clearTimeout(timerId);
3641
3628
  subscription.unsubscribe();
3642
3629
  };
3643
3630
  }, [
3644
3631
  agent,
3645
3632
  forceUpdate,
3646
- effectiveThrottleMs,
3633
+ throttleMs,
3634
+ providerThrottleMs,
3647
3635
  updateFlags
3648
3636
  ]);
3649
3637
  useEffect(() => {
@@ -3671,7 +3659,7 @@ function useRenderCustomMessages() {
3671
3659
  const runId = resolvedRunId ?? `missing-run-id:${message.id}`;
3672
3660
  const registryAgent = copilotkit.getAgent(agentId);
3673
3661
  const agent = getThreadClone(registryAgent, threadId) ?? registryAgent;
3674
- if (!agent) throw new Error("Agent not found");
3662
+ if (!agent) return null;
3675
3663
  const messagesIdsInRun = resolvedRunId ? agent.messages.filter((msg) => copilotkit.getRunIdForMessage(agentId, threadId, msg.id) === resolvedRunId).map((msg) => msg.id) : [message.id];
3676
3664
  const rawMessageIndex = agent.messages.findIndex((msg) => msg.id === message.id);
3677
3665
  const messageIndex = rawMessageIndex >= 0 ? rawMessageIndex : 0;
@@ -4597,13 +4585,14 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4597
4585
  const { copilotkit } = useCopilotKit();
4598
4586
  const [store] = useState(() => ɵcreateThreadStore({ fetch: globalThis.fetch }));
4599
4587
  const coreThreads = useThreadStoreSelector(store, ɵselectThreads);
4600
- const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4588
+ const threads = useMemo(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4601
4589
  id,
4602
4590
  agentId,
4603
4591
  name,
4604
4592
  archived,
4605
4593
  createdAt,
4606
- updatedAt
4594
+ updatedAt,
4595
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4607
4596
  })), [coreThreads]);
4608
4597
  const storeIsLoading = useThreadStoreSelector(store, ɵselectThreadsIsLoading);
4609
4598
  const storeError = useThreadStoreSelector(store, ɵselectThreadsError);
@@ -4616,7 +4605,9 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4616
4605
  if (copilotkit.runtimeUrl) return null;
4617
4606
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4618
4607
  }, [copilotkit.runtimeUrl]);
4619
- const isLoading = runtimeError ? false : storeIsLoading;
4608
+ const [hasDispatchedContext, setHasDispatchedContext] = useState(false);
4609
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4610
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4620
4611
  const error = runtimeError ?? storeError;
4621
4612
  useEffect(() => {
4622
4613
  store.start();
@@ -4624,19 +4615,27 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4624
4615
  store.stop();
4625
4616
  };
4626
4617
  }, [store]);
4618
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4627
4619
  useEffect(() => {
4628
- const context = copilotkit.runtimeUrl ? {
4620
+ if (!copilotkit.runtimeUrl) {
4621
+ store.setContext(null);
4622
+ return;
4623
+ }
4624
+ if (runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4625
+ const context = {
4629
4626
  runtimeUrl: copilotkit.runtimeUrl,
4630
4627
  headers: { ...copilotkit.headers },
4631
4628
  wsUrl: copilotkit.intelligence?.wsUrl,
4632
4629
  agentId,
4633
4630
  includeArchived,
4634
4631
  limit
4635
- } : null;
4632
+ };
4636
4633
  store.setContext(context);
4634
+ setHasDispatchedContext(true);
4637
4635
  }, [
4638
4636
  store,
4639
4637
  copilotkit.runtimeUrl,
4638
+ runtimeStatus,
4640
4639
  headersKey,
4641
4640
  copilotkit.intelligence?.wsUrl,
4642
4641
  agentId,
@@ -4841,10 +4840,10 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, onThumbsUp,
4841
4840
  if (message.content) return await copyToClipboard(message.content);
4842
4841
  return false;
4843
4842
  } });
4844
- const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp });
4845
- const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown });
4846
- const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud });
4847
- const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate });
4843
+ const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp ? () => onThumbsUp(message) : void 0 });
4844
+ const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown ? () => onThumbsDown(message) : void 0 });
4845
+ const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud ? () => onReadAloud(message) : void 0 });
4846
+ const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate ? () => onRegenerate(message) : void 0 });
4848
4847
  const boundToolbar = renderSlot(toolbar, CopilotChatAssistantMessage.Toolbar, { children: /* @__PURE__ */ jsxs("div", {
4849
4848
  className: "cpk:flex cpk:items-center cpk:gap-1",
4850
4849
  children: [
@@ -6161,9 +6160,97 @@ function useKeyboardHeight() {
6161
6160
  return keyboardState;
6162
6161
  }
6163
6162
 
6163
+ //#endregion
6164
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6165
+ const VALID = [
6166
+ "pin-to-bottom",
6167
+ "pin-to-send",
6168
+ "none"
6169
+ ];
6170
+ function normalizeAutoScroll(value) {
6171
+ if (value === void 0) return "pin-to-bottom";
6172
+ if (value === true) return "pin-to-bottom";
6173
+ if (value === false) return "none";
6174
+ if (VALID.includes(value)) return value;
6175
+ return "pin-to-bottom";
6176
+ }
6177
+
6178
+ //#endregion
6179
+ //#region src/v2/components/chat/last-user-message-context.ts
6180
+ const LastUserMessageContext = React.createContext({
6181
+ id: null,
6182
+ sendNonce: 0
6183
+ });
6184
+
6185
+ //#endregion
6186
+ //#region src/v2/hooks/use-pin-to-send.ts
6187
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6188
+ const { id, sendNonce } = useContext(LastUserMessageContext);
6189
+ const lastNonceRef = useRef(-1);
6190
+ const currentSpacerHeightRef = useRef(0);
6191
+ useEffect(() => {
6192
+ if (sendNonce === lastNonceRef.current) return;
6193
+ lastNonceRef.current = sendNonce;
6194
+ if (!id) return;
6195
+ const scrollEl = scrollRef.current;
6196
+ const contentEl = contentRef.current;
6197
+ const spacerEl = spacerRef.current;
6198
+ if (!scrollEl || !contentEl || !spacerEl) return;
6199
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6200
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6201
+ if (!targetEl) return;
6202
+ const viewportHeight = scrollEl.clientHeight;
6203
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6204
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6205
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6206
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6207
+ spacerEl.style.height = `${spacerHeight}px`;
6208
+ currentSpacerHeightRef.current = spacerHeight;
6209
+ const raf = requestAnimationFrame(() => {
6210
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6211
+ scrollEl.scrollTo({
6212
+ top: Math.max(0, targetTop),
6213
+ behavior: "smooth"
6214
+ });
6215
+ });
6216
+ const ro = new ResizeObserver(() => {
6217
+ if (!contentEl || !spacerEl || !scrollEl) return;
6218
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6219
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6220
+ if (remaining < currentSpacerHeightRef.current) {
6221
+ spacerEl.style.height = `${remaining}px`;
6222
+ currentSpacerHeightRef.current = remaining;
6223
+ }
6224
+ });
6225
+ ro.observe(contentEl);
6226
+ return () => {
6227
+ cancelAnimationFrame(raf);
6228
+ ro.disconnect();
6229
+ };
6230
+ }, [
6231
+ id,
6232
+ sendNonce,
6233
+ scrollRef,
6234
+ contentRef,
6235
+ spacerRef,
6236
+ topOffset
6237
+ ]);
6238
+ }
6239
+ function computeOffsetTop(el, stopAt) {
6240
+ const elRect = el.getBoundingClientRect();
6241
+ const stopRect = stopAt.getBoundingClientRect();
6242
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6243
+ }
6244
+
6164
6245
  //#endregion
6165
6246
  //#region src/v2/components/chat/CopilotChatView.tsx
6166
6247
  const FEATHER_HEIGHT = 96;
6248
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6249
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ jsx("div", {
6250
+ className: cn("cpk:absolute cpk:bottom-0 cpk:left-0 cpk:right-4 cpk:h-12 cpk:pointer-events-none cpk:z-10 cpk:bg-gradient-to-t", "cpk:from-white cpk:to-transparent", "cpk:dark:from-[rgb(33,33,33)]", className),
6251
+ style,
6252
+ ...props
6253
+ });
6167
6254
  function DropOverlay() {
6168
6255
  return /* @__PURE__ */ jsx("div", {
6169
6256
  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"),
@@ -6176,7 +6263,7 @@ function DropOverlay() {
6176
6263
  })
6177
6264
  });
6178
6265
  }
6179
- 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 }) {
6266
+ function CopilotChatView({ messageView, input, scrollView, suggestionView, welcomeScreen, messages = [], autoScroll = true, isRunning = false, suggestions, suggestionLoadingIndexes, onSelectSuggestion, onSubmitMessage, onStop, inputMode, inputValue, onInputChange, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, attachments, onRemoveAttachment, onAddFile, dragOver, onDragOver, onDragLeave, onDrop, isConnecting = false, hasExplicitThreadId = false, disclaimer, children, className, ...props }) {
6180
6267
  const inputContainerRef = useRef(null);
6181
6268
  const [inputContainerHeight, setInputContainerHeight] = useState(0);
6182
6269
  const [isResizing, setIsResizing] = useState(false);
@@ -6228,9 +6315,10 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6228
6315
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6229
6316
  containerRef: inputContainerRef,
6230
6317
  showDisclaimer: true,
6318
+ bottomAnchored: true,
6231
6319
  ...disclaimer !== void 0 ? { disclaimer } : {}
6232
6320
  });
6233
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6321
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6234
6322
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6235
6323
  suggestions,
6236
6324
  loadingIndexes: suggestionLoadingIndexes,
@@ -6252,7 +6340,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6252
6340
  })
6253
6341
  })
6254
6342
  });
6255
- if (messages.length === 0 && !(welcomeScreen === false)) {
6343
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6256
6344
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6257
6345
  onSubmitMessage,
6258
6346
  onStop,
@@ -6359,9 +6447,60 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6359
6447
  ] })
6360
6448
  });
6361
6449
  };
6362
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6450
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6451
+ const spacerRef = useRef(null);
6452
+ usePinToSend({
6453
+ scrollRef,
6454
+ contentRef,
6455
+ spacerRef,
6456
+ topOffset: 16
6457
+ });
6458
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6459
+ return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6460
+ value: nonAutoScrollEl,
6461
+ children: /* @__PURE__ */ jsxs("div", {
6462
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6463
+ children: [
6464
+ /* @__PURE__ */ jsxs("div", {
6465
+ ref: nonAutoScrollRefCallback,
6466
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6467
+ ...props,
6468
+ children: [/* @__PURE__ */ jsx("div", {
6469
+ ref: contentRef,
6470
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6471
+ children
6472
+ }), /* @__PURE__ */ jsx("div", {
6473
+ ref: spacerRef,
6474
+ "data-pin-to-send-spacer": true,
6475
+ "aria-hidden": "true",
6476
+ style: {
6477
+ height: 0,
6478
+ flex: "0 0 auto"
6479
+ }
6480
+ })]
6481
+ }),
6482
+ BoundFeather,
6483
+ showScrollButton && !isResizing && /* @__PURE__ */ jsx("div", {
6484
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6485
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6486
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6487
+ })
6488
+ ]
6489
+ })
6490
+ });
6491
+ };
6492
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6493
+ const mode = normalizeAutoScroll(autoScroll);
6363
6494
  const [hasMounted, setHasMounted] = useState(false);
6364
- const { scrollRef, contentRef, scrollToBottom } = useStickToBottom();
6495
+ const scrollRef = useRef(null);
6496
+ const contentRef = useRef(null);
6497
+ const scrollToBottom = useCallback(() => {
6498
+ const el = scrollRef.current;
6499
+ if (el) el.scrollTo({
6500
+ top: el.scrollHeight,
6501
+ behavior: "smooth"
6502
+ });
6503
+ }, []);
6365
6504
  const [showScrollButton, setShowScrollButton] = useState(false);
6366
6505
  const [nonAutoScrollEl, setNonAutoScrollEl] = useState(null);
6367
6506
  const nonAutoScrollRefCallback = useCallback((el) => {
@@ -6372,7 +6511,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6372
6511
  setHasMounted(true);
6373
6512
  }, []);
6374
6513
  useEffect(() => {
6375
- if (autoScroll) return;
6514
+ if (mode === "pin-to-bottom") return;
6376
6515
  const scrollElement = scrollRef.current;
6377
6516
  if (!scrollElement) return;
6378
6517
  const checkScroll = () => {
@@ -6386,7 +6525,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6386
6525
  scrollElement.removeEventListener("scroll", checkScroll);
6387
6526
  resizeObserver.disconnect();
6388
6527
  };
6389
- }, [scrollRef, autoScroll]);
6528
+ }, [scrollRef, mode]);
6390
6529
  if (!hasMounted) return /* @__PURE__ */ jsx("div", {
6391
6530
  className: "cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6392
6531
  children: /* @__PURE__ */ jsx("div", {
@@ -6394,7 +6533,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6394
6533
  children
6395
6534
  })
6396
6535
  });
6397
- if (!autoScroll) {
6536
+ if (mode === "none") {
6398
6537
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6399
6538
  return /* @__PURE__ */ jsx(ScrollElementContext.Provider, {
6400
6539
  value: nonAutoScrollEl,
@@ -6418,6 +6557,21 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6418
6557
  })
6419
6558
  });
6420
6559
  }
6560
+ if (mode === "pin-to-send") return /* @__PURE__ */ jsx(PinToSendScrollContainer, {
6561
+ scrollRef,
6562
+ contentRef,
6563
+ scrollToBottom,
6564
+ scrollToBottomButton,
6565
+ feather,
6566
+ inputContainerHeight,
6567
+ isResizing,
6568
+ nonAutoScrollEl,
6569
+ nonAutoScrollRefCallback,
6570
+ showScrollButton,
6571
+ className,
6572
+ ...props,
6573
+ children
6574
+ });
6421
6575
  return /* @__PURE__ */ jsx(StickToBottom, {
6422
6576
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6423
6577
  resize: "smooth",
@@ -6610,7 +6764,8 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
6610
6764
  function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
6611
6765
  const existingConfig = useCopilotChatConfiguration();
6612
6766
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? DEFAULT_AGENT_ID;
6613
- const resolvedThreadId = useMemo(() => threadId ?? existingConfig?.threadId ?? randomUUID(), [threadId, existingConfig?.threadId]);
6767
+ const providedThreadId = threadId ?? existingConfig?.threadId;
6768
+ const resolvedThreadId = useMemo(() => providedThreadId ?? randomUUID(), [providedThreadId]);
6614
6769
  const { agent } = useAgent({
6615
6770
  agentId: resolvedAgentId,
6616
6771
  threadId: resolvedThreadId,
@@ -6648,7 +6803,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6648
6803
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6649
6804
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6650
6805
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6806
+ const [lastConnectedThreadId, setLastConnectedThreadId] = useState(null);
6807
+ const isConnecting = !!providedThreadId && lastConnectedThreadId !== resolvedThreadId;
6651
6808
  useEffect(() => {
6809
+ if (!providedThreadId) return;
6652
6810
  let detached = false;
6653
6811
  const connectAbortController = new AbortController();
6654
6812
  if (agent instanceof HttpAgent) agent.abortController = connectAbortController;
@@ -6658,6 +6816,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6658
6816
  } catch (error) {
6659
6817
  if (detached) return;
6660
6818
  console.error("CopilotChat: connectAgent failed", error);
6819
+ } finally {
6820
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6821
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6822
+ });
6661
6823
  }
6662
6824
  };
6663
6825
  connect(agent);
@@ -6669,7 +6831,8 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6669
6831
  }, [
6670
6832
  resolvedThreadId,
6671
6833
  agent,
6672
- resolvedAgentId
6834
+ resolvedAgentId,
6835
+ providedThreadId
6673
6836
  ]);
6674
6837
  const onSubmitInput = useCallback(async (value) => {
6675
6838
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6822,6 +6985,22 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6822
6985
  const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6823
6986
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6824
6987
  }).join(",")]);
6988
+ const lastUserMessageId = useMemo(() => {
6989
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
6990
+ return null;
6991
+ }, [messages]);
6992
+ const [sendNonce, setSendNonce] = useState(0);
6993
+ const prevLastUserMessageIdRef = useRef(lastUserMessageId);
6994
+ useEffect(() => {
6995
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
6996
+ setSendNonce((n) => n + 1);
6997
+ prevLastUserMessageIdRef.current = lastUserMessageId;
6998
+ }
6999
+ }, [lastUserMessageId]);
7000
+ const lastUserMessageState = useMemo(() => ({
7001
+ id: lastUserMessageId,
7002
+ sendNonce
7003
+ }), [lastUserMessageId, sendNonce]);
6825
7004
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6826
7005
  ...mergedProps,
6827
7006
  messages,
@@ -6840,7 +7019,9 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6840
7019
  dragOver,
6841
7020
  onDragOver: handleDragOver,
6842
7021
  onDragLeave: handleDragLeave,
6843
- onDrop: handleDrop
7022
+ onDrop: handleDrop,
7023
+ isConnecting,
7024
+ hasExplicitThreadId: !!providedThreadId
6844
7025
  });
6845
7026
  return /* @__PURE__ */ jsx(CopilotChatConfigurationProvider, {
6846
7027
  agentId: resolvedAgentId,
@@ -6875,7 +7056,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6875
7056
  },
6876
7057
  children: transcriptionError
6877
7058
  }),
6878
- RenderedChatView
7059
+ /* @__PURE__ */ jsx(LastUserMessageContext.Provider, {
7060
+ value: lastUserMessageState,
7061
+ children: RenderedChatView
7062
+ })
6879
7063
  ]
6880
7064
  })
6881
7065
  });
@@ -9013,12 +9197,17 @@ const usePredictStateSubscription = (agent) => {
9013
9197
  };
9014
9198
  }, [agent, getSubscriber]);
9015
9199
  };
9016
- function CopilotListeners() {
9017
- const { copilotkit } = useCopilotKit();
9200
+ function CopilotListenersAgentSubscription() {
9018
9201
  const resolvedAgentId = useCopilotChatConfiguration()?.agentId;
9019
- const { setBannerError } = useToast();
9020
9202
  const { agent } = useAgent({ agentId: resolvedAgentId });
9021
9203
  usePredictStateSubscription(agent);
9204
+ return null;
9205
+ }
9206
+ function CopilotListeners() {
9207
+ const { copilotkit } = useCopilotKit();
9208
+ const { setBannerError } = useToast();
9209
+ const hasAgents = Object.keys(copilotkit.agents ?? {}).length > 0;
9210
+ const hasRuntime = copilotkit.runtimeUrl !== void 0;
9022
9211
  useEffect(() => {
9023
9212
  const subscription = copilotkit.subscribe({ onError: ({ error, code, context }) => {
9024
9213
  if (error.name === "AbortError" || error.message === "Fetch is aborted" || error.message === "signal is aborted without reason" || error.message === "component unmounted" || !error.message) return;
@@ -9040,7 +9229,7 @@ function CopilotListeners() {
9040
9229
  subscription.unsubscribe();
9041
9230
  };
9042
9231
  }, [copilotkit?.subscribe]);
9043
- return null;
9232
+ return hasAgents || hasRuntime ? /* @__PURE__ */ jsx(CopilotListenersAgentSubscription, {}) : null;
9044
9233
  }
9045
9234
 
9046
9235
  //#endregion
@@ -9553,4 +9742,4 @@ function validateProps(props) {
9553
9742
 
9554
9743
  //#endregion
9555
9744
  export { CopilotKitProvider as $, CopilotChatSuggestionView as A, useConfigureSuggestions as B, CopilotChatToggleButton as C, CopilotChatView_default as D, CopilotChat as E, CopilotChatAssistantMessage_default as F, useRenderTool as G, useCapabilities as H, CopilotChatToolCallsView as I, useRenderActivityMessage as J, useComponent as K, useAttachments as L, CopilotChatReasoningMessage_default as M, CopilotChatUserMessage_default as N, CopilotChatAttachmentQueue as O, CopilotChatAttachmentRenderer as P, useRenderToolCall as Q, useThreads$1 as R, CopilotModalHeader as S, DefaultOpenIcon as T, useHumanInTheLoop as U, useSuggestions as V, useDefaultRenderTool as W, UseAgentUpdate as X, useRenderCustomMessages as Y, useAgent as Z, WildcardToolCallRender as _, ThreadsProvider as a, SandboxFunctionsContext as at, CopilotPopupView as b, CoAgentStateRendersProvider as c, MCPAppsActivityRenderer as ct, shouldShowDevConsole as d, CopilotChatInput_default as dt, useCopilotKit as et, useToast as f, AudioRecorderError as ft, useCopilotContext as g, CopilotContext as h, useCopilotChatConfiguration as ht, ThreadsContext as i, createA2UIMessageRenderer as it, CopilotChatSuggestionPill as j, CopilotChatMessageView as k, useCoAgentStateRenders as l, MCPAppsActivityType as lt, useCopilotMessagesContext as m, CopilotChatConfigurationProvider as mt, defaultCopilotContextCategories as n, useAgentContext as nt, useThreads as o, useSandboxFunctions as ot, CopilotMessagesContext as p, CopilotChatAudioRecorder as pt, useFrontendTool as q, CoAgentStateRenderBridge as r, defineToolCallRenderer as rt, CoAgentStateRendersContext as s, MCPAppsActivityContentSchema as st, CopilotKit as t, CopilotKitCoreReact as tt, useAsyncCallback as u, CopilotKitInspector as ut, CopilotPopup as v, DefaultCloseIcon as w, CopilotSidebarView as x, CopilotSidebar as y, useInterrupt as z };
9556
- //# sourceMappingURL=copilotkit-BebqQrYT.mjs.map
9745
+ //# sourceMappingURL=copilotkit-BBYbekCa.mjs.map