@copilotkit/react-core 1.61.0 → 1.61.2

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-BCxdKlMw.mjs → copilotkit-Be-o2UaU.mjs} +262 -128
  2. package/dist/copilotkit-Be-o2UaU.mjs.map +1 -0
  3. package/dist/{copilotkit-M1FiciGd.d.mts → copilotkit-Bpt1c_-q.d.mts} +79 -71
  4. package/dist/copilotkit-Bpt1c_-q.d.mts.map +1 -0
  5. package/dist/{copilotkit-DtPCrXXd.cjs → copilotkit-CTCjVxkH.cjs} +251 -117
  6. package/dist/copilotkit-CTCjVxkH.cjs.map +1 -0
  7. package/dist/{copilotkit-CEdu_aie.d.cts → copilotkit-ClqbUuGX.d.cts} +79 -71
  8. package/dist/copilotkit-ClqbUuGX.d.cts.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 +188 -96
  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 +219 -102
  24. package/dist/v2/headless.cjs.map +1 -1
  25. package/dist/v2/headless.d.cts +76 -68
  26. package/dist/v2/headless.d.cts.map +1 -1
  27. package/dist/v2/headless.d.mts +76 -68
  28. package/dist/v2/headless.d.mts.map +1 -1
  29. package/dist/v2/headless.mjs +220 -103
  30. package/dist/v2/headless.mjs.map +1 -1
  31. package/dist/v2/index.cjs +1 -1
  32. package/dist/v2/index.d.cts +2 -2
  33. package/dist/v2/index.d.mts +2 -2
  34. package/dist/v2/index.mjs +1 -1
  35. package/dist/v2/index.umd.js +250 -116
  36. package/dist/v2/index.umd.js.map +1 -1
  37. package/package.json +6 -6
  38. package/skills/react-core/references/provider-setup.md +20 -2
  39. package/dist/copilotkit-BCxdKlMw.mjs.map +0 -1
  40. package/dist/copilotkit-CEdu_aie.d.cts.map +0 -1
  41. package/dist/copilotkit-DtPCrXXd.cjs.map +0 -1
  42. package/dist/copilotkit-M1FiciGd.d.mts.map +0 -1
@@ -3,7 +3,7 @@ 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";
6
+ import { HttpAgent, buildResumeArray, isInterruptExpired, randomUUID as randomUUID$1 } from "@ag-ui/client";
7
7
  import { CopilotKitCoreRuntimeConnectionStatus, ProxiedCopilotRuntimeAgent, ToolCallStatus, ɵcreateThreadStore, ɵselectHasNextPage, ɵselectIsFetchingNextPage, ɵselectThreads, ɵselectThreadsError, ɵselectThreadsIsLoading } from "@copilotkit/core";
8
8
  import { z } from "zod";
9
9
 
@@ -204,7 +204,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
204
204
  if (isRuntimeConfigured && (status === CopilotKitCoreRuntimeConnectionStatus.Disconnected || status === CopilotKitCoreRuntimeConnectionStatus.Connecting)) {
205
205
  const cached = provisionalAgentCache.current.get(agentId);
206
206
  if (cached) {
207
- cached.headers = { ...copilotkit.headers };
207
+ copilotkit.applyHeadersToAgent(cached);
208
208
  return cached;
209
209
  }
210
210
  const provisional = new ProxiedCopilotRuntimeAgent({
@@ -213,14 +213,14 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
213
213
  transport: copilotkit.runtimeTransport,
214
214
  runtimeMode: "pending"
215
215
  });
216
- provisional.headers = { ...copilotkit.headers };
216
+ copilotkit.applyHeadersToAgent(provisional);
217
217
  provisionalAgentCache.current.set(agentId, provisional);
218
218
  return provisional;
219
219
  }
220
220
  if (isRuntimeConfigured && status === CopilotKitCoreRuntimeConnectionStatus.Error) {
221
221
  const cached = provisionalAgentCache.current.get(agentId);
222
222
  if (cached) {
223
- cached.headers = { ...copilotkit.headers };
223
+ copilotkit.applyHeadersToAgent(cached);
224
224
  return cached;
225
225
  }
226
226
  const provisional = new ProxiedCopilotRuntimeAgent({
@@ -229,7 +229,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
229
229
  transport: copilotkit.runtimeTransport,
230
230
  runtimeMode: "pending"
231
231
  });
232
- provisional.headers = { ...copilotkit.headers };
232
+ copilotkit.applyHeadersToAgent(provisional);
233
233
  provisionalAgentCache.current.set(agentId, provisional);
234
234
  return provisional;
235
235
  }
@@ -280,7 +280,7 @@ function useAgent({ agentId, updates, throttleMs } = {}) {
280
280
  updateFlags
281
281
  ]);
282
282
  useEffect(() => {
283
- if (agent instanceof HttpAgent) agent.headers = { ...copilotkit.headers };
283
+ if (agent instanceof HttpAgent) copilotkit.applyHeadersToAgent(agent);
284
284
  }, [agent, JSON.stringify(copilotkit.headers)]);
285
285
  const chatConfig = useCopilotChatConfiguration();
286
286
  const configThreadId = chatConfig?.threadId;
@@ -402,15 +402,34 @@ function useComponent(config, deps) {
402
402
  function useHumanInTheLoop(tool, deps) {
403
403
  const { copilotkit } = useCopilotKit();
404
404
  const resolvePromiseRef = useRef(null);
405
+ const cleanupAbortRef = useRef(null);
405
406
  const respond = useCallback(async (result) => {
406
407
  if (resolvePromiseRef.current) {
408
+ cleanupAbortRef.current?.();
409
+ cleanupAbortRef.current = null;
407
410
  resolvePromiseRef.current(result);
408
411
  resolvePromiseRef.current = null;
409
412
  }
410
413
  }, []);
411
- const handler = useCallback(async () => {
412
- return new Promise((resolve) => {
414
+ const handler = useCallback(async (_args, context) => {
415
+ const signal = context?.signal;
416
+ return new Promise((resolve, reject) => {
417
+ if (signal?.aborted) {
418
+ reject(/* @__PURE__ */ new Error("Human-in-the-loop interaction aborted"));
419
+ return;
420
+ }
413
421
  resolvePromiseRef.current = resolve;
422
+ if (signal) {
423
+ const onAbort = () => {
424
+ cleanupAbortRef.current = null;
425
+ resolvePromiseRef.current = null;
426
+ reject(/* @__PURE__ */ new Error("Human-in-the-loop interaction aborted"));
427
+ };
428
+ signal.addEventListener("abort", onAbort, { once: true });
429
+ cleanupAbortRef.current = () => {
430
+ signal.removeEventListener("abort", onAbort);
431
+ };
432
+ }
414
433
  });
415
434
  }, []);
416
435
  const RenderComponent = useCallback((props) => {
@@ -473,114 +492,165 @@ const INTERRUPT_EVENT_NAME = "on_interrupt";
473
492
  function isPromiseLike(value) {
474
493
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
475
494
  }
495
+ /** Derive the legacy-compatible `event` for any pending interrupt. */
496
+ function toLegacyEvent(pending) {
497
+ if (pending.kind === "legacy") return pending.event;
498
+ return {
499
+ name: INTERRUPT_EVENT_NAME,
500
+ value: pending.interrupts[0]
501
+ };
502
+ }
476
503
  /**
477
- * Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.
478
- *
479
- * The hook listens to custom events on the active agent, stores interrupt payloads per run,
480
- * and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume
481
- * execution with user-provided data.
504
+ * Handles agent interrupts with optional filtering, preprocessing, and resume behavior.
482
505
  *
483
- * - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.
484
- * - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.
485
- *
486
- * `event.value` is typed as `any` since the interrupt payload shape depends on your agent.
487
- * Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.
488
- *
489
- * @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.
490
- * @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).
491
- * @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).
492
- * Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always
493
- * either the handler's resolved return value or `null` (including when no handler is provided,
494
- * when filtering skips the interrupt, or when handler execution fails).
495
- *
496
- * @example
497
- * ```tsx
498
- * import { useInterrupt } from "@copilotkit/react-core/v2";
499
- *
500
- * function InterruptUI() {
501
- * useInterrupt({
502
- * render: ({ event, resolve }) => (
503
- * <div>
504
- * <p>{event.value.question}</p>
505
- * <button onClick={() => resolve({ approved: true })}>Approve</button>
506
- * <button onClick={() => resolve({ approved: false })}>Reject</button>
507
- * </div>
508
- * ),
509
- * });
506
+ * Supports both the AG-UI standard interrupt flow (`RUN_FINISHED` with
507
+ * `outcome.type === "interrupt"`) and the legacy custom-event flow
508
+ * (`on_interrupt`). For standard interrupts, `render` receives `interrupt`
509
+ * (the primary one) and `interrupts` (the full open set); call `resolve(payload)`
510
+ * to resume or `cancel()` to cancel. Resuming addresses the targeted interrupt
511
+ * and, once every open interrupt is addressed, submits a single spec `resume`
512
+ * array via `copilotkit.runAgent`.
510
513
  *
511
- * return null;
512
- * }
513
- * ```
514
+ * - `renderInChat: true` (default): the element is published into `<CopilotChat>`; returns `void`.
515
+ * - `renderInChat: false`: the hook returns the interrupt element for manual placement.
514
516
  *
515
517
  * @example
516
518
  * ```tsx
517
- * import { useInterrupt } from "@copilotkit/react-core/v2";
518
- *
519
- * function CustomPanel() {
520
- * const interruptElement = useInterrupt({
521
- * renderInChat: false,
522
- * enabled: (event) => event.value.startsWith("approval:"),
523
- * handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
524
- * render: ({ event, result, resolve }) => (
525
- * <aside>
526
- * <strong>{result?.label ?? ""}</strong>
527
- * <button onClick={() => resolve({ value: event.value })}>Continue</button>
528
- * </aside>
529
- * ),
530
- * });
531
- *
532
- * return <>{interruptElement}</>;
533
- * }
519
+ * useInterrupt({
520
+ * render: ({ interrupt, resolve, cancel }) => (
521
+ * <div>
522
+ * <p>{interrupt?.message}</p>
523
+ * <button onClick={() => resolve({ approved: true })}>Approve</button>
524
+ * <button onClick={() => cancel()}>Cancel</button>
525
+ * </div>
526
+ * ),
527
+ * });
534
528
  * ```
535
529
  */
536
530
  function useInterrupt(config) {
537
531
  const { copilotkit } = useCopilotKit();
538
532
  const { agent } = useAgent({ agentId: config.agentId });
539
- const [pendingEvent, setPendingEvent] = useState(null);
540
- const pendingEventRef = useRef(pendingEvent);
541
- pendingEventRef.current = pendingEvent;
533
+ const [pending, setPending] = useState(null);
534
+ const pendingRef = useRef(pending);
535
+ pendingRef.current = pending;
542
536
  const [handlerResult, setHandlerResult] = useState(null);
537
+ const responsesRef = useRef({});
543
538
  useEffect(() => {
544
- let localInterrupt = null;
539
+ let localLegacy = null;
540
+ let localStandard = null;
545
541
  const subscription = agent.subscribe({
546
542
  onCustomEvent: ({ event }) => {
547
- if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
543
+ if (event.name === INTERRUPT_EVENT_NAME) localLegacy = {
548
544
  name: event.name,
549
545
  value: event.value
550
546
  };
551
547
  },
548
+ onRunFinishedEvent: (params) => {
549
+ if (params.outcome === "interrupt") localStandard = params.interrupts;
550
+ },
552
551
  onRunStartedEvent: () => {
553
- localInterrupt = null;
554
- setPendingEvent(null);
552
+ localLegacy = null;
553
+ localStandard = null;
554
+ responsesRef.current = {};
555
+ setPending(null);
555
556
  },
556
557
  onRunFinalized: () => {
557
- if (localInterrupt) {
558
- setPendingEvent(localInterrupt);
559
- localInterrupt = null;
560
- }
558
+ if (localStandard && localStandard.length > 0) setPending({
559
+ kind: "standard",
560
+ interrupts: localStandard
561
+ });
562
+ else if (localLegacy) setPending({
563
+ kind: "legacy",
564
+ event: localLegacy
565
+ });
566
+ localLegacy = null;
567
+ localStandard = null;
561
568
  },
562
569
  onRunFailed: () => {
563
- localInterrupt = null;
564
- setPendingEvent(null);
570
+ localLegacy = null;
571
+ localStandard = null;
572
+ responsesRef.current = {};
573
+ setPending(null);
565
574
  }
566
575
  });
567
576
  return () => subscription.unsubscribe();
568
577
  }, [agent]);
569
- const resolve = useCallback(async (response) => {
578
+ const submitStandardIfComplete = useCallback(async (interrupts) => {
579
+ if (!interrupts.every((i) => responsesRef.current[i.id])) return;
580
+ const expired = interrupts.find((i) => isInterruptExpired(i));
581
+ if (expired) {
582
+ console.error(`[CopilotKit] useInterrupt: interrupt ${expired.id} expired at ${expired.expiresAt}; not resuming.`);
583
+ responsesRef.current = {};
584
+ setPending(null);
585
+ return;
586
+ }
587
+ const resume = buildResumeArray(interrupts, responsesRef.current);
588
+ for (const i of interrupts) {
589
+ if (!i.toolCallId) continue;
590
+ const response = responsesRef.current[i.id];
591
+ const content = response.status === "cancelled" ? { status: "cancelled" } : response.payload ?? { status: "resolved" };
592
+ agent.addMessage({
593
+ id: randomUUID$1(),
594
+ role: "tool",
595
+ toolCallId: i.toolCallId,
596
+ content: JSON.stringify(content)
597
+ });
598
+ }
599
+ responsesRef.current = {};
570
600
  try {
601
+ return await copilotkit.runAgent({
602
+ agent,
603
+ resume
604
+ });
605
+ } catch (err) {
606
+ console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
607
+ setPending(null);
608
+ throw err;
609
+ }
610
+ }, [agent, copilotkit]);
611
+ const resolve = useCallback(async (payload, interruptId) => {
612
+ const current = pendingRef.current;
613
+ if (!current) return;
614
+ if (current.kind === "legacy") try {
571
615
  return await copilotkit.runAgent({
572
616
  agent,
573
617
  forwardedProps: { command: {
574
- resume: response,
575
- interruptEvent: pendingEventRef.current?.value
618
+ resume: payload,
619
+ interruptEvent: current.event.value
576
620
  } }
577
621
  });
578
622
  } catch (err) {
579
623
  console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
580
- setPendingEvent(null);
624
+ setPending(null);
581
625
  throw err;
582
626
  }
583
- }, [agent, copilotkit]);
627
+ 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.`);
628
+ const id = interruptId ?? current.interrupts[0]?.id;
629
+ if (!id) return;
630
+ responsesRef.current[id] = {
631
+ status: "resolved",
632
+ payload
633
+ };
634
+ return submitStandardIfComplete(current.interrupts);
635
+ }, [
636
+ agent,
637
+ copilotkit,
638
+ submitStandardIfComplete
639
+ ]);
640
+ const cancel = useCallback(async (interruptId) => {
641
+ const current = pendingRef.current;
642
+ if (!current) return;
643
+ if (current.kind === "legacy") {
644
+ console.warn("[CopilotKit] useInterrupt: cancel() is not supported for legacy on_interrupt interrupts; dismissing.");
645
+ setPending(null);
646
+ return;
647
+ }
648
+ 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.`);
649
+ const id = interruptId ?? current.interrupts[0]?.id;
650
+ if (!id) return;
651
+ responsesRef.current[id] = { status: "cancelled" };
652
+ return submitStandardIfComplete(current.interrupts);
653
+ }, [submitStandardIfComplete]);
584
654
  const renderRef = useRef(config.render);
585
655
  renderRef.current = config.render;
586
656
  const enabledRef = useRef(config.enabled);
@@ -589,6 +659,8 @@ function useInterrupt(config) {
589
659
  handlerRef.current = config.handler;
590
660
  const resolveRef = useRef(resolve);
591
661
  resolveRef.current = resolve;
662
+ const cancelRef = useRef(cancel);
663
+ cancelRef.current = cancel;
592
664
  const isEnabled = (event) => {
593
665
  const predicate = enabledRef.current;
594
666
  if (!predicate) return true;
@@ -600,11 +672,12 @@ function useInterrupt(config) {
600
672
  }
601
673
  };
602
674
  useEffect(() => {
603
- if (!pendingEvent) {
675
+ if (!pending) {
604
676
  setHandlerResult(null);
605
677
  return;
606
678
  }
607
- if (!isEnabled(pendingEvent)) {
679
+ const legacyEvent = toLegacyEvent(pending);
680
+ if (!isEnabled(legacyEvent)) {
608
681
  setHandlerResult(null);
609
682
  return;
610
683
  }
@@ -617,8 +690,11 @@ function useInterrupt(config) {
617
690
  let maybePromise;
618
691
  try {
619
692
  maybePromise = handler({
620
- event: pendingEvent,
621
- resolve: resolveRef.current
693
+ event: legacyEvent,
694
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
695
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
696
+ resolve: resolveRef.current,
697
+ cancel: cancelRef.current
622
698
  });
623
699
  } catch (err) {
624
700
  console.error("[CopilotKit] useInterrupt handler threw; result will be null:", err);
@@ -637,19 +713,24 @@ function useInterrupt(config) {
637
713
  return () => {
638
714
  cancelled = true;
639
715
  };
640
- }, [pendingEvent]);
716
+ }, [pending]);
641
717
  const element = useMemo(() => {
642
- if (!pendingEvent) return null;
643
- if (!isEnabled(pendingEvent)) return null;
718
+ if (!pending) return null;
719
+ const legacyEvent = toLegacyEvent(pending);
720
+ if (!isEnabled(legacyEvent)) return null;
644
721
  return renderRef.current({
645
- event: pendingEvent,
722
+ event: legacyEvent,
723
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
724
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
646
725
  result: handlerResult,
647
- resolve
726
+ resolve,
727
+ cancel
648
728
  });
649
729
  }, [
650
- pendingEvent,
730
+ pending,
651
731
  handlerResult,
652
- resolve
732
+ resolve,
733
+ cancel
653
734
  ]);
654
735
  useEffect(() => {
655
736
  if (config.renderInChat === false) return;
@@ -922,9 +1003,9 @@ function useThreadStoreSelector(store, selector) {
922
1003
  * current without polling — thread creates, renames, archives, and deletes
923
1004
  * from any client are reflected immediately.
924
1005
  *
925
- * Mutation methods (`renameThread`, `archiveThread`, `deleteThread`) return
926
- * promises that resolve once the platform confirms the operation and reject
927
- * with an `Error` on failure.
1006
+ * Mutation methods (`renameThread`, `archiveThread`, `unarchiveThread`,
1007
+ * `deleteThread`) return promises that resolve once the platform confirms the
1008
+ * operation and reject with an `Error` on failure.
928
1009
  *
929
1010
  * @param input - Agent identifier and optional list controls.
930
1011
  * @returns Thread list state and stable mutation callbacks.
@@ -974,21 +1055,32 @@ function useThreads({ agentId, includeArchived, limit }) {
974
1055
  const headersKey = useMemo(() => {
975
1056
  return JSON.stringify(Object.entries(copilotkit.headers ?? {}).sort(([left], [right]) => left.localeCompare(right)));
976
1057
  }, [copilotkit.headers]);
1058
+ const runtimeStatus = copilotkit.runtimeConnectionStatus;
1059
+ const threadListEndpointSupported = copilotkit.threadEndpoints?.list !== false;
1060
+ const threadMutationsSupported = copilotkit.threadEndpoints?.mutations !== false;
1061
+ const threadEndpointsUnavailable = !!copilotkit.runtimeUrl && runtimeStatus === CopilotKitCoreRuntimeConnectionStatus.Connected && !threadListEndpointSupported;
977
1062
  const runtimeError = useMemo(() => {
978
1063
  if (copilotkit.runtimeUrl) return null;
979
1064
  return /* @__PURE__ */ new Error("Runtime URL is not configured");
980
1065
  }, [copilotkit.runtimeUrl]);
1066
+ const threadEndpointsError = useMemo(() => {
1067
+ if (!threadEndpointsUnavailable) return null;
1068
+ return /* @__PURE__ */ new Error("Thread endpoints are not available on this CopilotKit runtime");
1069
+ }, [threadEndpointsUnavailable]);
1070
+ const threadMutationsError = useMemo(() => {
1071
+ if (threadMutationsSupported) return null;
1072
+ return /* @__PURE__ */ new Error("Thread mutations are not available on this CopilotKit runtime");
1073
+ }, [threadMutationsSupported]);
981
1074
  const [hasDispatchedContext, setHasDispatchedContext] = useState(false);
982
- const preConnectLoading = !!copilotkit.runtimeUrl && !hasDispatchedContext;
983
- const isLoading = runtimeError ? false : preConnectLoading || storeIsLoading;
984
- const error = runtimeError ?? storeError;
1075
+ const preConnectLoading = !!copilotkit.runtimeUrl && !threadEndpointsUnavailable && !hasDispatchedContext;
1076
+ const isLoading = runtimeError || threadEndpointsError ? false : preConnectLoading || storeIsLoading;
1077
+ const error = runtimeError ?? threadEndpointsError ?? storeError;
985
1078
  useEffect(() => {
986
1079
  store.start();
987
1080
  return () => {
988
1081
  store.stop();
989
1082
  };
990
1083
  }, [store]);
991
- const runtimeStatus = copilotkit.runtimeConnectionStatus;
992
1084
  useEffect(() => {
993
1085
  copilotkit.registerThreadStore(agentId, store);
994
1086
  return () => {
@@ -1002,9 +1094,15 @@ function useThreads({ agentId, includeArchived, limit }) {
1002
1094
  useEffect(() => {
1003
1095
  if (!copilotkit.runtimeUrl) {
1004
1096
  store.setContext(null);
1097
+ setHasDispatchedContext(false);
1005
1098
  return;
1006
1099
  }
1007
1100
  if (runtimeStatus !== CopilotKitCoreRuntimeConnectionStatus.Connected) return;
1101
+ if (!threadListEndpointSupported) {
1102
+ store.setContext(null);
1103
+ setHasDispatchedContext(false);
1104
+ return;
1105
+ }
1008
1106
  const context = {
1009
1107
  runtimeUrl: copilotkit.runtimeUrl,
1010
1108
  headers: { ...copilotkit.headers },
@@ -1021,13 +1119,21 @@ function useThreads({ agentId, includeArchived, limit }) {
1021
1119
  runtimeStatus,
1022
1120
  headersKey,
1023
1121
  copilotkit.intelligence?.wsUrl,
1122
+ threadListEndpointSupported,
1024
1123
  agentId,
1025
1124
  includeArchived,
1026
1125
  limit
1027
1126
  ]);
1028
- const renameThread = useCallback((threadId, name) => store.renameThread(threadId, name), [store]);
1029
- const archiveThread = useCallback((threadId) => store.archiveThread(threadId), [store]);
1030
- const deleteThread = useCallback((threadId) => store.deleteThread(threadId), [store]);
1127
+ const guardMutation = useCallback((mutation) => {
1128
+ return (...args) => {
1129
+ if (threadMutationsError) return Promise.reject(threadMutationsError);
1130
+ return mutation(...args);
1131
+ };
1132
+ }, [threadMutationsError]);
1133
+ const renameThread = useMemo(() => guardMutation((threadId, name) => store.renameThread(threadId, name)), [store, guardMutation]);
1134
+ const archiveThread = useMemo(() => guardMutation((threadId) => store.archiveThread(threadId)), [store, guardMutation]);
1135
+ const unarchiveThread = useMemo(() => guardMutation((threadId) => store.unarchiveThread(threadId)), [store, guardMutation]);
1136
+ const deleteThread = useMemo(() => guardMutation((threadId) => store.deleteThread(threadId)), [store, guardMutation]);
1031
1137
  return {
1032
1138
  threads,
1033
1139
  isLoading,
@@ -1037,6 +1143,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1037
1143
  fetchMoreThreads: useCallback(() => store.fetchNextPage(), [store]),
1038
1144
  renameThread,
1039
1145
  archiveThread,
1146
+ unarchiveThread,
1040
1147
  deleteThread
1041
1148
  };
1042
1149
  }
@@ -1111,10 +1218,20 @@ function useRenderTool(config, deps) {
1111
1218
  }) : defineToolCallRenderer({
1112
1219
  name: config.name,
1113
1220
  args: config.parameters,
1114
- render: (props) => config.render({
1115
- ...props,
1116
- parameters: props.args
1117
- }),
1221
+ render: (props) => {
1222
+ if (props.status === ToolCallStatus.InProgress) return config.render({
1223
+ ...props,
1224
+ parameters: props.args
1225
+ });
1226
+ if (props.status === ToolCallStatus.Executing) return config.render({
1227
+ ...props,
1228
+ parameters: props.args
1229
+ });
1230
+ return config.render({
1231
+ ...props,
1232
+ parameters: props.args
1233
+ });
1234
+ },
1118
1235
  ...config.agentId ? { agentId: config.agentId } : {}
1119
1236
  });
1120
1237
  copilotkit.addHookRenderToolCall(renderer);