@copilotkit/react-core 1.61.1 → 1.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/{copilotkit-UY-H6Kx7.mjs → copilotkit-BtRkFsNR.mjs} +1227 -599
  2. package/dist/copilotkit-BtRkFsNR.mjs.map +1 -0
  3. package/dist/{copilotkit-BCJDP8qd.cjs → copilotkit-CdpDZi3i.cjs} +1219 -585
  4. package/dist/copilotkit-CdpDZi3i.cjs.map +1 -0
  5. package/dist/{copilotkit-CEdu_aie.d.cts → copilotkit-Cga6AHO0.d.cts} +497 -209
  6. package/dist/copilotkit-Cga6AHO0.d.cts.map +1 -0
  7. package/dist/{copilotkit-M1FiciGd.d.mts → copilotkit-DnLpJ2Cl.d.mts} +497 -209
  8. package/dist/copilotkit-DnLpJ2Cl.d.mts.map +1 -0
  9. package/dist/index.cjs +1 -1
  10. package/dist/index.cjs.map +1 -1
  11. package/dist/index.d.cts +1 -1
  12. package/dist/index.d.cts.map +1 -1
  13. package/dist/index.d.mts +1 -1
  14. package/dist/index.d.mts.map +1 -1
  15. package/dist/index.mjs +1 -1
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/index.umd.js +360 -270
  18. package/dist/index.umd.js.map +1 -1
  19. package/dist/v2/context.d.cts +5 -1
  20. package/dist/v2/context.d.cts.map +1 -1
  21. package/dist/v2/context.d.mts +5 -1
  22. package/dist/v2/context.d.mts.map +1 -1
  23. package/dist/v2/headless.cjs +323 -104
  24. package/dist/v2/headless.cjs.map +1 -1
  25. package/dist/v2/headless.d.cts +187 -69
  26. package/dist/v2/headless.d.cts.map +1 -1
  27. package/dist/v2/headless.d.mts +187 -69
  28. package/dist/v2/headless.d.mts.map +1 -1
  29. package/dist/v2/headless.mjs +325 -106
  30. package/dist/v2/headless.mjs.map +1 -1
  31. package/dist/v2/index.cjs +2 -1
  32. package/dist/v2/index.css +1 -1
  33. package/dist/v2/index.d.cts +2 -2
  34. package/dist/v2/index.d.mts +2 -2
  35. package/dist/v2/index.mjs +2 -2
  36. package/dist/v2/index.umd.js +1112 -484
  37. package/dist/v2/index.umd.js.map +1 -1
  38. package/package.json +8 -6
  39. package/dist/copilotkit-BCJDP8qd.cjs.map +0 -1
  40. package/dist/copilotkit-CEdu_aie.d.cts.map +0 -1
  41. package/dist/copilotkit-M1FiciGd.d.mts.map +0 -1
  42. package/dist/copilotkit-UY-H6Kx7.mjs.map +0 -1
@@ -144,6 +144,25 @@ const CopilotChatDefaultLabels = {
144
144
  modalHeaderTitle: "CopilotKit Chat",
145
145
  welcomeMessageText: "How can I help you today?"
146
146
  };
147
+ /**
148
+ * Mobile breakpoint below which the chat modal and the thread-list drawer are
149
+ * mutually exclusive. At or above this width both surfaces may coexist. This
150
+ * mirrors the `(max-width: 767px)` / `(min-width: 768px)` split already used by
151
+ * CopilotChatInput and CopilotSidebarView.
152
+ */
153
+ const MOBILE_MAX_WIDTH_PX = 767;
154
+ /**
155
+ * Reports whether the current viewport is in the mobile range (`<768px`), where
156
+ * the chat modal and drawer must not be open simultaneously. SSR-safe and
157
+ * defensive against environments without `matchMedia` (treated as desktop, so
158
+ * no mutual-exclusion constraint is applied).
159
+ *
160
+ * @returns `true` when the viewport is mobile-width, `false` otherwise.
161
+ */
162
+ function isMobileViewport() {
163
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
164
+ return window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH_PX}px)`).matches;
165
+ }
147
166
  const CopilotChatConfiguration = (0, react.createContext)(null);
148
167
  const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
149
168
  const parentConfig = (0, react.useContext)(CopilotChatConfiguration);
@@ -154,12 +173,22 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
154
173
  ...stableLabels
155
174
  }), [stableLabels, parentConfig?.labels]);
156
175
  const resolvedAgentId = agentId ?? parentConfig?.agentId ?? _copilotkit_shared.DEFAULT_AGENT_ID;
176
+ const threadIdPropIsAuthoritative = threadId !== void 0 && hasExplicitThreadId !== false;
177
+ const isThreadIdControlled = threadIdPropIsAuthoritative;
178
+ const [activeThreadOverride, setActiveThreadOverride] = (0, react.useState)(null);
157
179
  const resolvedThreadId = (0, react.useMemo)(() => {
158
- if (threadId) return threadId;
180
+ if (threadIdPropIsAuthoritative) return threadId;
181
+ if (activeThreadOverride) return activeThreadOverride.threadId;
159
182
  if (parentConfig?.threadId) return parentConfig.threadId;
183
+ if (threadId) return threadId;
160
184
  return (0, _copilotkit_shared.randomUUID)();
161
- }, [threadId, parentConfig?.threadId]);
162
- const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId;
185
+ }, [
186
+ threadIdPropIsAuthoritative,
187
+ threadId,
188
+ parentConfig?.threadId,
189
+ activeThreadOverride
190
+ ]);
191
+ const resolvedHasExplicitThreadId = (threadIdPropIsAuthoritative ? true : activeThreadOverride?.explicit ?? hasExplicitThreadId ?? false) || !!parentConfig?.hasExplicitThreadId;
163
192
  const [internalModalOpen, setInternalModalOpen] = (0, react.useState)(isModalDefaultOpen ?? true);
164
193
  const hasExplicitDefault = isModalDefaultOpen !== void 0;
165
194
  const setAndSync = (0, react.useCallback)((open) => {
@@ -178,20 +207,113 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
178
207
  }, [parentConfig?.isModalOpen, hasExplicitDefault]);
179
208
  const resolvedIsModalOpen = hasExplicitDefault ? internalModalOpen : parentConfig?.isModalOpen ?? internalModalOpen;
180
209
  const resolvedSetModalOpen = hasExplicitDefault ? setAndSync : parentConfig?.setModalOpen ?? setInternalModalOpen;
210
+ const [ownDrawerOpen, setOwnDrawerOpen] = (0, react.useState)(false);
211
+ const [ownDrawerCount, setOwnDrawerCount] = (0, react.useState)(0);
212
+ const modalCloseRef = (0, react.useRef)(() => {});
213
+ modalCloseRef.current = resolvedSetModalOpen;
214
+ const registeredModalClosersRef = (0, react.useRef)([]);
215
+ const ownRegisterModalCloser = (0, react.useCallback)((closeModal) => {
216
+ registeredModalClosersRef.current.push(closeModal);
217
+ return () => {
218
+ registeredModalClosersRef.current = registeredModalClosersRef.current.filter((entry) => entry !== closeModal);
219
+ };
220
+ }, []);
221
+ const ownSetDrawerOpen = (0, react.useCallback)((open) => {
222
+ setOwnDrawerOpen(open);
223
+ if (open && isMobileViewport()) {
224
+ const registered = registeredModalClosersRef.current;
225
+ (registered.length > 0 ? registered[registered.length - 1] : modalCloseRef.current)(false);
226
+ }
227
+ }, []);
228
+ const ownRegisterDrawer = (0, react.useCallback)(() => {
229
+ setOwnDrawerCount((count) => count + 1);
230
+ return () => {
231
+ setOwnDrawerCount((count) => Math.max(0, count - 1));
232
+ };
233
+ }, []);
234
+ const resolvedDrawerOpen = parentConfig ? parentConfig.drawerOpen : ownDrawerOpen;
235
+ const resolvedSetDrawerOpen = parentConfig ? parentConfig.setDrawerOpen : ownSetDrawerOpen;
236
+ const resolvedDrawerRegistered = parentConfig ? parentConfig.drawerRegistered : ownDrawerCount > 0;
237
+ const resolvedRegisterDrawer = parentConfig ? parentConfig.registerDrawer : ownRegisterDrawer;
238
+ const resolvedRegisterModalCloser = parentConfig ? parentConfig.ɵregisterModalCloser : ownRegisterModalCloser;
239
+ (0, react.useEffect)(() => {
240
+ if (!hasExplicitDefault) return;
241
+ return resolvedRegisterModalCloser(resolvedSetModalOpen);
242
+ }, [
243
+ hasExplicitDefault,
244
+ resolvedRegisterModalCloser,
245
+ resolvedSetModalOpen
246
+ ]);
247
+ const isThreadIdControlledRef = (0, react.useRef)(isThreadIdControlled);
248
+ isThreadIdControlledRef.current = isThreadIdControlled;
249
+ const ownSetActiveThreadId = (0, react.useCallback)((id, options) => {
250
+ setActiveThreadOverride({
251
+ threadId: id,
252
+ explicit: options?.explicit ?? true
253
+ });
254
+ }, []);
255
+ const ownStartNewThread = (0, react.useCallback)(() => {
256
+ setActiveThreadOverride({
257
+ threadId: (0, _copilotkit_shared.randomUUID)(),
258
+ explicit: false
259
+ });
260
+ }, []);
261
+ const parentSetActiveThreadId = parentConfig?.setActiveThreadId;
262
+ const parentStartNewThread = parentConfig?.startNewThread;
263
+ const resolvedSetActiveThreadId = (0, react.useCallback)((id, options) => {
264
+ if (isThreadIdControlledRef.current) {
265
+ console.warn("[CopilotKit] Ignoring setActiveThreadId(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider.");
266
+ return;
267
+ }
268
+ if (parentSetActiveThreadId) {
269
+ parentSetActiveThreadId(id, options);
270
+ return;
271
+ }
272
+ ownSetActiveThreadId(id, options);
273
+ }, [parentSetActiveThreadId, ownSetActiveThreadId]);
274
+ const resolvedStartNewThread = (0, react.useCallback)(() => {
275
+ if (isThreadIdControlledRef.current) {
276
+ console.warn("[CopilotKit] Ignoring startNewThread(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider.");
277
+ return;
278
+ }
279
+ if (parentStartNewThread) {
280
+ parentStartNewThread();
281
+ return;
282
+ }
283
+ ownStartNewThread();
284
+ }, [parentStartNewThread, ownStartNewThread]);
285
+ const setModalOpenWithDrawerExclusion = (0, react.useCallback)((open) => {
286
+ if (open && isMobileViewport()) resolvedSetDrawerOpen(false);
287
+ resolvedSetModalOpen(open);
288
+ }, [resolvedSetModalOpen, resolvedSetDrawerOpen]);
181
289
  const configurationValue = (0, react.useMemo)(() => ({
182
290
  labels: mergedLabels,
183
291
  agentId: resolvedAgentId,
184
292
  threadId: resolvedThreadId,
185
293
  hasExplicitThreadId: resolvedHasExplicitThreadId,
186
294
  isModalOpen: resolvedIsModalOpen,
187
- setModalOpen: resolvedSetModalOpen
295
+ setModalOpen: setModalOpenWithDrawerExclusion,
296
+ drawerOpen: resolvedDrawerOpen,
297
+ setDrawerOpen: resolvedSetDrawerOpen,
298
+ drawerRegistered: resolvedDrawerRegistered,
299
+ registerDrawer: resolvedRegisterDrawer,
300
+ ɵregisterModalCloser: resolvedRegisterModalCloser,
301
+ setActiveThreadId: resolvedSetActiveThreadId,
302
+ startNewThread: resolvedStartNewThread
188
303
  }), [
189
304
  mergedLabels,
190
305
  resolvedAgentId,
191
306
  resolvedThreadId,
192
307
  resolvedHasExplicitThreadId,
193
308
  resolvedIsModalOpen,
194
- resolvedSetModalOpen
309
+ setModalOpenWithDrawerExclusion,
310
+ resolvedDrawerOpen,
311
+ resolvedSetDrawerOpen,
312
+ resolvedDrawerRegistered,
313
+ resolvedRegisterDrawer,
314
+ resolvedRegisterModalCloser,
315
+ resolvedSetActiveThreadId,
316
+ resolvedStartNewThread
195
317
  ]);
196
318
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CopilotChatConfiguration.Provider, {
197
319
  value: configurationValue,
@@ -233,7 +355,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
233
355
  if (isRuntimeConfigured && (status === _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
234
356
  const cached = provisionalAgentCache.current.get(agentId);
235
357
  if (cached) {
236
- cached.headers = { ...copilotkit.headers };
358
+ copilotkit.applyHeadersToAgent(cached);
237
359
  return cached;
238
360
  }
239
361
  const provisional = new _copilotkit_core.ProxiedCopilotRuntimeAgent({
@@ -242,14 +364,14 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
242
364
  transport: copilotkit.runtimeTransport,
243
365
  runtimeMode: "pending"
244
366
  });
245
- provisional.headers = { ...copilotkit.headers };
367
+ copilotkit.applyHeadersToAgent(provisional);
246
368
  provisionalAgentCache.current.set(agentId, provisional);
247
369
  return provisional;
248
370
  }
249
371
  if (isRuntimeConfigured && status === _copilotkit_core.CopilotKitCoreRuntimeConnectionStatus.Error) {
250
372
  const cached = provisionalAgentCache.current.get(agentId);
251
373
  if (cached) {
252
- cached.headers = { ...copilotkit.headers };
374
+ copilotkit.applyHeadersToAgent(cached);
253
375
  return cached;
254
376
  }
255
377
  const provisional = new _copilotkit_core.ProxiedCopilotRuntimeAgent({
@@ -258,7 +380,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
258
380
  transport: copilotkit.runtimeTransport,
259
381
  runtimeMode: "pending"
260
382
  });
261
- provisional.headers = { ...copilotkit.headers };
383
+ copilotkit.applyHeadersToAgent(provisional);
262
384
  provisionalAgentCache.current.set(agentId, provisional);
263
385
  return provisional;
264
386
  }
@@ -309,7 +431,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
309
431
  updateFlags
310
432
  ]);
311
433
  (0, react.useEffect)(() => {
312
- if (agent instanceof _ag_ui_client.HttpAgent) agent.headers = { ...copilotkit.headers };
434
+ if (agent instanceof _ag_ui_client.HttpAgent) copilotkit.applyHeadersToAgent(agent);
313
435
  }, [agent, JSON.stringify(copilotkit.headers)]);
314
436
  const chatConfig = useCopilotChatConfiguration();
315
437
  const configThreadId = chatConfig?.threadId;
@@ -521,114 +643,165 @@ const INTERRUPT_EVENT_NAME = "on_interrupt";
521
643
  function isPromiseLike(value) {
522
644
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
523
645
  }
646
+ /** Derive the legacy-compatible `event` for any pending interrupt. */
647
+ function toLegacyEvent(pending) {
648
+ if (pending.kind === "legacy") return pending.event;
649
+ return {
650
+ name: INTERRUPT_EVENT_NAME,
651
+ value: pending.interrupts[0]
652
+ };
653
+ }
524
654
  /**
525
- * Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.
526
- *
527
- * The hook listens to custom events on the active agent, stores interrupt payloads per run,
528
- * and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume
529
- * execution with user-provided data.
530
- *
531
- * - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.
532
- * - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.
655
+ * Handles agent interrupts with optional filtering, preprocessing, and resume behavior.
533
656
  *
534
- * `event.value` is typed as `any` since the interrupt payload shape depends on your agent.
535
- * Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.
657
+ * Supports both the AG-UI standard interrupt flow (`RUN_FINISHED` with
658
+ * `outcome.type === "interrupt"`) and the legacy custom-event flow
659
+ * (`on_interrupt`). For standard interrupts, `render` receives `interrupt`
660
+ * (the primary one) and `interrupts` (the full open set); call `resolve(payload)`
661
+ * to resume or `cancel()` to cancel. Resuming addresses the targeted interrupt
662
+ * and, once every open interrupt is addressed, submits a single spec `resume`
663
+ * array via `copilotkit.runAgent`.
536
664
  *
537
- * @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.
538
- * @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).
539
- * @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).
540
- * Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always
541
- * either the handler's resolved return value or `null` (including when no handler is provided,
542
- * when filtering skips the interrupt, or when handler execution fails).
665
+ * - `renderInChat: true` (default): the element is published into `<CopilotChat>`; returns `void`.
666
+ * - `renderInChat: false`: the hook returns the interrupt element for manual placement.
543
667
  *
544
668
  * @example
545
669
  * ```tsx
546
- * import { useInterrupt } from "@copilotkit/react-core/v2";
547
- *
548
- * function InterruptUI() {
549
- * useInterrupt({
550
- * render: ({ event, resolve }) => (
551
- * <div>
552
- * <p>{event.value.question}</p>
553
- * <button onClick={() => resolve({ approved: true })}>Approve</button>
554
- * <button onClick={() => resolve({ approved: false })}>Reject</button>
555
- * </div>
556
- * ),
557
- * });
558
- *
559
- * return null;
560
- * }
561
- * ```
562
- *
563
- * @example
564
- * ```tsx
565
- * import { useInterrupt } from "@copilotkit/react-core/v2";
566
- *
567
- * function CustomPanel() {
568
- * const interruptElement = useInterrupt({
569
- * renderInChat: false,
570
- * enabled: (event) => event.value.startsWith("approval:"),
571
- * handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
572
- * render: ({ event, result, resolve }) => (
573
- * <aside>
574
- * <strong>{result?.label ?? ""}</strong>
575
- * <button onClick={() => resolve({ value: event.value })}>Continue</button>
576
- * </aside>
577
- * ),
578
- * });
579
- *
580
- * return <>{interruptElement}</>;
581
- * }
670
+ * useInterrupt({
671
+ * render: ({ interrupt, resolve, cancel }) => (
672
+ * <div>
673
+ * <p>{interrupt?.message}</p>
674
+ * <button onClick={() => resolve({ approved: true })}>Approve</button>
675
+ * <button onClick={() => cancel()}>Cancel</button>
676
+ * </div>
677
+ * ),
678
+ * });
582
679
  * ```
583
680
  */
584
681
  function useInterrupt(config) {
585
682
  const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
586
683
  const { agent } = useAgent({ agentId: config.agentId });
587
- const [pendingEvent, setPendingEvent] = (0, react.useState)(null);
588
- const pendingEventRef = (0, react.useRef)(pendingEvent);
589
- pendingEventRef.current = pendingEvent;
684
+ const [pending, setPending] = (0, react.useState)(null);
685
+ const pendingRef = (0, react.useRef)(pending);
686
+ pendingRef.current = pending;
590
687
  const [handlerResult, setHandlerResult] = (0, react.useState)(null);
688
+ const responsesRef = (0, react.useRef)({});
591
689
  (0, react.useEffect)(() => {
592
- let localInterrupt = null;
690
+ let localLegacy = null;
691
+ let localStandard = null;
593
692
  const subscription = agent.subscribe({
594
693
  onCustomEvent: ({ event }) => {
595
- if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
694
+ if (event.name === INTERRUPT_EVENT_NAME) localLegacy = {
596
695
  name: event.name,
597
696
  value: event.value
598
697
  };
599
698
  },
699
+ onRunFinishedEvent: (params) => {
700
+ if (params.outcome === "interrupt") localStandard = params.interrupts;
701
+ },
600
702
  onRunStartedEvent: () => {
601
- localInterrupt = null;
602
- setPendingEvent(null);
703
+ localLegacy = null;
704
+ localStandard = null;
705
+ responsesRef.current = {};
706
+ setPending(null);
603
707
  },
604
708
  onRunFinalized: () => {
605
- if (localInterrupt) {
606
- setPendingEvent(localInterrupt);
607
- localInterrupt = null;
608
- }
709
+ if (localStandard && localStandard.length > 0) setPending({
710
+ kind: "standard",
711
+ interrupts: localStandard
712
+ });
713
+ else if (localLegacy) setPending({
714
+ kind: "legacy",
715
+ event: localLegacy
716
+ });
717
+ localLegacy = null;
718
+ localStandard = null;
609
719
  },
610
720
  onRunFailed: () => {
611
- localInterrupt = null;
612
- setPendingEvent(null);
721
+ localLegacy = null;
722
+ localStandard = null;
723
+ responsesRef.current = {};
724
+ setPending(null);
613
725
  }
614
726
  });
615
727
  return () => subscription.unsubscribe();
616
728
  }, [agent]);
617
- const resolve = (0, react.useCallback)(async (response) => {
729
+ const submitStandardIfComplete = (0, react.useCallback)(async (interrupts) => {
730
+ if (!interrupts.every((i) => responsesRef.current[i.id])) return;
731
+ const expired = interrupts.find((i) => (0, _ag_ui_client.isInterruptExpired)(i));
732
+ if (expired) {
733
+ console.error(`[CopilotKit] useInterrupt: interrupt ${expired.id} expired at ${expired.expiresAt}; not resuming.`);
734
+ responsesRef.current = {};
735
+ setPending(null);
736
+ return;
737
+ }
738
+ const resume = (0, _ag_ui_client.buildResumeArray)(interrupts, responsesRef.current);
739
+ for (const i of interrupts) {
740
+ if (!i.toolCallId) continue;
741
+ const response = responsesRef.current[i.id];
742
+ const content = response.status === "cancelled" ? { status: "cancelled" } : response.payload ?? { status: "resolved" };
743
+ agent.addMessage({
744
+ id: (0, _ag_ui_client.randomUUID)(),
745
+ role: "tool",
746
+ toolCallId: i.toolCallId,
747
+ content: JSON.stringify(content)
748
+ });
749
+ }
750
+ responsesRef.current = {};
618
751
  try {
752
+ return await copilotkit.runAgent({
753
+ agent,
754
+ resume
755
+ });
756
+ } catch (err) {
757
+ console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
758
+ setPending(null);
759
+ throw err;
760
+ }
761
+ }, [agent, copilotkit]);
762
+ const resolve = (0, react.useCallback)(async (payload, interruptId) => {
763
+ const current = pendingRef.current;
764
+ if (!current) return;
765
+ if (current.kind === "legacy") try {
619
766
  return await copilotkit.runAgent({
620
767
  agent,
621
768
  forwardedProps: { command: {
622
- resume: response,
623
- interruptEvent: pendingEventRef.current?.value
769
+ resume: payload,
770
+ interruptEvent: current.event.value
624
771
  } }
625
772
  });
626
773
  } catch (err) {
627
774
  console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
628
- setPendingEvent(null);
775
+ setPending(null);
629
776
  throw err;
630
777
  }
631
- }, [agent, copilotkit]);
778
+ if (current.interrupts.length > 1 && interruptId === void 0) console.warn(`[CopilotKit] useInterrupt: resolve()/cancel() called without an interruptId while ${current.interrupts.length} interrupts are open; defaulting to the first. Pass an interruptId to address a specific interrupt.`);
779
+ const id = interruptId ?? current.interrupts[0]?.id;
780
+ if (!id) return;
781
+ responsesRef.current[id] = {
782
+ status: "resolved",
783
+ payload
784
+ };
785
+ return submitStandardIfComplete(current.interrupts);
786
+ }, [
787
+ agent,
788
+ copilotkit,
789
+ submitStandardIfComplete
790
+ ]);
791
+ const cancel = (0, react.useCallback)(async (interruptId) => {
792
+ const current = pendingRef.current;
793
+ if (!current) return;
794
+ if (current.kind === "legacy") {
795
+ console.warn("[CopilotKit] useInterrupt: cancel() is not supported for legacy on_interrupt interrupts; dismissing.");
796
+ setPending(null);
797
+ return;
798
+ }
799
+ if (current.interrupts.length > 1 && interruptId === void 0) console.warn(`[CopilotKit] useInterrupt: resolve()/cancel() called without an interruptId while ${current.interrupts.length} interrupts are open; defaulting to the first. Pass an interruptId to address a specific interrupt.`);
800
+ const id = interruptId ?? current.interrupts[0]?.id;
801
+ if (!id) return;
802
+ responsesRef.current[id] = { status: "cancelled" };
803
+ return submitStandardIfComplete(current.interrupts);
804
+ }, [submitStandardIfComplete]);
632
805
  const renderRef = (0, react.useRef)(config.render);
633
806
  renderRef.current = config.render;
634
807
  const enabledRef = (0, react.useRef)(config.enabled);
@@ -637,6 +810,8 @@ function useInterrupt(config) {
637
810
  handlerRef.current = config.handler;
638
811
  const resolveRef = (0, react.useRef)(resolve);
639
812
  resolveRef.current = resolve;
813
+ const cancelRef = (0, react.useRef)(cancel);
814
+ cancelRef.current = cancel;
640
815
  const isEnabled = (event) => {
641
816
  const predicate = enabledRef.current;
642
817
  if (!predicate) return true;
@@ -648,11 +823,12 @@ function useInterrupt(config) {
648
823
  }
649
824
  };
650
825
  (0, react.useEffect)(() => {
651
- if (!pendingEvent) {
826
+ if (!pending) {
652
827
  setHandlerResult(null);
653
828
  return;
654
829
  }
655
- if (!isEnabled(pendingEvent)) {
830
+ const legacyEvent = toLegacyEvent(pending);
831
+ if (!isEnabled(legacyEvent)) {
656
832
  setHandlerResult(null);
657
833
  return;
658
834
  }
@@ -665,8 +841,11 @@ function useInterrupt(config) {
665
841
  let maybePromise;
666
842
  try {
667
843
  maybePromise = handler({
668
- event: pendingEvent,
669
- resolve: resolveRef.current
844
+ event: legacyEvent,
845
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
846
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
847
+ resolve: resolveRef.current,
848
+ cancel: cancelRef.current
670
849
  });
671
850
  } catch (err) {
672
851
  console.error("[CopilotKit] useInterrupt handler threw; result will be null:", err);
@@ -685,19 +864,24 @@ function useInterrupt(config) {
685
864
  return () => {
686
865
  cancelled = true;
687
866
  };
688
- }, [pendingEvent]);
867
+ }, [pending]);
689
868
  const element = (0, react.useMemo)(() => {
690
- if (!pendingEvent) return null;
691
- if (!isEnabled(pendingEvent)) return null;
869
+ if (!pending) return null;
870
+ const legacyEvent = toLegacyEvent(pending);
871
+ if (!isEnabled(legacyEvent)) return null;
692
872
  return renderRef.current({
693
- event: pendingEvent,
873
+ event: legacyEvent,
874
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
875
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
694
876
  result: handlerResult,
695
- resolve
877
+ resolve,
878
+ cancel
696
879
  });
697
880
  }, [
698
- pendingEvent,
881
+ pending,
699
882
  handlerResult,
700
- resolve
883
+ resolve,
884
+ cancel
701
885
  ]);
702
886
  (0, react.useEffect)(() => {
703
887
  if (config.renderInChat === false) return;
@@ -959,7 +1143,7 @@ function useThreadStoreSelector(store, selector) {
959
1143
  return (0, react.useSyncExternalStore)((0, react.useCallback)((onStoreChange) => {
960
1144
  const subscription = store.select(selector).subscribe(onStoreChange);
961
1145
  return () => subscription.unsubscribe();
962
- }, [store, selector]), () => selector(store.getState()));
1146
+ }, [store, selector]), () => selector(store.getState()), () => selector(store.getServerState()));
963
1147
  }
964
1148
  /**
965
1149
  * React hook for listing and managing Intelligence platform threads.
@@ -970,9 +1154,9 @@ function useThreadStoreSelector(store, selector) {
970
1154
  * current without polling — thread creates, renames, archives, and deletes
971
1155
  * from any client are reflected immediately.
972
1156
  *
973
- * Mutation methods (`renameThread`, `archiveThread`, `deleteThread`) return
974
- * promises that resolve once the platform confirms the operation and reject
975
- * with an `Error` on failure.
1157
+ * Mutation methods (`renameThread`, `archiveThread`, `unarchiveThread`,
1158
+ * `deleteThread`) return promises that resolve once the platform confirms the
1159
+ * operation and reject with an `Error` on failure.
976
1160
  *
977
1161
  * @param input - Agent identifier and optional list controls.
978
1162
  * @returns Thread list state and stable mutation callbacks.
@@ -1002,7 +1186,7 @@ function useThreadStoreSelector(store, selector) {
1002
1186
  * }
1003
1187
  * ```
1004
1188
  */
1005
- function useThreads({ agentId, includeArchived, limit }) {
1189
+ function useThreads({ agentId, includeArchived, limit, enabled = true }) {
1006
1190
  const { copilotkit } = (0, _copilotkit_react_core_v2_context.useCopilotKit)();
1007
1191
  const [store] = (0, react.useState)(() => (0, _copilotkit_core.ɵcreateThreadStore)({ fetch: globalThis.fetch }));
1008
1192
  const coreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreads);
@@ -1019,6 +1203,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1019
1203
  const storeError = useThreadStoreSelector(store, _copilotkit_core.ɵselectThreadsError);
1020
1204
  const hasMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectHasNextPage);
1021
1205
  const isFetchingMoreThreads = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsFetchingNextPage);
1206
+ const isMutating = useThreadStoreSelector(store, _copilotkit_core.ɵselectIsMutating);
1022
1207
  const headersKey = (0, react.useMemo)(() => {
1023
1208
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
1024
1209
  }, [copilotkit.headers]);
@@ -1039,9 +1224,16 @@ function useThreads({ agentId, includeArchived, limit }) {
1039
1224
  return /* @__PURE__ */ new Error("Thread mutations are not available on this CopilotKit runtime");
1040
1225
  }, [threadMutationsSupported]);
1041
1226
  const [hasDispatchedContext, setHasDispatchedContext] = (0, react.useState)(false);
1042
- const preConnectLoading = !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
1043
- const isLoading = runtimeError || threadEndpointsError ? false : preConnectLoading || storeIsLoading;
1044
- const error = runtimeError ?? threadEndpointsError ?? storeError;
1227
+ const preConnectLoading = enabled && !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
1228
+ const [configErrorDismissed, setConfigErrorDismissed] = (0, react.useState)(false);
1229
+ (0, react.useEffect)(() => {
1230
+ setConfigErrorDismissed(false);
1231
+ }, [runtimeError, threadEndpointsError]);
1232
+ const activeRuntimeError = configErrorDismissed ? null : runtimeError;
1233
+ const activeThreadEndpointsError = configErrorDismissed ? null : threadEndpointsError;
1234
+ const isLoading = activeRuntimeError || activeThreadEndpointsError ? false : preConnectLoading || storeIsLoading;
1235
+ const error = activeRuntimeError ?? activeThreadEndpointsError ?? storeError;
1236
+ const listError = storeError;
1045
1237
  (0, react.useEffect)(() => {
1046
1238
  store.start();
1047
1239
  return () => {
@@ -1049,6 +1241,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1049
1241
  };
1050
1242
  }, [store]);
1051
1243
  (0, react.useEffect)(() => {
1244
+ if (!enabled) return;
1052
1245
  copilotkit.registerThreadStore(agentId, store);
1053
1246
  return () => {
1054
1247
  copilotkit.unregisterThreadStore(agentId);
@@ -1056,9 +1249,15 @@ function useThreads({ agentId, includeArchived, limit }) {
1056
1249
  }, [
1057
1250
  copilotkit,
1058
1251
  agentId,
1059
- store
1252
+ store,
1253
+ enabled
1060
1254
  ]);
1061
1255
  (0, react.useEffect)(() => {
1256
+ if (!enabled) {
1257
+ store.setContext(null);
1258
+ setHasDispatchedContext(false);
1259
+ return;
1260
+ }
1062
1261
  if (!copilotkit.runtimeUrl) {
1063
1262
  store.setContext(null);
1064
1263
  setHasDispatchedContext(false);
@@ -1082,6 +1281,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1082
1281
  setHasDispatchedContext(true);
1083
1282
  }, [
1084
1283
  store,
1284
+ enabled,
1085
1285
  copilotkit.runtimeUrl,
1086
1286
  runtimeStatus,
1087
1287
  headersKey,
@@ -1099,16 +1299,25 @@ function useThreads({ agentId, includeArchived, limit }) {
1099
1299
  }, [threadMutationsError]);
1100
1300
  const renameThread = (0, react.useMemo)(() => guardMutation((threadId, name) => store.renameThread(threadId, name)), [store, guardMutation]);
1101
1301
  const archiveThread = (0, react.useMemo)(() => guardMutation((threadId) => store.archiveThread(threadId)), [store, guardMutation]);
1302
+ const unarchiveThread = (0, react.useMemo)(() => guardMutation((threadId) => store.unarchiveThread(threadId)), [store, guardMutation]);
1102
1303
  const deleteThread = (0, react.useMemo)(() => guardMutation((threadId) => store.deleteThread(threadId)), [store, guardMutation]);
1103
1304
  return {
1104
1305
  threads,
1105
1306
  isLoading,
1106
1307
  error,
1308
+ listError,
1107
1309
  hasMoreThreads,
1108
1310
  isFetchingMoreThreads,
1311
+ isMutating,
1109
1312
  fetchMoreThreads: (0, react.useCallback)(() => store.fetchNextPage(), [store]),
1313
+ refetchThreads: (0, react.useCallback)(() => store.refetchThreads(), [store]),
1314
+ startNewThread: (0, react.useCallback)(() => {
1315
+ setConfigErrorDismissed(true);
1316
+ store.startNewThread();
1317
+ }, [store]),
1110
1318
  renameThread,
1111
1319
  archiveThread,
1320
+ unarchiveThread,
1112
1321
  deleteThread
1113
1322
  };
1114
1323
  }
@@ -1183,10 +1392,20 @@ function useRenderTool(config, deps) {
1183
1392
  }) : defineToolCallRenderer({
1184
1393
  name: config.name,
1185
1394
  args: config.parameters,
1186
- render: (props) => config.render({
1187
- ...props,
1188
- parameters: props.args
1189
- }),
1395
+ render: (props) => {
1396
+ if (props.status === _copilotkit_core.ToolCallStatus.InProgress) return config.render({
1397
+ ...props,
1398
+ parameters: props.args
1399
+ });
1400
+ if (props.status === _copilotkit_core.ToolCallStatus.Executing) return config.render({
1401
+ ...props,
1402
+ parameters: props.args
1403
+ });
1404
+ return config.render({
1405
+ ...props,
1406
+ parameters: props.args
1407
+ });
1408
+ },
1190
1409
  ...config.agentId ? { agentId: config.agentId } : {}
1191
1410
  });
1192
1411
  copilotkit.addHookRenderToolCall(renderer);