@copilotkit/react-core 1.56.1 → 1.56.2-canary.test-welcome-screen

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 (57) hide show
  1. package/dist/{copilotkit-CSJw5BG8.cjs → copilotkit-By2G6-Zx.cjs} +250 -63
  2. package/dist/copilotkit-By2G6-Zx.cjs.map +1 -0
  3. package/dist/{copilotkit-CCbxm6JM.d.mts → copilotkit-DFaI4j2r.d.mts} +64 -18
  4. package/dist/copilotkit-DFaI4j2r.d.mts.map +1 -0
  5. package/dist/{copilotkit-BtP7w7cT.d.cts → copilotkit-Dg4r4Gi_.d.cts} +64 -18
  6. package/dist/copilotkit-Dg4r4Gi_.d.cts.map +1 -0
  7. package/dist/{copilotkit-Cj2ZIxVr.mjs → copilotkit-PzJlPKcU.mjs} +251 -64
  8. package/dist/copilotkit-PzJlPKcU.mjs.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.d.cts +2 -1
  11. package/dist/index.d.cts.map +1 -1
  12. package/dist/index.d.mts +2 -1
  13. package/dist/index.d.mts.map +1 -1
  14. package/dist/index.mjs +1 -1
  15. package/dist/index.umd.js +31 -44
  16. package/dist/index.umd.js.map +1 -1
  17. package/dist/v2/index.cjs +1 -1
  18. package/dist/v2/index.css +1 -1
  19. package/dist/v2/index.d.cts +2 -2
  20. package/dist/v2/index.d.mts +2 -2
  21. package/dist/v2/index.mjs +1 -1
  22. package/dist/v2/index.umd.js +249 -66
  23. package/dist/v2/index.umd.js.map +1 -1
  24. package/package.json +6 -6
  25. package/src/components/copilot-provider/__tests__/v1-explicit-threadid-bridge.test.tsx +107 -0
  26. package/src/components/copilot-provider/copilotkit.tsx +6 -1
  27. package/src/context/__tests__/threads-context.test.tsx +116 -3
  28. package/src/context/threads-context.tsx +18 -1
  29. package/src/v2/components/chat/CopilotChat.tsx +91 -4
  30. package/src/v2/components/chat/CopilotChatInput.tsx +22 -0
  31. package/src/v2/components/chat/CopilotChatView.tsx +206 -11
  32. package/src/v2/components/chat/__tests__/CopilotChat.absentThreadConnect.test.tsx +66 -0
  33. package/src/v2/components/chat/__tests__/CopilotChat.welcomeGate.test.tsx +186 -0
  34. package/src/v2/components/chat/__tests__/CopilotChatActivityRendering.e2e.test.tsx +300 -2
  35. package/src/v2/components/chat/__tests__/CopilotChatView.connectingGate.test.tsx +56 -0
  36. package/src/v2/components/chat/__tests__/CopilotChatView.pinToSend.test.tsx +94 -0
  37. package/src/v2/components/chat/__tests__/copilot-chat-throttle.test.tsx +0 -1
  38. package/src/v2/components/chat/__tests__/normalize-auto-scroll.test.ts +37 -0
  39. package/src/v2/components/chat/index.ts +2 -0
  40. package/src/v2/components/chat/last-user-message-context.ts +21 -0
  41. package/src/v2/components/chat/normalize-auto-scroll.ts +17 -0
  42. package/src/v2/components/license-warning-banner.tsx +20 -1
  43. package/src/v2/hooks/__tests__/use-agent-stability.test.tsx +6 -0
  44. package/src/v2/hooks/__tests__/use-agent-thread-isolation.test.tsx +6 -0
  45. package/src/v2/hooks/__tests__/use-agent-throttle.test.tsx +76 -50
  46. package/src/v2/hooks/__tests__/use-pin-to-send.test.tsx +219 -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-threads.tsx +55 -12
  51. package/src/v2/providers/CopilotChatConfigurationProvider.tsx +29 -1
  52. package/src/v2/providers/CopilotKitProvider.tsx +2 -11
  53. package/src/v2/providers/__tests__/CopilotChatConfigurationProvider.test.tsx +106 -0
  54. package/dist/copilotkit-BtP7w7cT.d.cts.map +0 -1
  55. package/dist/copilotkit-CCbxm6JM.d.mts.map +0 -1
  56. package/dist/copilotkit-CSJw5BG8.cjs.map +0 -1
  57. package/dist/copilotkit-Cj2ZIxVr.mjs.map +0 -1
@@ -175,7 +175,7 @@ const CopilotChatDefaultLabels = {
175
175
  welcomeMessageText: "How can I help you today?"
176
176
  };
177
177
  const CopilotChatConfiguration = (0, react.createContext)(null);
178
- const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, isModalDefaultOpen }) => {
178
+ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
179
179
  const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
180
180
  const stableLabels = useShallowStableRef(labels);
181
181
  const mergedLabels = (0, react.useMemo)(() => ({
@@ -189,6 +189,7 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
189
189
  if (parentConfig?.threadId) return parentConfig.threadId;
190
190
  return (0, _copilotkit_shared.randomUUID)();
191
191
  }, [threadId, parentConfig?.threadId]);
192
+ const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId;
192
193
  const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen ?? true);
193
194
  const hasExplicitDefault = isModalDefaultOpen !== void 0;
194
195
  const setAndSync = (0, react.useCallback)((open) => {
@@ -211,12 +212,14 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
211
212
  labels: mergedLabels,
212
213
  agentId: resolvedAgentId,
213
214
  threadId: resolvedThreadId,
215
+ hasExplicitThreadId: resolvedHasExplicitThreadId,
214
216
  isModalOpen: resolvedIsModalOpen,
215
217
  setModalOpen: resolvedSetModalOpen
216
218
  }), [
217
219
  mergedLabels,
218
220
  resolvedAgentId,
219
221
  resolvedThreadId,
222
+ resolvedHasExplicitThreadId,
220
223
  resolvedIsModalOpen,
221
224
  resolvedSetModalOpen
222
225
  ]);
@@ -610,7 +613,7 @@ CopilotChatAudioRecorder.displayName = "CopilotChatAudioRecorder";
610
613
  //#region src/v2/components/chat/CopilotChatInput.tsx
611
614
  const SLASH_MENU_MAX_VISIBLE_ITEMS = 5;
612
615
  const SLASH_MENU_ITEM_HEIGHT_PX = 40;
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, textArea, sendButton, startTranscribeButton, cancelTranscribeButton, finishTranscribeButton, addMenuButton, audioRecorder, disclaimer, children, className, ...props }) {
616
+ 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 }) {
614
617
  const isControlled = value !== void 0;
615
618
  const [internalValue, setInternalValue] = (0, react.useState)(() => value ?? "");
616
619
  (0, react.useEffect)(() => {
@@ -1134,7 +1137,8 @@ function CopilotChatInput({ mode = "input", onSubmitMessage, onStop, isRunning =
1134
1137
  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),
1135
1138
  style: {
1136
1139
  transform: keyboardHeight > 0 ? `translateY(-${keyboardHeight}px)` : void 0,
1137
- transition: "transform 0.2s ease-out"
1140
+ transition: "transform 0.2s ease-out",
1141
+ ...positioning === "absolute" || bottomAnchored ? { paddingBottom: "var(--copilotkit-license-banner-offset, 0px)" } : {}
1138
1142
  },
1139
1143
  ...props,
1140
1144
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -1349,6 +1353,8 @@ CopilotKitInspector.displayName = "CopilotKitInspector";
1349
1353
 
1350
1354
  //#endregion
1351
1355
  //#region src/v2/components/license-warning-banner.tsx
1356
+ const LICENSE_BANNER_OFFSET_PX = 52;
1357
+ const LICENSE_BANNER_OFFSET_VAR = "--copilotkit-license-banner-offset";
1352
1358
  const BANNER_STYLES = {
1353
1359
  base: {
1354
1360
  position: "fixed",
@@ -1390,6 +1396,14 @@ function getSeverityStyle(severity) {
1390
1396
  }
1391
1397
  }
1392
1398
  function BannerShell({ severity, message, actionLabel, actionUrl, onDismiss }) {
1399
+ (0, react.useEffect)(() => {
1400
+ if (typeof document === "undefined") return;
1401
+ const root = document.documentElement;
1402
+ root.style.setProperty(LICENSE_BANNER_OFFSET_VAR, `${LICENSE_BANNER_OFFSET_PX}px`);
1403
+ return () => {
1404
+ root.style.removeProperty(LICENSE_BANNER_OFFSET_VAR);
1405
+ };
1406
+ }, []);
1393
1407
  return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1394
1408
  style: {
1395
1409
  ...BANNER_STYLES.base,
@@ -3342,7 +3356,6 @@ const CopilotKitProvider = ({ children, runtimeUrl, headers: headersProp = {}, c
3342
3356
  didMountRef.current = true;
3343
3357
  }, []);
3344
3358
  (0, react.useEffect)(() => {
3345
- 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.`);
3346
3359
  copilotkit.setDefaultThrottleMs(defaultThrottleMs);
3347
3360
  }, [copilotkit, defaultThrottleMs]);
3348
3361
  const designSkill = openGenerativeUI?.designSkill ?? DEFAULT_DESIGN_SKILL;
@@ -3559,15 +3572,6 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3559
3572
  const providerThrottleMs = copilotkit.defaultThrottleMs;
3560
3573
  const chatConfig = useCopilotChatConfiguration();
3561
3574
  threadId ??= chatConfig?.threadId;
3562
- const effectiveThrottleMs = (0, react.useMemo)(() => {
3563
- const resolved = throttleMs ?? providerThrottleMs ?? 0;
3564
- if (!Number.isFinite(resolved) || resolved < 0) {
3565
- const source = throttleMs !== void 0 ? "hook-level throttleMs" : "provider-level defaultThrottleMs";
3566
- console.error(`useAgent: ${source} must be a non-negative finite number, got ${resolved}. Falling back to unthrottled.`);
3567
- return 0;
3568
- }
3569
- return resolved;
3570
- }, [throttleMs, providerThrottleMs]);
3571
3575
  const [, forceUpdate] = (0, react.useReducer)((x) => x + 1, 0);
3572
3576
  const updateFlags = (0, react.useMemo)(() => updates ?? ALL_UPDATES, [JSON.stringify(updates)]);
3573
3577
  const provisionalAgentCache = (0, react.useRef)(/* @__PURE__ */ new Map());
@@ -3630,9 +3634,8 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3630
3634
  ]);
3631
3635
  (0, react.useEffect)(() => {
3632
3636
  if (updateFlags.length === 0) return;
3633
- const handlers = {};
3634
- let timerId = null;
3635
3637
  let active = true;
3638
+ const handlers = {};
3636
3639
  let batchScheduled = false;
3637
3640
  const batchedForceUpdate = () => {
3638
3641
  if (!active) return;
@@ -3644,46 +3647,24 @@ function useAgent({ agentId, threadId, updates, throttleMs } = {}) {
3644
3647
  });
3645
3648
  }
3646
3649
  };
3647
- if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) {
3648
- const ms = effectiveThrottleMs;
3649
- if (ms > 0) {
3650
- let throttleActive = false;
3651
- let pending = false;
3652
- const throttledNotify = () => {
3653
- if (!active) return;
3654
- if (!throttleActive) {
3655
- throttleActive = true;
3656
- pending = false;
3657
- forceUpdate();
3658
- timerId = setTimeout(function trailingEdge() {
3659
- timerId = null;
3660
- if (active && pending) {
3661
- pending = false;
3662
- forceUpdate();
3663
- timerId = setTimeout(trailingEdge, ms);
3664
- } else throttleActive = false;
3665
- }, ms);
3666
- } else pending = true;
3667
- };
3668
- handlers.onMessagesChanged = throttledNotify;
3669
- } else handlers.onMessagesChanged = forceUpdate;
3670
- }
3650
+ if (updateFlags.includes(UseAgentUpdate.OnMessagesChanged)) handlers.onMessagesChanged = forceUpdate;
3671
3651
  if (updateFlags.includes(UseAgentUpdate.OnStateChanged)) handlers.onStateChanged = batchedForceUpdate;
3672
3652
  if (updateFlags.includes(UseAgentUpdate.OnRunStatusChanged)) {
3673
3653
  handlers.onRunInitialized = batchedForceUpdate;
3674
3654
  handlers.onRunFinalized = batchedForceUpdate;
3675
3655
  handlers.onRunFailed = batchedForceUpdate;
3656
+ handlers.onRunErrorEvent = batchedForceUpdate;
3676
3657
  }
3677
- const subscription = agent.subscribe(handlers);
3658
+ const subscription = copilotkit.subscribeToAgentWithOptions(agent, handlers, { throttleMs });
3678
3659
  return () => {
3679
3660
  active = false;
3680
- if (timerId !== null) clearTimeout(timerId);
3681
3661
  subscription.unsubscribe();
3682
3662
  };
3683
3663
  }, [
3684
3664
  agent,
3685
3665
  forceUpdate,
3686
- effectiveThrottleMs,
3666
+ throttleMs,
3667
+ providerThrottleMs,
3687
3668
  updateFlags
3688
3669
  ]);
3689
3670
  (0, react.useEffect)(() => {
@@ -4637,13 +4618,14 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4637
4618
  const { copilotkit } = useCopilotKit();
4638
4619
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
4639
4620
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
4640
- const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt }) => ({
4621
+ const threads = (0, react.useMemo)(() => coreThreads.map(({ id, agentId, name, archived, createdAt, updatedAt, lastRunAt }) => ({
4641
4622
  id,
4642
4623
  agentId,
4643
4624
  name,
4644
4625
  archived,
4645
4626
  createdAt,
4646
- updatedAt
4627
+ updatedAt,
4628
+ ...lastRunAt !== void 0 ? { lastRunAt } : {}
4647
4629
  })), [coreThreads]);
4648
4630
  const storeIsLoading = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsIsLoading);
4649
4631
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
@@ -4656,7 +4638,9 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4656
4638
  if (copilotkit.runtimeUrl) return null;
4657
4639
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
4658
4640
  }, [copilotkit.runtimeUrl]);
4659
- const isLoading = runtimeError ? false : storeIsLoading;
4641
+ const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
4642
+ const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
4643
+ const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
4660
4644
  const error = runtimeError ?? storeError;
4661
4645
  (0, react.useEffect)(() => {
4662
4646
  store.start();
@@ -4664,19 +4648,27 @@ function useThreads$1({ agentId, includeArchived, limit }) {
4664
4648
  store.stop();
4665
4649
  };
4666
4650
  }, [store]);
4651
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
4667
4652
  (0, react.useEffect)(() => {
4668
- const context = copilotkit.runtimeUrl ? {
4653
+ if (!copilotkit.runtimeUrl) {
4654
+ store.setContext(null);
4655
+ return;
4656
+ }
4657
+ if (runtimeStatus !== _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connected) return;
4658
+ const context = {
4669
4659
  runtimeUrl: copilotkit.runtimeUrl,
4670
4660
  headers: { ...copilotkit.headers },
4671
4661
  wsUrl: copilotkit.intelligence?.wsUrl,
4672
4662
  agentId,
4673
4663
  includeArchived,
4674
4664
  limit
4675
- } : null;
4665
+ };
4676
4666
  store.setContext(context);
4667
+ setHasDispatchedContext(true);
4677
4668
  }, [
4678
4669
  store,
4679
4670
  copilotkit.runtimeUrl,
4671
+ runtimeStatus,
4680
4672
  headersKey,
4681
4673
  copilotkit.intelligence?.wsUrl,
4682
4674
  agentId,
@@ -6201,9 +6193,97 @@ function useKeyboardHeight() {
6201
6193
  return keyboardState;
6202
6194
  }
6203
6195
 
6196
+ //#endregion
6197
+ //#region src/v2/components/chat/normalize-auto-scroll.ts
6198
+ const VALID = [
6199
+ "pin-to-bottom",
6200
+ "pin-to-send",
6201
+ "none"
6202
+ ];
6203
+ function normalizeAutoScroll(value) {
6204
+ if (value === void 0) return "pin-to-bottom";
6205
+ if (value === true) return "pin-to-bottom";
6206
+ if (value === false) return "none";
6207
+ if (VALID.includes(value)) return value;
6208
+ return "pin-to-bottom";
6209
+ }
6210
+
6211
+ //#endregion
6212
+ //#region src/v2/components/chat/last-user-message-context.ts
6213
+ const LastUserMessageContext = react.default.createContext({
6214
+ id: null,
6215
+ sendNonce: 0
6216
+ });
6217
+
6218
+ //#endregion
6219
+ //#region src/v2/hooks/use-pin-to-send.ts
6220
+ function usePinToSend({ scrollRef, contentRef, spacerRef, topOffset = 16 }) {
6221
+ const { id, sendNonce } = (0, react.useContext)(LastUserMessageContext);
6222
+ const lastNonceRef = (0, react.useRef)(-1);
6223
+ const currentSpacerHeightRef = (0, react.useRef)(0);
6224
+ (0, react.useEffect)(() => {
6225
+ if (sendNonce === lastNonceRef.current) return;
6226
+ lastNonceRef.current = sendNonce;
6227
+ if (!id) return;
6228
+ const scrollEl = scrollRef.current;
6229
+ const contentEl = contentRef.current;
6230
+ const spacerEl = spacerRef.current;
6231
+ if (!scrollEl || !contentEl || !spacerEl) return;
6232
+ const escaped = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(id) : id.replace(/[!"#$%&'()*+,./:;<=>?@[\\\]^`{|}~]/g, "\\$&");
6233
+ const targetEl = contentEl.querySelector(`[data-message-id="${escaped}"]`);
6234
+ if (!targetEl) return;
6235
+ const viewportHeight = scrollEl.clientHeight;
6236
+ const userMessageHeight = targetEl.getBoundingClientRect().height;
6237
+ const paddingTop = parseFloat(getComputedStyle(targetEl).paddingTop) || 0;
6238
+ const bubbleHeight = Math.max(0, userMessageHeight - paddingTop);
6239
+ const spacerHeight = Math.max(0, viewportHeight - bubbleHeight - topOffset);
6240
+ spacerEl.style.height = `${spacerHeight}px`;
6241
+ currentSpacerHeightRef.current = spacerHeight;
6242
+ const raf = requestAnimationFrame(() => {
6243
+ const targetTop = computeOffsetTop(targetEl, scrollEl) + paddingTop - topOffset;
6244
+ scrollEl.scrollTo({
6245
+ top: Math.max(0, targetTop),
6246
+ behavior: "smooth"
6247
+ });
6248
+ });
6249
+ const ro = new ResizeObserver(() => {
6250
+ if (!contentEl || !spacerEl || !scrollEl) return;
6251
+ const consumedBelow = contentEl.getBoundingClientRect().height - computeOffsetTop(targetEl, contentEl) - userMessageHeight;
6252
+ const remaining = Math.max(0, spacerHeight - consumedBelow);
6253
+ if (remaining < currentSpacerHeightRef.current) {
6254
+ spacerEl.style.height = `${remaining}px`;
6255
+ currentSpacerHeightRef.current = remaining;
6256
+ }
6257
+ });
6258
+ ro.observe(contentEl);
6259
+ return () => {
6260
+ cancelAnimationFrame(raf);
6261
+ ro.disconnect();
6262
+ };
6263
+ }, [
6264
+ id,
6265
+ sendNonce,
6266
+ scrollRef,
6267
+ contentRef,
6268
+ spacerRef,
6269
+ topOffset
6270
+ ]);
6271
+ }
6272
+ function computeOffsetTop(el, stopAt) {
6273
+ const elRect = el.getBoundingClientRect();
6274
+ const stopRect = stopAt.getBoundingClientRect();
6275
+ return elRect.top - stopRect.top + stopAt.scrollTop;
6276
+ }
6277
+
6204
6278
  //#endregion
6205
6279
  //#region src/v2/components/chat/CopilotChatView.tsx
6206
6280
  const FEATHER_HEIGHT = 96;
6281
+ const PIN_TO_SEND_FEATHER_HEIGHT = 48;
6282
+ const PinToSendSoftFeather = ({ className, style, ...props }) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6283
+ 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),
6284
+ style,
6285
+ ...props
6286
+ });
6207
6287
  function DropOverlay() {
6208
6288
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6209
6289
  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"),
@@ -6216,7 +6296,7 @@ function DropOverlay() {
6216
6296
  })
6217
6297
  });
6218
6298
  }
6219
- 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 }) {
6299
+ 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 }) {
6220
6300
  const inputContainerRef = (0, react.useRef)(null);
6221
6301
  const [inputContainerHeight, setInputContainerHeight] = (0, react.useState)(0);
6222
6302
  const [isResizing, setIsResizing] = (0, react.useState)(false);
@@ -6268,9 +6348,10 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6268
6348
  keyboardHeight: isKeyboardOpen ? keyboardHeight : 0,
6269
6349
  containerRef: inputContainerRef,
6270
6350
  showDisclaimer: true,
6351
+ bottomAnchored: true,
6271
6352
  ...disclaimer !== void 0 ? { disclaimer } : {}
6272
6353
  });
6273
- const hasSuggestions = Array.isArray(suggestions) && suggestions.length > 0;
6354
+ const hasSuggestions = !isConnecting && !isRunning && Array.isArray(suggestions) && suggestions.length > 0;
6274
6355
  const BoundSuggestionView = hasSuggestions ? renderSlot(suggestionView, CopilotChatSuggestionView, {
6275
6356
  suggestions,
6276
6357
  loadingIndexes: suggestionLoadingIndexes,
@@ -6292,7 +6373,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6292
6373
  })
6293
6374
  })
6294
6375
  });
6295
- if (messages.length === 0 && !(welcomeScreen === false)) {
6376
+ if (messages.length === 0 && !(welcomeScreen === false) && !isConnecting && !hasExplicitThreadId) {
6296
6377
  const BoundInputForWelcome = renderSlot(input, CopilotChatInput_default, {
6297
6378
  onSubmitMessage,
6298
6379
  onStop,
@@ -6399,9 +6480,60 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6399
6480
  ] })
6400
6481
  });
6401
6482
  };
6402
- _CopilotChatView.ScrollView = ({ children, autoScroll = true, scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6483
+ const PinToSendScrollContainer = ({ children, scrollRef, contentRef, scrollToBottom, scrollToBottomButton, feather, inputContainerHeight, isResizing, nonAutoScrollEl, nonAutoScrollRefCallback, showScrollButton, className, ...props }) => {
6484
+ const spacerRef = (0, react.useRef)(null);
6485
+ usePinToSend({
6486
+ scrollRef,
6487
+ contentRef,
6488
+ spacerRef,
6489
+ topOffset: 16
6490
+ });
6491
+ const BoundFeather = renderSlot(feather, PinToSendSoftFeather, {});
6492
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6493
+ value: nonAutoScrollEl,
6494
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6495
+ className: cn("cpk:h-full cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0 cpk:relative", className),
6496
+ children: [
6497
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
6498
+ ref: nonAutoScrollRefCallback,
6499
+ className: "cpk:flex-1 cpk:min-h-0 cpk:overflow-y-auto cpk:overflow-x-hidden",
6500
+ ...props,
6501
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6502
+ ref: contentRef,
6503
+ className: "cpk:px-4 cpk:sm:px-0 cpk:[div[data-sidebar-chat]_&]:px-8 cpk:[div[data-popup-chat]_&]:px-6",
6504
+ children
6505
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6506
+ ref: spacerRef,
6507
+ "data-pin-to-send-spacer": true,
6508
+ "aria-hidden": "true",
6509
+ style: {
6510
+ height: 0,
6511
+ flex: "0 0 auto"
6512
+ }
6513
+ })]
6514
+ }),
6515
+ BoundFeather,
6516
+ showScrollButton && !isResizing && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6517
+ className: "cpk:absolute cpk:inset-x-0 cpk:flex cpk:justify-center cpk:z-30 cpk:pointer-events-none",
6518
+ style: { bottom: `${inputContainerHeight + PIN_TO_SEND_FEATHER_HEIGHT + 16}px` },
6519
+ children: renderSlot(scrollToBottomButton, CopilotChatView.ScrollToBottomButton, { onClick: () => scrollToBottom() })
6520
+ })
6521
+ ]
6522
+ })
6523
+ });
6524
+ };
6525
+ _CopilotChatView.ScrollView = ({ children, autoScroll = "pin-to-bottom", scrollToBottomButton, feather, inputContainerHeight = 0, isResizing = false, className, ...props }) => {
6526
+ const mode = normalizeAutoScroll(autoScroll);
6403
6527
  const [hasMounted, setHasMounted] = (0, react.useState)(false);
6404
- const { scrollRef, contentRef, scrollToBottom } = (0, use_stick_to_bottom.useStickToBottom)();
6528
+ const scrollRef = (0, react.useRef)(null);
6529
+ const contentRef = (0, react.useRef)(null);
6530
+ const scrollToBottom = (0, react.useCallback)(() => {
6531
+ const el = scrollRef.current;
6532
+ if (el) el.scrollTo({
6533
+ top: el.scrollHeight,
6534
+ behavior: "smooth"
6535
+ });
6536
+ }, []);
6405
6537
  const [showScrollButton, setShowScrollButton] = (0, react.useState)(false);
6406
6538
  const [nonAutoScrollEl, setNonAutoScrollEl] = (0, react.useState)(null);
6407
6539
  const nonAutoScrollRefCallback = (0, react.useCallback)((el) => {
@@ -6412,7 +6544,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6412
6544
  setHasMounted(true);
6413
6545
  }, []);
6414
6546
  (0, react.useEffect)(() => {
6415
- if (autoScroll) return;
6547
+ if (mode === "pin-to-bottom") return;
6416
6548
  const scrollElement = scrollRef.current;
6417
6549
  if (!scrollElement) return;
6418
6550
  const checkScroll = () => {
@@ -6426,7 +6558,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6426
6558
  scrollElement.removeEventListener("scroll", checkScroll);
6427
6559
  resizeObserver.disconnect();
6428
6560
  };
6429
- }, [scrollRef, autoScroll]);
6561
+ }, [scrollRef, mode]);
6430
6562
  if (!hasMounted) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
6431
6563
  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",
6432
6564
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
@@ -6434,7 +6566,7 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6434
6566
  children
6435
6567
  })
6436
6568
  });
6437
- if (!autoScroll) {
6569
+ if (mode === "none") {
6438
6570
  const BoundFeather = renderSlot(feather, CopilotChatView.Feather, {});
6439
6571
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ScrollElementContext.Provider, {
6440
6572
  value: nonAutoScrollEl,
@@ -6458,6 +6590,21 @@ function CopilotChatView({ messageView, input, scrollView, suggestionView, welco
6458
6590
  })
6459
6591
  });
6460
6592
  }
6593
+ if (mode === "pin-to-send") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PinToSendScrollContainer, {
6594
+ scrollRef,
6595
+ contentRef,
6596
+ scrollToBottom,
6597
+ scrollToBottomButton,
6598
+ feather,
6599
+ inputContainerHeight,
6600
+ isResizing,
6601
+ nonAutoScrollEl,
6602
+ nonAutoScrollRefCallback,
6603
+ showScrollButton,
6604
+ className,
6605
+ ...props,
6606
+ children
6607
+ });
6461
6608
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(use_stick_to_bottom.StickToBottom, {
6462
6609
  className: cn("cpk:flex-1 cpk:max-h-full cpk:flex cpk:flex-col cpk:min-h-0", className),
6463
6610
  resize: "smooth",
@@ -6650,7 +6797,9 @@ async function transcribeAudio(core, audioBlob, filename = "recording.webm") {
6650
6797
  function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen, attachments: attachmentsConfig, onError, throttleMs, ...props }) {
6651
6798
  const existingConfig = useCopilotChatConfiguration();
6652
6799
  const resolvedAgentId = agentId ?? existingConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
6653
- const resolvedThreadId = (0, react.useMemo)(() => threadId ?? existingConfig?.threadId ?? (0, _copilotkit_shared.randomUUID)(), [threadId, existingConfig?.threadId]);
6800
+ const providedThreadId = threadId ?? existingConfig?.threadId;
6801
+ const resolvedThreadId = (0, react.useMemo)(() => providedThreadId ?? (0, _copilotkit_shared.randomUUID)(), [providedThreadId]);
6802
+ const hasExplicitThreadId = !!threadId || !!existingConfig?.hasExplicitThreadId;
6654
6803
  const { agent } = useAgent({
6655
6804
  agentId: resolvedAgentId,
6656
6805
  threadId: resolvedThreadId,
@@ -6688,7 +6837,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6688
6837
  const isTranscriptionEnabled = copilotkit.audioFileTranscriptionEnabled;
6689
6838
  const isMediaRecorderSupported = typeof window !== "undefined" && typeof MediaRecorder !== "undefined";
6690
6839
  const { messageView: providedMessageView, suggestionView: providedSuggestionView, onStop: providedStopHandler, ...restProps } = props;
6840
+ const [lastConnectedThreadId, setLastConnectedThreadId] = (0, react.useState)(null);
6841
+ const isConnecting = hasExplicitThreadId && lastConnectedThreadId !== resolvedThreadId;
6691
6842
  (0, react.useEffect)(() => {
6843
+ if (!hasExplicitThreadId) return;
6692
6844
  let detached = false;
6693
6845
  const connectAbortController = new AbortController();
6694
6846
  if (agent instanceof _ag_ui_client.HttpAgent) agent.abortController = connectAbortController;
@@ -6698,6 +6850,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6698
6850
  } catch (error) {
6699
6851
  if (detached) return;
6700
6852
  console.error("CopilotChat: connectAgent failed", error);
6853
+ } finally {
6854
+ if (!detached) (typeof requestAnimationFrame === "function" ? requestAnimationFrame : (cb) => setTimeout(cb, 16))(() => {
6855
+ if (!detached) setLastConnectedThreadId(resolvedThreadId);
6856
+ });
6701
6857
  }
6702
6858
  };
6703
6859
  connect(agent);
@@ -6709,7 +6865,8 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6709
6865
  }, [
6710
6866
  resolvedThreadId,
6711
6867
  agent,
6712
- resolvedAgentId
6868
+ resolvedAgentId,
6869
+ hasExplicitThreadId
6713
6870
  ]);
6714
6871
  const onSubmitInput = (0, react.useCallback)(async (value) => {
6715
6872
  if (selectedAttachments.some((a) => a.status === "uploading")) {
@@ -6862,6 +7019,22 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6862
7019
  const toolCallsKey = "toolCalls" in m && Array.isArray(m.toolCalls) ? m.toolCalls.map((tc) => `${tc.id}:${tc.function?.arguments?.length ?? 0}`).join(";") : "";
6863
7020
  return `${m.id}:${m.role}:${contentKey}:${toolCallsKey}`;
6864
7021
  }).join(",")]);
7022
+ const lastUserMessageId = (0, react.useMemo)(() => {
7023
+ for (let i = messages.length - 1; i >= 0; i--) if (messages[i].role === "user") return messages[i].id;
7024
+ return null;
7025
+ }, [messages]);
7026
+ const [sendNonce, setSendNonce] = (0, react.useState)(0);
7027
+ const prevLastUserMessageIdRef = (0, react.useRef)(lastUserMessageId);
7028
+ (0, react.useEffect)(() => {
7029
+ if (lastUserMessageId && lastUserMessageId !== prevLastUserMessageIdRef.current) {
7030
+ setSendNonce((n) => n + 1);
7031
+ prevLastUserMessageIdRef.current = lastUserMessageId;
7032
+ }
7033
+ }, [lastUserMessageId]);
7034
+ const lastUserMessageState = (0, react.useMemo)(() => ({
7035
+ id: lastUserMessageId,
7036
+ sendNonce
7037
+ }), [lastUserMessageId, sendNonce]);
6865
7038
  const RenderedChatView = renderSlot(chatView, CopilotChatView, {
6866
7039
  ...mergedProps,
6867
7040
  messages,
@@ -6880,11 +7053,14 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6880
7053
  dragOver,
6881
7054
  onDragOver: handleDragOver,
6882
7055
  onDragLeave: handleDragLeave,
6883
- onDrop: handleDrop
7056
+ onDrop: handleDrop,
7057
+ isConnecting,
7058
+ hasExplicitThreadId
6884
7059
  });
6885
7060
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
6886
7061
  agentId: resolvedAgentId,
6887
7062
  threadId: resolvedThreadId,
7063
+ hasExplicitThreadId,
6888
7064
  labels,
6889
7065
  isModalDefaultOpen,
6890
7066
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
@@ -6915,7 +7091,10 @@ function CopilotChat({ agentId, threadId, labels, chatView, isModalDefaultOpen,
6915
7091
  },
6916
7092
  children: transcriptionError
6917
7093
  }),
6918
- RenderedChatView
7094
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(LastUserMessageContext.Provider, {
7095
+ value: lastUserMessageState,
7096
+ children: RenderedChatView
7097
+ })
6919
7098
  ]
6920
7099
  })
6921
7100
  });
@@ -8641,12 +8820,19 @@ function useCoAgentStateRenders() {
8641
8820
  //#region src/context/threads-context.tsx
8642
8821
  const ThreadsContext = (0, react.createContext)(void 0);
8643
8822
  function ThreadsProvider({ children, threadId: explicitThreadId }) {
8644
- const [internalThreadId, setThreadId] = (0, react.useState)(() => (0, _copilotkit_shared.randomUUID)());
8823
+ const [internalThreadId, setInternalThreadId] = (0, react.useState)(() => (0, _copilotkit_shared.randomUUID)());
8824
+ const [internalIsExplicit, setInternalIsExplicit] = (0, react.useState)(false);
8645
8825
  const threadId = explicitThreadId ?? internalThreadId;
8826
+ const isThreadIdExplicit = explicitThreadId != null || internalIsExplicit;
8827
+ const setThreadId = (0, react.useCallback)((value) => {
8828
+ setInternalThreadId(value);
8829
+ setInternalIsExplicit(true);
8830
+ }, []);
8646
8831
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ThreadsContext.Provider, {
8647
8832
  value: {
8648
8833
  threadId,
8649
- setThreadId
8834
+ setThreadId,
8835
+ isThreadIdExplicit
8650
8836
  },
8651
8837
  children
8652
8838
  });
@@ -9348,7 +9534,7 @@ function CopilotKitInternal(cpkProps) {
9348
9534
  if (props.agent) setAgentSession({ agentName: props.agent });
9349
9535
  else setAgentSession(null);
9350
9536
  }, [props.agent]);
9351
- const { threadId, setThreadId: setInternalThreadId } = useThreads();
9537
+ const { threadId, setThreadId: setInternalThreadId, isThreadIdExplicit } = useThreads();
9352
9538
  const setThreadId = (0, react.useCallback)((value) => {
9353
9539
  if (props.threadId) throw new Error("Cannot call setThreadId() when threadId is provided via props.");
9354
9540
  setInternalThreadId(value);
@@ -9548,6 +9734,7 @@ function CopilotKitInternal(cpkProps) {
9548
9734
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfigurationProvider, {
9549
9735
  agentId: props.agent ?? "default",
9550
9736
  threadId,
9737
+ hasExplicitThreadId: isThreadIdExplicit,
9551
9738
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CopilotContext.Provider, {
9552
9739
  value: copilotContextValue,
9553
9740
  children: [
@@ -10017,4 +10204,4 @@ Object.defineProperty(exports, 'useToast', {
10017
10204
  return useToast;
10018
10205
  }
10019
10206
  });
10020
- //# sourceMappingURL=copilotkit-CSJw5BG8.cjs.map
10207
+ //# sourceMappingURL=copilotkit-By2G6-Zx.cjs.map