@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
@@ -3,8 +3,8 @@ import React, { createContext, useCallback, useContext, useEffect, useLayoutEffe
3
3
  import { DEFAULT_AGENT_ID, randomUUID } from "@copilotkit/shared";
4
4
  import { twMerge } from "tailwind-merge";
5
5
  import { jsx } from "react/jsx-runtime";
6
- import { HttpAgent } from "@ag-ui/client";
7
- import { CopilotKitCoreRuntimeConnectionStatus, ProxiedCopilotRuntimeAgent, ToolCallStatus, ɵcreateThreadStore, ɵselectHasNextPage, ɵselectIsFetchingNextPage, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
6
+ import { HttpAgent, buildResumeArray, isInterruptExpired, randomUUID as randomUUID$1 } from "@ag-ui/client";
7
+ import { CopilotKitCoreRuntimeConnectionStatus, ProxiedCopilotRuntimeAgent, ToolCallStatus, ɵcreateThreadStore, ɵselectHasNextPage, ɵselectIsFetchingNextPage, ɵselectIsMutating, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
8
8
  import { z } from "zod";
9
9
 
10
10
  //#region src/v2/lib/slots.tsx
@@ -115,6 +115,25 @@ const CopilotChatDefaultLabels = {
115
115
  modalHeaderTitle: "CopilotKit Chat",
116
116
  welcomeMessageText: "How can I help you today?"
117
117
  };
118
+ /**
119
+ * Mobile breakpoint below which the chat modal and the thread-list drawer are
120
+ * mutually exclusive. At or above this width both surfaces may coexist. This
121
+ * mirrors the `(max-width: 767px)` / `(min-width: 768px)` split already used by
122
+ * CopilotChatInput and CopilotSidebarView.
123
+ */
124
+ const MOBILE_MAX_WIDTH_PX = 767;
125
+ /**
126
+ * Reports whether the current viewport is in the mobile range (`<768px`), where
127
+ * the chat modal and drawer must not be open simultaneously. SSR-safe and
128
+ * defensive against environments without `matchMedia` (treated as desktop, so
129
+ * no mutual-exclusion constraint is applied).
130
+ *
131
+ * @returns `true` when the viewport is mobile-width, `false` otherwise.
132
+ */
133
+ function isMobileViewport() {
134
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
135
+ return window.matchMedia(`(max-width: ${MOBILE_MAX_WIDTH_PX}px)`).matches;
136
+ }
118
137
  const CopilotChatConfiguration = createContext(null);
119
138
  const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId, hasExplicitThreadId, isModalDefaultOpen }) => {
120
139
  const parentConfig = useContext(CopilotChatConfiguration);
@@ -125,12 +144,22 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
125
144
  ...stableLabels
126
145
  }), [stableLabels, parentConfig?.labels]);
127
146
  const resolvedAgentId = agentId ?? parentConfig?.agentId ?? DEFAULT_AGENT_ID;
147
+ const threadIdPropIsAuthoritative = threadId !== void 0 && hasExplicitThreadId !== false;
148
+ const isThreadIdControlled = threadIdPropIsAuthoritative;
149
+ const [activeThreadOverride, setActiveThreadOverride] = useState(null);
128
150
  const resolvedThreadId = useMemo(() => {
129
- if (threadId) return threadId;
151
+ if (threadIdPropIsAuthoritative) return threadId;
152
+ if (activeThreadOverride) return activeThreadOverride.threadId;
130
153
  if (parentConfig?.threadId) return parentConfig.threadId;
154
+ if (threadId) return threadId;
131
155
  return randomUUID();
132
- }, [threadId, parentConfig?.threadId]);
133
- const resolvedHasExplicitThreadId = (hasExplicitThreadId !== void 0 ? hasExplicitThreadId : !!threadId) || !!parentConfig?.hasExplicitThreadId;
156
+ }, [
157
+ threadIdPropIsAuthoritative,
158
+ threadId,
159
+ parentConfig?.threadId,
160
+ activeThreadOverride
161
+ ]);
162
+ const resolvedHasExplicitThreadId = (threadIdPropIsAuthoritative ? true : activeThreadOverride?.explicit ?? hasExplicitThreadId ?? false) || !!parentConfig?.hasExplicitThreadId;
134
163
  const [internalModalOpen, setInternalModalOpen] = useState(isModalDefaultOpen ?? true);
135
164
  const hasExplicitDefault = isModalDefaultOpen !== void 0;
136
165
  const setAndSync = useCallback((open) => {
@@ -149,20 +178,113 @@ const CopilotChatConfigurationProvider = ({ children, labels, agentId, threadId,
149
178
  }, [parentConfig?.isModalOpen, hasExplicitDefault]);
150
179
  const resolvedIsModalOpen = hasExplicitDefault ? internalModalOpen : parentConfig?.isModalOpen ?? internalModalOpen;
151
180
  const resolvedSetModalOpen = hasExplicitDefault ? setAndSync : parentConfig?.setModalOpen ?? setInternalModalOpen;
181
+ const [ownDrawerOpen, setOwnDrawerOpen] = useState(false);
182
+ const [ownDrawerCount, setOwnDrawerCount] = useState(0);
183
+ const modalCloseRef = useRef(() => {});
184
+ modalCloseRef.current = resolvedSetModalOpen;
185
+ const registeredModalClosersRef = useRef([]);
186
+ const ownRegisterModalCloser = useCallback((closeModal) => {
187
+ registeredModalClosersRef.current.push(closeModal);
188
+ return () => {
189
+ registeredModalClosersRef.current = registeredModalClosersRef.current.filter((entry) => entry !== closeModal);
190
+ };
191
+ }, []);
192
+ const ownSetDrawerOpen = useCallback((open) => {
193
+ setOwnDrawerOpen(open);
194
+ if (open && isMobileViewport()) {
195
+ const registered = registeredModalClosersRef.current;
196
+ (registered.length > 0 ? registered[registered.length - 1] : modalCloseRef.current)(false);
197
+ }
198
+ }, []);
199
+ const ownRegisterDrawer = useCallback(() => {
200
+ setOwnDrawerCount((count) => count + 1);
201
+ return () => {
202
+ setOwnDrawerCount((count) => Math.max(0, count - 1));
203
+ };
204
+ }, []);
205
+ const resolvedDrawerOpen = parentConfig ? parentConfig.drawerOpen : ownDrawerOpen;
206
+ const resolvedSetDrawerOpen = parentConfig ? parentConfig.setDrawerOpen : ownSetDrawerOpen;
207
+ const resolvedDrawerRegistered = parentConfig ? parentConfig.drawerRegistered : ownDrawerCount > 0;
208
+ const resolvedRegisterDrawer = parentConfig ? parentConfig.registerDrawer : ownRegisterDrawer;
209
+ const resolvedRegisterModalCloser = parentConfig ? parentConfig.ɵregisterModalCloser : ownRegisterModalCloser;
210
+ useEffect(() => {
211
+ if (!hasExplicitDefault) return;
212
+ return resolvedRegisterModalCloser(resolvedSetModalOpen);
213
+ }, [
214
+ hasExplicitDefault,
215
+ resolvedRegisterModalCloser,
216
+ resolvedSetModalOpen
217
+ ]);
218
+ const isThreadIdControlledRef = useRef(isThreadIdControlled);
219
+ isThreadIdControlledRef.current = isThreadIdControlled;
220
+ const ownSetActiveThreadId = useCallback((id, options) => {
221
+ setActiveThreadOverride({
222
+ threadId: id,
223
+ explicit: options?.explicit ?? true
224
+ });
225
+ }, []);
226
+ const ownStartNewThread = useCallback(() => {
227
+ setActiveThreadOverride({
228
+ threadId: randomUUID(),
229
+ explicit: false
230
+ });
231
+ }, []);
232
+ const parentSetActiveThreadId = parentConfig?.setActiveThreadId;
233
+ const parentStartNewThread = parentConfig?.startNewThread;
234
+ const resolvedSetActiveThreadId = useCallback((id, options) => {
235
+ if (isThreadIdControlledRef.current) {
236
+ console.warn("[CopilotKit] Ignoring setActiveThreadId(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider.");
237
+ return;
238
+ }
239
+ if (parentSetActiveThreadId) {
240
+ parentSetActiveThreadId(id, options);
241
+ return;
242
+ }
243
+ ownSetActiveThreadId(id, options);
244
+ }, [parentSetActiveThreadId, ownSetActiveThreadId]);
245
+ const resolvedStartNewThread = useCallback(() => {
246
+ if (isThreadIdControlledRef.current) {
247
+ console.warn("[CopilotKit] Ignoring startNewThread(): threadId is controlled via the `threadId` prop on CopilotChatConfigurationProvider.");
248
+ return;
249
+ }
250
+ if (parentStartNewThread) {
251
+ parentStartNewThread();
252
+ return;
253
+ }
254
+ ownStartNewThread();
255
+ }, [parentStartNewThread, ownStartNewThread]);
256
+ const setModalOpenWithDrawerExclusion = useCallback((open) => {
257
+ if (open && isMobileViewport()) resolvedSetDrawerOpen(false);
258
+ resolvedSetModalOpen(open);
259
+ }, [resolvedSetModalOpen, resolvedSetDrawerOpen]);
152
260
  const configurationValue = useMemo(() => ({
153
261
  labels: mergedLabels,
154
262
  agentId: resolvedAgentId,
155
263
  threadId: resolvedThreadId,
156
264
  hasExplicitThreadId: resolvedHasExplicitThreadId,
157
265
  isModalOpen: resolvedIsModalOpen,
158
- setModalOpen: resolvedSetModalOpen
266
+ setModalOpen: setModalOpenWithDrawerExclusion,
267
+ drawerOpen: resolvedDrawerOpen,
268
+ setDrawerOpen: resolvedSetDrawerOpen,
269
+ drawerRegistered: resolvedDrawerRegistered,
270
+ registerDrawer: resolvedRegisterDrawer,
271
+ ɵregisterModalCloser: resolvedRegisterModalCloser,
272
+ setActiveThreadId: resolvedSetActiveThreadId,
273
+ startNewThread: resolvedStartNewThread
159
274
  }), [
160
275
  mergedLabels,
161
276
  resolvedAgentId,
162
277
  resolvedThreadId,
163
278
  resolvedHasExplicitThreadId,
164
279
  resolvedIsModalOpen,
165
- resolvedSetModalOpen
280
+ setModalOpenWithDrawerExclusion,
281
+ resolvedDrawerOpen,
282
+ resolvedSetDrawerOpen,
283
+ resolvedDrawerRegistered,
284
+ resolvedRegisterDrawer,
285
+ resolvedRegisterModalCloser,
286
+ resolvedSetActiveThreadId,
287
+ resolvedStartNewThread
166
288
  ]);
167
289
  return /* @__PURE__ */ jsx(CopilotChatConfiguration.Provider, {
168
290
  value: configurationValue,
@@ -204,7 +326,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
204
326
  if (isRuntimeConfigured && (status === CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
205
327
  const cached = provisionalAgentCache.current.get(agentId);
206
328
  if (cached) {
207
- cached.headers = { ...copilotkit.headers };
329
+ copilotkit.applyHeadersToAgent(cached);
208
330
  return cached;
209
331
  }
210
332
  const provisional = new ProxiedCopilotRuntimeAgent({
@@ -213,14 +335,14 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
213
335
  transport: copilotkit.runtimeTransport,
214
336
  runtimeMode: "pending"
215
337
  });
216
- provisional.headers = { ...copilotkit.headers };
338
+ copilotkit.applyHeadersToAgent(provisional);
217
339
  provisionalAgentCache.current.set(agentId, provisional);
218
340
  return provisional;
219
341
  }
220
342
  if (isRuntimeConfigured && status === CopilotKitCoreRuntimeConnectionStatus.Error) {
221
343
  const cached = provisionalAgentCache.current.get(agentId);
222
344
  if (cached) {
223
- cached.headers = { ...copilotkit.headers };
345
+ copilotkit.applyHeadersToAgent(cached);
224
346
  return cached;
225
347
  }
226
348
  const provisional = new ProxiedCopilotRuntimeAgent({
@@ -229,7 +351,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
229
351
  transport: copilotkit.runtimeTransport,
230
352
  runtimeMode: "pending"
231
353
  });
232
- provisional.headers = { ...copilotkit.headers };
354
+ copilotkit.applyHeadersToAgent(provisional);
233
355
  provisionalAgentCache.current.set(agentId, provisional);
234
356
  return provisional;
235
357
  }
@@ -280,7 +402,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
280
402
  updateFlags
281
403
  ]);
282
404
  useEffect(() => {
283
- if (agent instanceof HttpAgent) agent.headers = { ...copilotkit.headers };
405
+ if (agent instanceof HttpAgent) copilotkit.applyHeadersToAgent(agent);
284
406
  }, [agent, JSON.stringify(copilotkit.headers)]);
285
407
  const chatConfig = useCopilotChatConfiguration();
286
408
  const configThreadId = chatConfig?.threadId;
@@ -492,114 +614,165 @@ const INTERRUPT_EVENT_NAME = "on_interrupt";
492
614
  function isPromiseLike(value) {
493
615
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
494
616
  }
617
+ /** Derive the legacy-compatible `event` for any pending interrupt. */
618
+ function toLegacyEvent(pending) {
619
+ if (pending.kind === "legacy") return pending.event;
620
+ return {
621
+ name: INTERRUPT_EVENT_NAME,
622
+ value: pending.interrupts[0]
623
+ };
624
+ }
495
625
  /**
496
- * Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.
497
- *
498
- * The hook listens to custom events on the active agent, stores interrupt payloads per run,
499
- * and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume
500
- * execution with user-provided data.
501
- *
502
- * - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.
503
- * - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.
626
+ * Handles agent interrupts with optional filtering, preprocessing, and resume behavior.
504
627
  *
505
- * `event.value` is typed as `any` since the interrupt payload shape depends on your agent.
506
- * Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.
628
+ * Supports both the AG-UI standard interrupt flow (`RUN_FINISHED` with
629
+ * `outcome.type === "interrupt"`) and the legacy custom-event flow
630
+ * (`on_interrupt`). For standard interrupts, `render` receives `interrupt`
631
+ * (the primary one) and `interrupts` (the full open set); call `resolve(payload)`
632
+ * to resume or `cancel()` to cancel. Resuming addresses the targeted interrupt
633
+ * and, once every open interrupt is addressed, submits a single spec `resume`
634
+ * array via `copilotkit.runAgent`.
507
635
  *
508
- * @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.
509
- * @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).
510
- * @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).
511
- * Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always
512
- * either the handler's resolved return value or `null` (including when no handler is provided,
513
- * when filtering skips the interrupt, or when handler execution fails).
636
+ * - `renderInChat: true` (default): the element is published into `<CopilotChat>`; returns `void`.
637
+ * - `renderInChat: false`: the hook returns the interrupt element for manual placement.
514
638
  *
515
639
  * @example
516
640
  * ```tsx
517
- * import { useInterrupt } from "@copilotkit/react-core/v2";
518
- *
519
- * function InterruptUI() {
520
- * useInterrupt({
521
- * render: ({ event, resolve }) => (
522
- * <div>
523
- * <p>{event.value.question}</p>
524
- * <button onClick={() => resolve({ approved: true })}>Approve</button>
525
- * <button onClick={() => resolve({ approved: false })}>Reject</button>
526
- * </div>
527
- * ),
528
- * });
529
- *
530
- * return null;
531
- * }
532
- * ```
533
- *
534
- * @example
535
- * ```tsx
536
- * import { useInterrupt } from "@copilotkit/react-core/v2";
537
- *
538
- * function CustomPanel() {
539
- * const interruptElement = useInterrupt({
540
- * renderInChat: false,
541
- * enabled: (event) => event.value.startsWith("approval:"),
542
- * handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
543
- * render: ({ event, result, resolve }) => (
544
- * <aside>
545
- * <strong>{result?.label ?? ""}</strong>
546
- * <button onClick={() => resolve({ value: event.value })}>Continue</button>
547
- * </aside>
548
- * ),
549
- * });
550
- *
551
- * return <>{interruptElement}</>;
552
- * }
641
+ * useInterrupt({
642
+ * render: ({ interrupt, resolve, cancel }) => (
643
+ * <div>
644
+ * <p>{interrupt?.message}</p>
645
+ * <button onClick={() => resolve({ approved: true })}>Approve</button>
646
+ * <button onClick={() => cancel()}>Cancel</button>
647
+ * </div>
648
+ * ),
649
+ * });
553
650
  * ```
554
651
  */
555
652
  function useInterrupt(config) {
556
653
  const { copilotkit } = useCopilotKit();
557
654
  const { agent } = useAgent({ agentId: config.agentId });
558
- const [pendingEvent, setPendingEvent] = useState(null);
559
- const pendingEventRef = useRef(pendingEvent);
560
- pendingEventRef.current = pendingEvent;
655
+ const [pending, setPending] = useState(null);
656
+ const pendingRef = useRef(pending);
657
+ pendingRef.current = pending;
561
658
  const [handlerResult, setHandlerResult] = useState(null);
659
+ const responsesRef = useRef({});
562
660
  useEffect(() => {
563
- let localInterrupt = null;
661
+ let localLegacy = null;
662
+ let localStandard = null;
564
663
  const subscription = agent.subscribe({
565
664
  onCustomEvent: ({ event }) => {
566
- if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
665
+ if (event.name === INTERRUPT_EVENT_NAME) localLegacy = {
567
666
  name: event.name,
568
667
  value: event.value
569
668
  };
570
669
  },
670
+ onRunFinishedEvent: (params) => {
671
+ if (params.outcome === "interrupt") localStandard = params.interrupts;
672
+ },
571
673
  onRunStartedEvent: () => {
572
- localInterrupt = null;
573
- setPendingEvent(null);
674
+ localLegacy = null;
675
+ localStandard = null;
676
+ responsesRef.current = {};
677
+ setPending(null);
574
678
  },
575
679
  onRunFinalized: () => {
576
- if (localInterrupt) {
577
- setPendingEvent(localInterrupt);
578
- localInterrupt = null;
579
- }
680
+ if (localStandard && localStandard.length > 0) setPending({
681
+ kind: "standard",
682
+ interrupts: localStandard
683
+ });
684
+ else if (localLegacy) setPending({
685
+ kind: "legacy",
686
+ event: localLegacy
687
+ });
688
+ localLegacy = null;
689
+ localStandard = null;
580
690
  },
581
691
  onRunFailed: () => {
582
- localInterrupt = null;
583
- setPendingEvent(null);
692
+ localLegacy = null;
693
+ localStandard = null;
694
+ responsesRef.current = {};
695
+ setPending(null);
584
696
  }
585
697
  });
586
698
  return () => subscription.unsubscribe();
587
699
  }, [agent]);
588
- const resolve = useCallback(async (response) => {
700
+ const submitStandardIfComplete = useCallback(async (interrupts) => {
701
+ if (!interrupts.every((i) => responsesRef.current[i.id])) return;
702
+ const expired = interrupts.find((i) => isInterruptExpired(i));
703
+ if (expired) {
704
+ console.error(`[CopilotKit] useInterrupt: interrupt ${expired.id} expired at ${expired.expiresAt}; not resuming.`);
705
+ responsesRef.current = {};
706
+ setPending(null);
707
+ return;
708
+ }
709
+ const resume = buildResumeArray(interrupts, responsesRef.current);
710
+ for (const i of interrupts) {
711
+ if (!i.toolCallId) continue;
712
+ const response = responsesRef.current[i.id];
713
+ const content = response.status === "cancelled" ? { status: "cancelled" } : response.payload ?? { status: "resolved" };
714
+ agent.addMessage({
715
+ id: randomUUID$1(),
716
+ role: "tool",
717
+ toolCallId: i.toolCallId,
718
+ content: JSON.stringify(content)
719
+ });
720
+ }
721
+ responsesRef.current = {};
589
722
  try {
723
+ return await copilotkit.runAgent({
724
+ agent,
725
+ resume
726
+ });
727
+ } catch (err) {
728
+ console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
729
+ setPending(null);
730
+ throw err;
731
+ }
732
+ }, [agent, copilotkit]);
733
+ const resolve = useCallback(async (payload, interruptId) => {
734
+ const current = pendingRef.current;
735
+ if (!current) return;
736
+ if (current.kind === "legacy") try {
590
737
  return await copilotkit.runAgent({
591
738
  agent,
592
739
  forwardedProps: { command: {
593
- resume: response,
594
- interruptEvent: pendingEventRef.current?.value
740
+ resume: payload,
741
+ interruptEvent: current.event.value
595
742
  } }
596
743
  });
597
744
  } catch (err) {
598
745
  console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
599
- setPendingEvent(null);
746
+ setPending(null);
600
747
  throw err;
601
748
  }
602
- }, [agent, copilotkit]);
749
+ 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.`);
750
+ const id = interruptId ?? current.interrupts[0]?.id;
751
+ if (!id) return;
752
+ responsesRef.current[id] = {
753
+ status: "resolved",
754
+ payload
755
+ };
756
+ return submitStandardIfComplete(current.interrupts);
757
+ }, [
758
+ agent,
759
+ copilotkit,
760
+ submitStandardIfComplete
761
+ ]);
762
+ const cancel = useCallback(async (interruptId) => {
763
+ const current = pendingRef.current;
764
+ if (!current) return;
765
+ if (current.kind === "legacy") {
766
+ console.warn("[CopilotKit] useInterrupt: cancel() is not supported for legacy on_interrupt interrupts; dismissing.");
767
+ setPending(null);
768
+ return;
769
+ }
770
+ 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.`);
771
+ const id = interruptId ?? current.interrupts[0]?.id;
772
+ if (!id) return;
773
+ responsesRef.current[id] = { status: "cancelled" };
774
+ return submitStandardIfComplete(current.interrupts);
775
+ }, [submitStandardIfComplete]);
603
776
  const renderRef = useRef(config.render);
604
777
  renderRef.current = config.render;
605
778
  const enabledRef = useRef(config.enabled);
@@ -608,6 +781,8 @@ function useInterrupt(config) {
608
781
  handlerRef.current = config.handler;
609
782
  const resolveRef = useRef(resolve);
610
783
  resolveRef.current = resolve;
784
+ const cancelRef = useRef(cancel);
785
+ cancelRef.current = cancel;
611
786
  const isEnabled = (event) => {
612
787
  const predicate = enabledRef.current;
613
788
  if (!predicate) return true;
@@ -619,11 +794,12 @@ function useInterrupt(config) {
619
794
  }
620
795
  };
621
796
  useEffect(() => {
622
- if (!pendingEvent) {
797
+ if (!pending) {
623
798
  setHandlerResult(null);
624
799
  return;
625
800
  }
626
- if (!isEnabled(pendingEvent)) {
801
+ const legacyEvent = toLegacyEvent(pending);
802
+ if (!isEnabled(legacyEvent)) {
627
803
  setHandlerResult(null);
628
804
  return;
629
805
  }
@@ -636,8 +812,11 @@ function useInterrupt(config) {
636
812
  let maybePromise;
637
813
  try {
638
814
  maybePromise = handler({
639
- event: pendingEvent,
640
- resolve: resolveRef.current
815
+ event: legacyEvent,
816
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
817
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
818
+ resolve: resolveRef.current,
819
+ cancel: cancelRef.current
641
820
  });
642
821
  } catch (err) {
643
822
  console.error("[CopilotKit] useInterrupt handler threw; result will be null:", err);
@@ -656,19 +835,24 @@ function useInterrupt(config) {
656
835
  return () => {
657
836
  cancelled = true;
658
837
  };
659
- }, [pendingEvent]);
838
+ }, [pending]);
660
839
  const element = useMemo(() => {
661
- if (!pendingEvent) return null;
662
- if (!isEnabled(pendingEvent)) return null;
840
+ if (!pending) return null;
841
+ const legacyEvent = toLegacyEvent(pending);
842
+ if (!isEnabled(legacyEvent)) return null;
663
843
  return renderRef.current({
664
- event: pendingEvent,
844
+ event: legacyEvent,
845
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
846
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
665
847
  result: handlerResult,
666
- resolve
848
+ resolve,
849
+ cancel
667
850
  });
668
851
  }, [
669
- pendingEvent,
852
+ pending,
670
853
  handlerResult,
671
- resolve
854
+ resolve,
855
+ cancel
672
856
  ]);
673
857
  useEffect(() => {
674
858
  if (config.renderInChat === false) return;
@@ -930,7 +1114,7 @@ function useThreadStoreSelector(store, selector) {
930
1114
  return useSyncExternalStore(useCallback((onStoreChange) => {
931
1115
  const subscription = store.select(selector).subscribe(onStoreChange);
932
1116
  return () => subscription.unsubscribe();
933
- }, [store, selector]), () => selector(store.getState()));
1117
+ }, [store, selector]), () => selector(store.getState()), () => selector(store.getServerState()));
934
1118
  }
935
1119
  /**
936
1120
  * React hook for listing and managing Intelligence platform threads.
@@ -941,9 +1125,9 @@ function useThreadStoreSelector(store, selector) {
941
1125
  * current without polling — thread creates, renames, archives, and deletes
942
1126
  * from any client are reflected immediately.
943
1127
  *
944
- * Mutation methods (`renameThread`, `archiveThread`, `deleteThread`) return
945
- * promises that resolve once the platform confirms the operation and reject
946
- * with an `Error` on failure.
1128
+ * Mutation methods (`renameThread`, `archiveThread`, `unarchiveThread`,
1129
+ * `deleteThread`) return promises that resolve once the platform confirms the
1130
+ * operation and reject with an `Error` on failure.
947
1131
  *
948
1132
  * @param input - Agent identifier and optional list controls.
949
1133
  * @returns Thread list state and stable mutation callbacks.
@@ -973,7 +1157,7 @@ function useThreadStoreSelector(store, selector) {
973
1157
  * }
974
1158
  * ```
975
1159
  */
976
- function useThreads({ agentId, includeArchived, limit }) {
1160
+ function useThreads({ agentId, includeArchived, limit, enabled = true }) {
977
1161
  const { copilotkit } = useCopilotKit();
978
1162
  const [store] = useState(() => ɵcreateThreadStore({ fetch: globalThis.fetch }));
979
1163
  const coreThreads = useThreadStoreSelector(store, ɵselectThreads);
@@ -990,6 +1174,7 @@ function useThreads({ agentId, includeArchived, limit }) {
990
1174
  const storeError = useThreadStoreSelector(store, ɵselectThreadsError);
991
1175
  const hasMoreThreads = useThreadStoreSelector(store, ɵselectHasNextPage);
992
1176
  const isFetchingMoreThreads = useThreadStoreSelector(store, ɵselectIsFetchingNextPage);
1177
+ const isMutating = useThreadStoreSelector(store, ɵselectIsMutating);
993
1178
  const headersKey = useMemo(() => {
994
1179
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
995
1180
  }, [copilotkit.headers]);
@@ -1010,9 +1195,16 @@ function useThreads({ agentId, includeArchived, limit }) {
1010
1195
  return /* @__PURE__ */ new Error("Thread mutations are not available on this CopilotKit runtime");
1011
1196
  }, [threadMutationsSupported]);
1012
1197
  const [hasDispatchedContext, setHasDispatchedContext] = useState(false);
1013
- const preConnectLoading = !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
1014
- const isLoading = runtimeError || threadEndpointsError ? false : preConnectLoading || storeIsLoading;
1015
- const error = runtimeError ?? threadEndpointsError ?? storeError;
1198
+ const preConnectLoading = enabled && !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
1199
+ const [configErrorDismissed, setConfigErrorDismissed] = useState(false);
1200
+ useEffect(() => {
1201
+ setConfigErrorDismissed(false);
1202
+ }, [runtimeError, threadEndpointsError]);
1203
+ const activeRuntimeError = configErrorDismissed ? null : runtimeError;
1204
+ const activeThreadEndpointsError = configErrorDismissed ? null : threadEndpointsError;
1205
+ const isLoading = activeRuntimeError || activeThreadEndpointsError ? false : preConnectLoading || storeIsLoading;
1206
+ const error = activeRuntimeError ?? activeThreadEndpointsError ?? storeError;
1207
+ const listError = storeError;
1016
1208
  useEffect(() => {
1017
1209
  store.start();
1018
1210
  return () => {
@@ -1020,6 +1212,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1020
1212
  };
1021
1213
  }, [store]);
1022
1214
  useEffect(() => {
1215
+ if (!enabled) return;
1023
1216
  copilotkit.registerThreadStore(agentId, store);
1024
1217
  return () => {
1025
1218
  copilotkit.unregisterThreadStore(agentId);
@@ -1027,9 +1220,15 @@ function useThreads({ agentId, includeArchived, limit }) {
1027
1220
  }, [
1028
1221
  copilotkit,
1029
1222
  agentId,
1030
- store
1223
+ store,
1224
+ enabled
1031
1225
  ]);
1032
1226
  useEffect(() => {
1227
+ if (!enabled) {
1228
+ store.setContext(null);
1229
+ setHasDispatchedContext(false);
1230
+ return;
1231
+ }
1033
1232
  if (!copilotkit.runtimeUrl) {
1034
1233
  store.setContext(null);
1035
1234
  setHasDispatchedContext(false);
@@ -1053,6 +1252,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1053
1252
  setHasDispatchedContext(true);
1054
1253
  }, [
1055
1254
  store,
1255
+ enabled,
1056
1256
  copilotkit.runtimeUrl,
1057
1257
  runtimeStatus,
1058
1258
  headersKey,
@@ -1070,16 +1270,25 @@ function useThreads({ agentId, includeArchived, limit }) {
1070
1270
  }, [threadMutationsError]);
1071
1271
  const renameThread = useMemo(() => guardMutation((threadId, name) => store.renameThread(threadId, name)), [store, guardMutation]);
1072
1272
  const archiveThread = useMemo(() => guardMutation((threadId) => store.archiveThread(threadId)), [store, guardMutation]);
1273
+ const unarchiveThread = useMemo(() => guardMutation((threadId) => store.unarchiveThread(threadId)), [store, guardMutation]);
1073
1274
  const deleteThread = useMemo(() => guardMutation((threadId) => store.deleteThread(threadId)), [store, guardMutation]);
1074
1275
  return {
1075
1276
  threads,
1076
1277
  isLoading,
1077
1278
  error,
1279
+ listError,
1078
1280
  hasMoreThreads,
1079
1281
  isFetchingMoreThreads,
1282
+ isMutating,
1080
1283
  fetchMoreThreads: useCallback(() => store.fetchNextPage(), [store]),
1284
+ refetchThreads: useCallback(() => store.refetchThreads(), [store]),
1285
+ startNewThread: useCallback(() => {
1286
+ setConfigErrorDismissed(true);
1287
+ store.startNewThread();
1288
+ }, [store]),
1081
1289
  renameThread,
1082
1290
  archiveThread,
1291
+ unarchiveThread,
1083
1292
  deleteThread
1084
1293
  };
1085
1294
  }
@@ -1154,10 +1363,20 @@ function useRenderTool(config, deps) {
1154
1363
  }) : defineToolCallRenderer({
1155
1364
  name: config.name,
1156
1365
  args: config.parameters,
1157
- render: (props) => config.render({
1158
- ...props,
1159
- parameters: props.args
1160
- }),
1366
+ render: (props) => {
1367
+ if (props.status === ToolCallStatus.InProgress) return config.render({
1368
+ ...props,
1369
+ parameters: props.args
1370
+ });
1371
+ if (props.status === ToolCallStatus.Executing) return config.render({
1372
+ ...props,
1373
+ parameters: props.args
1374
+ });
1375
+ return config.render({
1376
+ ...props,
1377
+ parameters: props.args
1378
+ });
1379
+ },
1161
1380
  ...config.agentId ? { agentId: config.agentId } : {}
1162
1381
  });
1163
1382
  copilotkit.addHookRenderToolCall(renderer);