@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
@@ -299,8 +299,9 @@ const buttonVariants = (0, class_variance_authority.cva)("cpk:inline-flex cpk:it
299
299
  size: "default"
300
300
  }
301
301
  });
302
- function Button({ className, variant, size, asChild = false, ...props }) {
302
+ const Button = react.forwardRef(function Button({ className, variant, size, asChild = false, ...props }, ref) {
303
303
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(asChild ? _radix_ui_react_slot.Slot : "button", {
304
+ ref,
304
305
  "data-slot": "button",
305
306
  className: cn(buttonVariants({
306
307
  variant,
@@ -309,7 +310,7 @@ function Button({ className, variant, size, asChild = false, ...props }) {
309
310
  })),
310
311
  ...props
311
312
  });
312
- }
313
+ });
313
314
 
314
315
  //#endregion
315
316
  //#region src/v2/components/ui/tooltip.tsx
@@ -609,7 +610,7 @@ CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
609
610
  //#region src/v2/components/chat/CopilotChatInput.tsx
610
611
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
611
612
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
612
- function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning = false, onStartTranscribe, onCancelTranscribe, onFinishTranscribe, onFinishTranscribeWithAudio, onAddFile, onChange, value, toolsMenu, autoFocus = false, positioning = "static", keyboardHeight = 0, containerRef, showDisclaimer, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
613
+ 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 }) {
613
614
  const isControlled = value !== void 0;
614
615
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
615
616
  (0, react.useEffect)(() => {
@@ -751,6 +752,7 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
751
752
  });
752
753
  }, [clearInputValue]);
753
754
  const handleKeyDown = (e) => {
755
+ if (e.nativeEvent.isComposing || e.keyCode === 229) return;
754
756
  if (commandQuery !== null && mode === "input") {
755
757
  if (e.key === "ArrowDown") {
756
758
  if (filteredCommands.length > 0) {
@@ -798,10 +800,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
798
800
  const trimmed = resolvedValue.trim();
799
801
  if (!trimmed) return;
800
802
  onSubmitMessage(trimmed);
801
- if (!isControlled) {
802
- setInternalValue("");
803
- onChange?.("");
804
- }
803
+ if (!isControlled) setInternalValue("");
804
+ onChange?.("");
805
805
  if (inputRef.current) inputRef.current.focus();
806
806
  };
807
807
  const BoundTextArea = renderSlot(textArea, CopilotChatInput.TextArea, {
@@ -809,6 +809,12 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
809
809
  value: resolvedValue,
810
810
  onChange: handleChange,
811
811
  onKeyDown: handleKeyDown,
812
+ onCompositionStart: () => {
813
+ isComposingRef.current = true;
814
+ },
815
+ onCompositionEnd: () => {
816
+ isComposingRef.current = false;
817
+ },
812
818
  autoFocus,
813
819
  className: (0, tailwind_merge.twMerge)("cpk:w-full cpk:py-3", isExpanded ? "cpk:px-5" : "cpk:pr-5")
814
820
  });
@@ -883,9 +889,10 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
883
889
  const target = e.target;
884
890
  if (target.tagName !== "BUTTON" && !target.closest("button") && inputRef.current && mode === "input") inputRef.current.focus();
885
891
  };
892
+ const isComposingRef = (0, react.useRef)(false);
886
893
  const ensureMeasurements = (0, react.useCallback)(() => {
887
894
  const textarea = inputRef.current;
888
- if (!textarea) return;
895
+ if (!textarea || isComposingRef.current) return;
889
896
  const previousValue = textarea.value;
890
897
  const previousHeight = textarea.style.height;
891
898
  textarea.style.height = "auto";
@@ -1127,7 +1134,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1127
1134
  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),
1128
1135
  style: {
1129
1136
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1130
- transition: "transform 0.2s ease-out"
1137
+ transition: "transform 0.2s ease-out",
1138
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1131
1139
  },
1132
1140
  ...props,
1133
1141
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1342,6 +1350,8 @@ CopilotKitInspector.displayName = "CopilotKitInspector";
1342
1350
 
1343
1351
  //#endregion
1344
1352
  //#region src/v2/components/license-warning-banner.tsx
1353
+ const LICENSE_BANNER_OFFSET_PX = 52;
1354
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1345
1355
  const BANNER_STYLES = {
1346
1356
  base: {
1347
1357
  position: "fixed",
@@ -1383,6 +1393,14 @@ function getSeverityStyle(severity) {
1383
1393
  }
1384
1394
  }
1385
1395
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1396
+ (0, react.useEffect)(() => {
1397
+ if (typeof document === "undefined") return;
1398
+ const root = document.documentElement;
1399
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1400
+ return () => {
1401
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1402
+ };
1403
+ }, []);
1386
1404
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1387
1405
  style: {
1388
1406
  ...BANNER_STYLES.base,
@@ -3335,7 +3353,6 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = {}, c
3335
3353
  didMountRef.current = true;
3336
3354
  }, []);
3337
3355
  (0, react.useEffect)(() => {
3338
- 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.`);
3339
3356
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3340
3357
  }, [copilotkit, defaultThrottleMs]);
3341
3358
  const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
@@ -3433,18 +3450,21 @@ const ToolCallRenderer = react.default.memo(function ToolCallRenderer({ toolCall
3433
3450
  const toolName = toolCall.function.name;
3434
3451
  if (toolMessage) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
3435
3452
  name: toolName,
3453
+ toolCallId: toolCall.id,
3436
3454
  args,
3437
3455
  status: _copilotkit_core.ToolCallStatus.Complete,
3438
3456
  result: toolMessage.content
3439
3457
  });
3440
3458
  else if (isExecuting) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
3441
3459
  name: toolName,
3460
+ toolCallId: toolCall.id,
3442
3461
  args,
3443
3462
  status: _copilotkit_core.ToolCallStatus.Executing,
3444
3463
  result: void 0
3445
3464
  });
3446
3465
  else return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RenderComponent, {
3447
3466
  name: toolName,
3467
+ toolCallId: toolCall.id,
3448
3468
  args,
3449
3469
  status: _copilotkit_core.ToolCallStatus.InProgress,
3450
3470
  result: void 0
@@ -3549,15 +3569,6 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3549
3569
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3550
3570
  const chatConfig = useCopilotChatConfiguration();
3551
3571
  threadId ??= chatConfig?.threadId;
3552
- const effectiveThrottleMs = (0, react.useMemo)(() => {
3553
- const resolved = throttleMs ?? providerThrottleMs ?? 0;
3554
- if (!Number.isFinite(resolved) || resolved < 0) {
3555
- const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3556
- console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3557
- return 0;
3558
- }
3559
- return resolved;
3560
- }, [throttleMs, providerThrottleMs]);
3561
3572
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
3562
3573
  const updateFlags = (0, react.useMemo)(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3563
3574
  const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
@@ -3620,9 +3631,8 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3620
3631
  ]);
3621
3632
  (0, react.useEffect)(() => {
3622
3633
  if (updateFlags.length === 0) return;
3623
- const handlers = {};
3624
- let timerId = null;
3625
3634
  let active = true;
3635
+ const handlers = {};
3626
3636
  let batchScheduled = false;
3627
3637
  const batchedForceUpdate = () => {
3628
3638
  if (!active) return;
@@ -3634,46 +3644,24 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3634
3644
  });
3635
3645
  }
3636
3646
  };
3637
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3638
- const ms = effectiveThrottleMs;
3639
- if (ms > 0) {
3640
- let throttleActive = false;
3641
- let pending = false;
3642
- const throttledNotify = () => {
3643
- if (!active) return;
3644
- if (!throttleActive) {
3645
- throttleActive = true;
3646
- pending = false;
3647
- forceUpdate();
3648
- timerId = setTimeout(function trailingEdge() {
3649
- timerId = null;
3650
- if (active && pending) {
3651
- pending = false;
3652
- forceUpdate();
3653
- timerId = setTimeout(trailingEdge, ms);
3654
- } else throttleActive = false;
3655
- }, ms);
3656
- } else pending = true;
3657
- };
3658
- handlers.onMessagesChanged = throttledNotify;
3659
- } else handlers.onMessagesChanged = forceUpdate;
3660
- }
3647
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3661
3648
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3662
3649
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3663
3650
  handlers.onRunInitialized = batchedForceUpdate;
3664
3651
  handlers.onRunFinalized = batchedForceUpdate;
3665
3652
  handlers.onRunFailed = batchedForceUpdate;
3653
+ handlers.onRunErrorEvent = batchedForceUpdate;
3666
3654
  }
3667
- const subscription = agent.subscribe(handlers);
3655
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3668
3656
  return () => {
3669
3657
  active = false;
3670
- if (timerId !== null) clearTimeout(timerId);
3671
3658
  subscription.unsubscribe();
3672
3659
  };
3673
3660
  }, [
3674
3661
  agent,
3675
3662
  forceUpdate,
3676
- effectiveThrottleMs,
3663
+ throttleMs,
3664
+ providerThrottleMs,
3677
3665
  updateFlags
3678
3666
  ]);
3679
3667
  (0, react.useEffect)(() => {
@@ -3701,7 +3689,7 @@ function useRenderCustomMessages() {
3701
3689
  const runId = resolvedRunId ?? `missing-run-id:${message.id}`;
3702
3690
  const registryAgent = copilotkit.getAgent(agentId);
3703
3691
  const agent = getThreadClone(registryAgent, threadId) ?? registryAgent;
3704
- if (!agent) throw new Error("Agent not found");
3692
+ if (!agent) return null;
3705
3693
  const messagesIdsInRun = resolvedRunId ? agent.messages.filter((msg) => copilotkit.getRunIdForMessage(agentId, threadId, msg.id) === resolvedRunId).map((msg) => msg.id) : [message.id];
3706
3694
  const rawMessageIndex = agent.messages.findIndex((msg) => msg.id === message.id);
3707
3695
  const messageIndex = rawMessageIndex >= 0 ? rawMessageIndex : 0;
@@ -4627,13 +4615,14 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4627
4615
  const { copilotkit } = useCopilotKit();
4628
4616
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
4629
4617
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4630
- const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4618
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4631
4619
  id,
4632
4620
  agentId,
4633
4621
  name,
4634
4622
  archived,
4635
4623
  createdAt,
4636
- updatedAt
4624
+ updatedAt,
4625
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4637
4626
  })), [coreThreads]);
4638
4627
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
4639
4628
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
@@ -4646,7 +4635,9 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4646
4635
  if (copilotkit.runtimeUrl) return null;
4647
4636
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4648
4637
  }, [copilotkit.runtimeUrl]);
4649
- const isLoading = runtimeError ? false : storeIsLoading;
4638
+ const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
4639
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4640
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4650
4641
  const error = runtimeError ?? storeError;
4651
4642
  (0, react.useEffect)(() => {
4652
4643
  store.start();
@@ -4654,19 +4645,27 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4654
4645
  store.stop();
4655
4646
  };
4656
4647
  }, [store]);
4648
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4657
4649
  (0, react.useEffect)(() => {
4658
- const context = copilotkit.runtimeUrl ? {
4650
+ if (!copilotkit.runtimeUrl) {
4651
+ store.setContext(null);
4652
+ return;
4653
+ }
4654
+ if (runtimeStatus !== _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4655
+ const context = {
4659
4656
  runtimeUrl: copilotkit.runtimeUrl,
4660
4657
  headers: { ...copilotkit.headers },
4661
4658
  wsUrl: copilotkit.intelligence?.wsUrl,
4662
4659
  agentId,
4663
4660
  includeArchived,
4664
4661
  limit
4665
- } : null;
4662
+ };
4666
4663
  store.setContext(context);
4664
+ setHasDispatchedContext(true);
4667
4665
  }, [
4668
4666
  store,
4669
4667
  copilotkit.runtimeUrl,
4668
+ runtimeStatus,
4670
4669
  headersKey,
4671
4670
  copilotkit.intelligence?.wsUrl,
4672
4671
  agentId,
@@ -4871,10 +4870,10 @@ function CopilotChatAssistantMessage({ message, messages, isRunning, onThumbsUp,
4871
4870
  if (message.content) return await (0, _copilotkit_shared.copyToClipboard)(message.content);
4872
4871
  return false;
4873
4872
  } });
4874
- const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp });
4875
- const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown });
4876
- const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud });
4877
- const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate });
4873
+ const boundThumbsUpButton = renderSlot(thumbsUpButton, CopilotChatAssistantMessage.ThumbsUpButton, { onClick: onThumbsUp ? () => onThumbsUp(message) : void 0 });
4874
+ const boundThumbsDownButton = renderSlot(thumbsDownButton, CopilotChatAssistantMessage.ThumbsDownButton, { onClick: onThumbsDown ? () => onThumbsDown(message) : void 0 });
4875
+ const boundReadAloudButton = renderSlot(readAloudButton, CopilotChatAssistantMessage.ReadAloudButton, { onClick: onReadAloud ? () => onReadAloud(message) : void 0 });
4876
+ const boundRegenerateButton = renderSlot(regenerateButton, CopilotChatAssistantMessage.RegenerateButton, { onClick: onRegenerate ? () => onRegenerate(message) : void 0 });
4878
4877
  const boundToolbar = renderSlot(toolbar, CopilotChatAssistantMessage.Toolbar, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4879
4878
  className: "cpk:flex cpk:items-center cpk:gap-1",
4880
4879
  children: [
@@ -6191,9 +6190,97 @@ function useKeyboardHeight() {
6191
6190
  return keyboardState;
6192
6191
  }
6193
6192
 
6193
+ //#endregion
6194
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6195
+ const VALID = [
6196
+ "pin-to-bottom",
6197
+ "pin-to-send",
6198
+ "none"
6199
+ ];
6200
+ function normalizeAutoScroll(value) {
6201
+ if (value === void 0) return "pin-to-bottom";
6202
+ if (value === true) return "pin-to-bottom";
6203
+ if (value === false) return "none";
6204
+ if (VALID.includes(value)) return value;
6205
+ return "pin-to-bottom";
6206
+ }
6207
+
6208
+ //#endregion
6209
+ //#region src/v2/components/chat/last-user-message-context.ts
6210
+ const LastUserMessageContext = react.default.createContext({
6211
+ id: null,
6212
+ sendNonce: 0
6213
+ });
6214
+
6215
+ //#endregion
6216
+ //#region src/v2/hooks/use-pin-to-send.ts
6217
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6218
+ const { id, sendNonce } = (0, react.useContext)(LastUserMessageContext);
6219
+ const lastNonceRef = (0, react.useRef)(-1);
6220
+ const currentSpacerHeightRef = (0, react.useRef)(0);
6221
+ (0, react.useEffect)(() => {
6222
+ if (sendNonce === lastNonceRef.current) return;
6223
+ lastNonceRef.current = sendNonce;
6224
+ if (!id) return;
6225
+ const scrollEl = scrollRef.current;
6226
+ const contentEl = contentRef.current;
6227
+ const spacerEl = spacerRef.current;
6228
+ if (!scrollEl || !contentEl || !spacerEl) return;
6229
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6230
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6231
+ if (!targetEl) return;
6232
+ const viewportHeight = scrollEl.clientHeight;
6233
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6234
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6235
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6236
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6237
+ spacerEl.style.height = `${spacerHeight}px`;
6238
+ currentSpacerHeightRef.current = spacerHeight;
6239
+ const raf = requestAnimationFrame(() => {
6240
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6241
+ scrollEl.scrollTo({
6242
+ top: Math.max(0, targetTop),
6243
+ behavior: "smooth"
6244
+ });
6245
+ });
6246
+ const ro = new ResizeObserver(() => {
6247
+ if (!contentEl || !spacerEl || !scrollEl) return;
6248
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6249
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6250
+ if (remaining < currentSpacerHeightRef.current) {
6251
+ spacerEl.style.height = `${remaining}px`;
6252
+ currentSpacerHeightRef.current = remaining;
6253
+ }
6254
+ });
6255
+ ro.observe(contentEl);
6256
+ return () => {
6257
+ cancelAnimationFrame(raf);
6258
+ ro.disconnect();
6259
+ };
6260
+ }, [
6261
+ id,
6262
+ sendNonce,
6263
+ scrollRef,
6264
+ contentRef,
6265
+ spacerRef,
6266
+ topOffset
6267
+ ]);
6268
+ }
6269
+ function computeOffsetTop(el, stopAt) {
6270
+ const elRect = el.getBoundingClientRect();
6271
+ const stopRect = stopAt.getBoundingClientRect();
6272
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6273
+ }
6274
+
6194
6275
  //#endregion
6195
6276
  //#region src/v2/components/chat/CopilotChatView.tsx
6196
6277
  const FEATHER_HEIGHT = 96;
6278
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6279
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6280
+ 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),
6281
+ style,
6282
+ ...props
6283
+ });
6197
6284
  function DropOverlay() {
6198
6285
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6199
6286
  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"),
@@ -6206,7 +6293,7 @@ function DropOverlay() {
6206
6293
  })
6207
6294
  });
6208
6295
  }
6209
- 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 }) {
6296
+ 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 }) {
6210
6297
  const inputContainerRef = (0, react.useRef)(null);
6211
6298
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6212
6299
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -6258,9 +6345,10 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6258
6345
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6259
6346
  containerRef: inputContainerRef,
6260
6347
  showDisclaimer: true,
6348
+ bottomAnchored: true,
6261
6349
  ...disclaimer !== void 0 ? { disclaimer } : {}
6262
6350
  });
6263
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6351
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6264
6352
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6265
6353
  suggestions,
6266
6354
  loadingIndexes: suggestionLoadingIndexes,
@@ -6282,7 +6370,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6282
6370
  })
6283
6371
  })
6284
6372
  });
6285
- if (messages.length === 0 && !(welcomeScreen === false)) {
6373
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6286
6374
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6287
6375
  onSubmitMessage,
6288
6376
  onStop,
@@ -6389,9 +6477,60 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6389
6477
  ] })
6390
6478
  });
6391
6479
  };
6392
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6480
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6481
+ const spacerRef = (0, react.useRef)(null);
6482
+ usePinToSend({
6483
+ scrollRef,
6484
+ contentRef,
6485
+ spacerRef,
6486
+ topOffset: 16
6487
+ });
6488
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6489
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6490
+ value: nonAutoScrollEl,
6491
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6492
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6493
+ children: [
6494
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6495
+ ref: nonAutoScrollRefCallback,
6496
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6497
+ ...props,
6498
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6499
+ ref: contentRef,
6500
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6501
+ children
6502
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6503
+ ref: spacerRef,
6504
+ "data-pin-to-send-spacer": true,
6505
+ "aria-hidden": "true",
6506
+ style: {
6507
+ height: 0,
6508
+ flex: "0 0 auto"
6509
+ }
6510
+ })]
6511
+ }),
6512
+ BoundFeather,
6513
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6514
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6515
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6516
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6517
+ })
6518
+ ]
6519
+ })
6520
+ });
6521
+ };
6522
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6523
+ const mode = normalizeAutoScroll(autoScroll);
6393
6524
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
6394
- const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
6525
+ const scrollRef = (0, react.useRef)(null);
6526
+ const contentRef = (0, react.useRef)(null);
6527
+ const scrollToBottom = (0, react.useCallback)(() => {
6528
+ const el = scrollRef.current;
6529
+ if (el) el.scrollTo({
6530
+ top: el.scrollHeight,
6531
+ behavior: "smooth"
6532
+ });
6533
+ }, []);
6395
6534
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6396
6535
  const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6397
6536
  const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
@@ -6402,7 +6541,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6402
6541
  setHasMounted(true);
6403
6542
  }, []);
6404
6543
  (0, react.useEffect)(() => {
6405
- if (autoScroll) return;
6544
+ if (mode === "pin-to-bottom") return;
6406
6545
  const scrollElement = scrollRef.current;
6407
6546
  if (!scrollElement) return;
6408
6547
  const checkScroll = () => {
@@ -6416,7 +6555,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6416
6555
  scrollElement.removeEventListener("scroll", checkScroll);
6417
6556
  resizeObserver.disconnect();
6418
6557
  };
6419
- }, [scrollRef, autoScroll]);
6558
+ }, [scrollRef, mode]);
6420
6559
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6421
6560
  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",
6422
6561
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6424,7 +6563,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6424
6563
  children
6425
6564
  })
6426
6565
  });
6427
- if (!autoScroll) {
6566
+ if (mode === "none") {
6428
6567
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6429
6568
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6430
6569
  value: nonAutoScrollEl,
@@ -6448,6 +6587,21 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6448
6587
  })
6449
6588
  });
6450
6589
  }
6590
+ if (mode === "pin-to-send") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinToSendScrollContainer, {
6591
+ scrollRef,
6592
+ contentRef,
6593
+ scrollToBottom,
6594
+ scrollToBottomButton,
6595
+ feather,
6596
+ inputContainerHeight,
6597
+ isResizing,
6598
+ nonAutoScrollEl,
6599
+ nonAutoScrollRefCallback,
6600
+ showScrollButton,
6601
+ className,
6602
+ ...props,
6603
+ children
6604
+ });
6451
6605
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
6452
6606
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6453
6607
  resize: "smooth",
@@ -6640,7 +6794,8 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
6640
6794
  function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
6641
6795
  const existingConfig = useCopilotChatConfiguration();
6642
6796
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
6643
- const resolvedThreadId = (0, react.useMemo)(() => threadId ?? existingConfig?.threadId ?? (0, _copilotkit_shared.randomUUID)(), [threadId, existingConfig?.threadId]);
6797
+ const providedThreadId = threadId ?? existingConfig?.threadId;
6798
+ const resolvedThreadId = (0, react.useMemo)(() => providedThreadId ?? (0, _copilotkit_shared.randomUUID)(), [providedThreadId]);
6644
6799
  const { agent } = useAgent({
6645
6800
  agentId: resolvedAgentId,
6646
6801
  threadId: resolvedThreadId,
@@ -6678,7 +6833,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6678
6833
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6679
6834
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6680
6835
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6836
+ const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
6837
+ const isConnecting = !!providedThreadId && lastConnectedThreadId !== resolvedThreadId;
6681
6838
  (0, react.useEffect)(() => {
6839
+ if (!providedThreadId) return;
6682
6840
  let detached = false;
6683
6841
  const connectAbortController = new AbortController();
6684
6842
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -6688,6 +6846,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6688
6846
  } catch (error) {
6689
6847
  if (detached) return;
6690
6848
  console.error("CopilotChat: connectAgent failed", error);
6849
+ } finally {
6850
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6851
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6852
+ });
6691
6853
  }
6692
6854
  };
6693
6855
  connect(agent);
@@ -6699,7 +6861,8 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6699
6861
  }, [
6700
6862
  resolvedThreadId,
6701
6863
  agent,
6702
- resolvedAgentId
6864
+ resolvedAgentId,
6865
+ providedThreadId
6703
6866
  ]);
6704
6867
  const onSubmitInput = (0, react.useCallback)(async (value) => {
6705
6868
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6852,6 +7015,22 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6852
7015
  const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6853
7016
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6854
7017
  }).join(",")]);
7018
+ const lastUserMessageId = (0, react.useMemo)(() => {
7019
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7020
+ return null;
7021
+ }, [messages]);
7022
+ const [sendNonce, setSendNonce] = (0, react.useState)(0);
7023
+ const prevLastUserMessageIdRef = (0, react.useRef)(lastUserMessageId);
7024
+ (0, react.useEffect)(() => {
7025
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7026
+ setSendNonce((n) => n + 1);
7027
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7028
+ }
7029
+ }, [lastUserMessageId]);
7030
+ const lastUserMessageState = (0, react.useMemo)(() => ({
7031
+ id: lastUserMessageId,
7032
+ sendNonce
7033
+ }), [lastUserMessageId, sendNonce]);
6855
7034
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6856
7035
  ...mergedProps,
6857
7036
  messages,
@@ -6870,7 +7049,9 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6870
7049
  dragOver,
6871
7050
  onDragOver: handleDragOver,
6872
7051
  onDragLeave: handleDragLeave,
6873
- onDrop: handleDrop
7052
+ onDrop: handleDrop,
7053
+ isConnecting,
7054
+ hasExplicitThreadId: !!providedThreadId
6874
7055
  });
6875
7056
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
6876
7057
  agentId: resolvedAgentId,
@@ -6905,7 +7086,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6905
7086
  },
6906
7087
  children: transcriptionError
6907
7088
  }),
6908
- RenderedChatView
7089
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastUserMessageContext.Provider, {
7090
+ value: lastUserMessageState,
7091
+ children: RenderedChatView
7092
+ })
6909
7093
  ]
6910
7094
  })
6911
7095
  });
@@ -9043,12 +9227,17 @@ const usePredictStateSubscription = (agent) => {
9043
9227
  };
9044
9228
  }, [agent, getSubscriber]);
9045
9229
  };
9046
- function CopilotListeners() {
9047
- const { copilotkit } = useCopilotKit();
9230
+ function CopilotListenersAgentSubscription() {
9048
9231
  const resolvedAgentId = useCopilotChatConfiguration()?.agentId;
9049
- const { setBannerError } = useToast();
9050
9232
  const { agent } = useAgent({ agentId: resolvedAgentId });
9051
9233
  usePredictStateSubscription(agent);
9234
+ return null;
9235
+ }
9236
+ function CopilotListeners() {
9237
+ const { copilotkit } = useCopilotKit();
9238
+ const { setBannerError } = useToast();
9239
+ const hasAgents = Object.keys(copilotkit.agents ?? {}).length > 0;
9240
+ const hasRuntime = copilotkit.runtimeUrl !== void 0;
9052
9241
  (0, react.useEffect)(() => {
9053
9242
  const subscription = copilotkit.subscribe({ onError: ({ error, code, context }) => {
9054
9243
  if (error.name === "AbortError" || error.message === "Fetch is aborted" || error.message === "signal is aborted without reason" || error.message === "component unmounted" || !error.message) return;
@@ -9070,7 +9259,7 @@ function CopilotListeners() {
9070
9259
  subscription.unsubscribe();
9071
9260
  };
9072
9261
  }, [copilotkit?.subscribe]);
9073
- return null;
9262
+ return hasAgents || hasRuntime ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotListenersAgentSubscription, {}) : null;
9074
9263
  }
9075
9264
 
9076
9265
  //#endregion
@@ -10002,4 +10191,4 @@ Object.defineProperty(exports, 'useToast', {
10002
10191
  return useToast;
10003
10192
  }
10004
10193
  });
10005
- //# sourceMappingURL=copilotkit-Cvb6WpAX.cjs.map
10194
+ //# sourceMappingURL=copilotkit-D5JT2Pu3.cjs.map