@copilotkit/react-core 1.61.1 → 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 (41) hide show
  1. package/dist/{copilotkit-UY-H6Kx7.mjs → copilotkit-Be-o2UaU.mjs} +210 -119
  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-BCJDP8qd.cjs → copilotkit-CTCjVxkH.cjs} +199 -108
  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 +167 -94
  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 +167 -93
  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 +168 -94
  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 +198 -107
  36. package/dist/v2/index.umd.js.map +1 -1
  37. package/package.json +6 -6
  38. package/dist/copilotkit-BCJDP8qd.cjs.map +0 -1
  39. package/dist/copilotkit-CEdu_aie.d.cts.map +0 -1
  40. package/dist/copilotkit-M1FiciGd.d.mts.map +0 -1
  41. package/dist/copilotkit-UY-H6Kx7.mjs.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;
@@ -492,114 +492,165 @@ const INTERRUPT_EVENT_NAME = "on_interrupt";
492
492
  function isPromiseLike(value) {
493
493
  return (typeof value === "object" || typeof value === "function") && value !== null && typeof Reflect.get(value, "then") === "function";
494
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
+ }
495
503
  /**
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.
504
+ * Handles agent interrupts with optional filtering, preprocessing, and resume behavior.
501
505
  *
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.
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`.
504
513
  *
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.
507
- *
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).
514
- *
515
- * @example
516
- * ```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
- * ```
514
+ * - `renderInChat: true` (default): the element is published into `<CopilotChat>`; returns `void`.
515
+ * - `renderInChat: false`: the hook returns the interrupt element for manual placement.
533
516
  *
534
517
  * @example
535
518
  * ```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
- * }
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
+ * });
553
528
  * ```
554
529
  */
555
530
  function useInterrupt(config) {
556
531
  const { copilotkit } = useCopilotKit();
557
532
  const { agent } = useAgent({ agentId: config.agentId });
558
- const [pendingEvent, setPendingEvent] = useState(null);
559
- const pendingEventRef = useRef(pendingEvent);
560
- pendingEventRef.current = pendingEvent;
533
+ const [pending, setPending] = useState(null);
534
+ const pendingRef = useRef(pending);
535
+ pendingRef.current = pending;
561
536
  const [handlerResult, setHandlerResult] = useState(null);
537
+ const responsesRef = useRef({});
562
538
  useEffect(() => {
563
- let localInterrupt = null;
539
+ let localLegacy = null;
540
+ let localStandard = null;
564
541
  const subscription = agent.subscribe({
565
542
  onCustomEvent: ({ event }) => {
566
- if (event.name === INTERRUPT_EVENT_NAME) localInterrupt = {
543
+ if (event.name === INTERRUPT_EVENT_NAME) localLegacy = {
567
544
  name: event.name,
568
545
  value: event.value
569
546
  };
570
547
  },
548
+ onRunFinishedEvent: (params) => {
549
+ if (params.outcome === "interrupt") localStandard = params.interrupts;
550
+ },
571
551
  onRunStartedEvent: () => {
572
- localInterrupt = null;
573
- setPendingEvent(null);
552
+ localLegacy = null;
553
+ localStandard = null;
554
+ responsesRef.current = {};
555
+ setPending(null);
574
556
  },
575
557
  onRunFinalized: () => {
576
- if (localInterrupt) {
577
- setPendingEvent(localInterrupt);
578
- localInterrupt = null;
579
- }
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;
580
568
  },
581
569
  onRunFailed: () => {
582
- localInterrupt = null;
583
- setPendingEvent(null);
570
+ localLegacy = null;
571
+ localStandard = null;
572
+ responsesRef.current = {};
573
+ setPending(null);
584
574
  }
585
575
  });
586
576
  return () => subscription.unsubscribe();
587
577
  }, [agent]);
588
- 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 = {};
589
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 {
590
615
  return await copilotkit.runAgent({
591
616
  agent,
592
617
  forwardedProps: { command: {
593
- resume: response,
594
- interruptEvent: pendingEventRef.current?.value
618
+ resume: payload,
619
+ interruptEvent: current.event.value
595
620
  } }
596
621
  });
597
622
  } catch (err) {
598
623
  console.error("[CopilotKit] useInterrupt resolve: runAgent rejected; clearing pending + rethrowing", err);
599
- setPendingEvent(null);
624
+ setPending(null);
600
625
  throw err;
601
626
  }
602
- }, [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]);
603
654
  const renderRef = useRef(config.render);
604
655
  renderRef.current = config.render;
605
656
  const enabledRef = useRef(config.enabled);
@@ -608,6 +659,8 @@ function useInterrupt(config) {
608
659
  handlerRef.current = config.handler;
609
660
  const resolveRef = useRef(resolve);
610
661
  resolveRef.current = resolve;
662
+ const cancelRef = useRef(cancel);
663
+ cancelRef.current = cancel;
611
664
  const isEnabled = (event) => {
612
665
  const predicate = enabledRef.current;
613
666
  if (!predicate) return true;
@@ -619,11 +672,12 @@ function useInterrupt(config) {
619
672
  }
620
673
  };
621
674
  useEffect(() => {
622
- if (!pendingEvent) {
675
+ if (!pending) {
623
676
  setHandlerResult(null);
624
677
  return;
625
678
  }
626
- if (!isEnabled(pendingEvent)) {
679
+ const legacyEvent = toLegacyEvent(pending);
680
+ if (!isEnabled(legacyEvent)) {
627
681
  setHandlerResult(null);
628
682
  return;
629
683
  }
@@ -636,8 +690,11 @@ function useInterrupt(config) {
636
690
  let maybePromise;
637
691
  try {
638
692
  maybePromise = handler({
639
- event: pendingEvent,
640
- 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
641
698
  });
642
699
  } catch (err) {
643
700
  console.error("[CopilotKit] useInterrupt handler threw; result will be null:", err);
@@ -656,19 +713,24 @@ function useInterrupt(config) {
656
713
  return () => {
657
714
  cancelled = true;
658
715
  };
659
- }, [pendingEvent]);
716
+ }, [pending]);
660
717
  const element = useMemo(() => {
661
- if (!pendingEvent) return null;
662
- if (!isEnabled(pendingEvent)) return null;
718
+ if (!pending) return null;
719
+ const legacyEvent = toLegacyEvent(pending);
720
+ if (!isEnabled(legacyEvent)) return null;
663
721
  return renderRef.current({
664
- event: pendingEvent,
722
+ event: legacyEvent,
723
+ interrupt: pending.kind === "standard" ? pending.interrupts[0] : null,
724
+ interrupts: pending.kind === "standard" ? pending.interrupts : [],
665
725
  result: handlerResult,
666
- resolve
726
+ resolve,
727
+ cancel
667
728
  });
668
729
  }, [
669
- pendingEvent,
730
+ pending,
670
731
  handlerResult,
671
- resolve
732
+ resolve,
733
+ cancel
672
734
  ]);
673
735
  useEffect(() => {
674
736
  if (config.renderInChat === false) return;
@@ -941,9 +1003,9 @@ function useThreadStoreSelector(store, selector) {
941
1003
  * current without polling — thread creates, renames, archives, and deletes
942
1004
  * from any client are reflected immediately.
943
1005
  *
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.
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.
947
1009
  *
948
1010
  * @param input - Agent identifier and optional list controls.
949
1011
  * @returns Thread list state and stable mutation callbacks.
@@ -1070,6 +1132,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1070
1132
  }, [threadMutationsError]);
1071
1133
  const renameThread = useMemo(() => guardMutation((threadId, name) => store.renameThread(threadId, name)), [store, guardMutation]);
1072
1134
  const archiveThread = useMemo(() => guardMutation((threadId) => store.archiveThread(threadId)), [store, guardMutation]);
1135
+ const unarchiveThread = useMemo(() => guardMutation((threadId) => store.unarchiveThread(threadId)), [store, guardMutation]);
1073
1136
  const deleteThread = useMemo(() => guardMutation((threadId) => store.deleteThread(threadId)), [store, guardMutation]);
1074
1137
  return {
1075
1138
  threads,
@@ -1080,6 +1143,7 @@ function useThreads({ agentId, includeArchived, limit }) {
1080
1143
  fetchMoreThreads: useCallback(() => store.fetchNextPage(), [store]),
1081
1144
  renameThread,
1082
1145
  archiveThread,
1146
+ unarchiveThread,
1083
1147
  deleteThread
1084
1148
  };
1085
1149
  }
@@ -1154,10 +1218,20 @@ function useRenderTool(config, deps) {
1154
1218
  }) : defineToolCallRenderer({
1155
1219
  name: config.name,
1156
1220
  args: config.parameters,
1157
- render: (props) => config.render({
1158
- ...props,
1159
- parameters: props.args
1160
- }),
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
+ },
1161
1235
  ...config.agentId ? { agentId: config.agentId } : {}
1162
1236
  });
1163
1237
  copilotkit.addHookRenderToolCall(renderer);