{"version":3,"file":"use-interrupt.cjs","names":["useCopilotKit","useAgent"],"sources":["../../src/hooks/use-interrupt.tsx"],"sourcesContent":["import React, {\n  useState,\n  useEffect,\n  useCallback,\n  useMemo,\n  useRef,\n} from \"react\";\nimport { useCopilotKit } from \"@/providers/CopilotKitProvider\";\nimport { useAgent } from \"./use-agent\";\nimport type {\n  InterruptEvent,\n  InterruptRenderProps,\n  InterruptHandlerProps,\n} from \"../types/interrupt\";\n\nexport type { InterruptEvent, InterruptRenderProps, InterruptHandlerProps };\n\nconst INTERRUPT_EVENT_NAME = \"on_interrupt\";\n\ntype InterruptHandlerFn<TValue, TResult> = (\n  props: InterruptHandlerProps<TValue>,\n) => TResult | PromiseLike<TResult>;\n\ntype InterruptResultFromHandler<THandler> = THandler extends (\n  ...args: never[]\n) => infer TResult\n  ? TResult extends PromiseLike<infer TResolved>\n    ? TResolved | null\n    : TResult | null\n  : null;\n\ntype InterruptResult<TValue, TResult> = InterruptResultFromHandler<\n  InterruptHandlerFn<TValue, TResult>\n>;\n\ntype InterruptRenderInChat = boolean | undefined;\n\ntype UseInterruptReturn<TRenderInChat extends InterruptRenderInChat> =\n  TRenderInChat extends false\n    ? React.ReactElement | null\n    : TRenderInChat extends true | undefined\n      ? void\n      : React.ReactElement | null | void;\n\nexport function isPromiseLike<TValue>(\n  value: TValue | PromiseLike<TValue>,\n): value is PromiseLike<TValue> {\n  return (\n    (typeof value === \"object\" || typeof value === \"function\") &&\n    value !== null &&\n    typeof Reflect.get(value, \"then\") === \"function\"\n  );\n}\n\n/**\n * Configuration options for `useInterrupt`.\n */\ninterface UseInterruptConfigBase<TValue = unknown, TResult = never> {\n  /**\n   * Render function for the interrupt UI.\n   *\n   * This is called once an interrupt is finalized and accepted by `enabled` (if provided).\n   * Use `resolve` from render props to resume the agent run with user input.\n   */\n  render: (\n    props: InterruptRenderProps<TValue, InterruptResult<TValue, TResult>>,\n  ) => React.ReactElement;\n  /**\n   * Optional pre-render handler invoked when an interrupt is received.\n   *\n   * Return either a sync value or an async value to pass into `render` as `result`.\n   * Rejecting/throwing falls back to `result = null`.\n   */\n  handler?: InterruptHandlerFn<TValue, TResult>;\n  /**\n   * Optional predicate to filter which interrupts should be handled by this hook.\n   * Return `false` to ignore an interrupt.\n   */\n  enabled?: (event: InterruptEvent<TValue>) => boolean;\n  /** Optional agent id. Defaults to the current configured chat agent. */\n  agentId?: string;\n}\n\nexport interface UseInterruptInChatConfig<\n  TValue = unknown,\n  TResult = never,\n> extends UseInterruptConfigBase<TValue, TResult> {\n  /** When true (default), the interrupt UI renders inside `<CopilotChat>` automatically. Set to false to render it yourself. */\n  renderInChat?: true;\n}\n\nexport interface UseInterruptExternalConfig<\n  TValue = unknown,\n  TResult = never,\n> extends UseInterruptConfigBase<TValue, TResult> {\n  /** When true (default), the interrupt UI renders inside `<CopilotChat>` automatically. Set to false to render it yourself. */\n  renderInChat: false;\n}\n\nexport interface UseInterruptDynamicConfig<\n  TValue = unknown,\n  TResult = never,\n> extends UseInterruptConfigBase<TValue, TResult> {\n  /** Dynamic boolean mode. When non-literal, return type is a union. */\n  renderInChat: boolean;\n}\n\nexport type UseInterruptConfig<\n  TValue = unknown,\n  TResult = never,\n  TRenderInChat extends InterruptRenderInChat = undefined,\n> = UseInterruptConfigBase<TValue, TResult> & {\n  /** When true (default), the interrupt UI renders inside `<CopilotChat>` automatically. Set to false to render it yourself. */\n  renderInChat?: TRenderInChat;\n};\n\n/**\n * Handles agent interrupts (`on_interrupt`) with optional filtering, preprocessing, and resume behavior.\n *\n * The hook listens to custom events on the active agent, stores interrupt payloads per run,\n * and surfaces a render callback once the run finalizes. Call `resolve` from your UI to resume\n * execution with user-provided data.\n *\n * - `renderInChat: true` (default): the element is published into `<CopilotChat>` and this hook returns `void`.\n * - `renderInChat: false`: the hook returns the interrupt element so you can place it anywhere in your component tree.\n *\n * `event.value` is typed as `any` since the interrupt payload shape depends on your agent.\n * Type-narrow it in your callbacks (e.g. `handler`, `enabled`, `render`) as needed.\n *\n * @typeParam TResult - Inferred from `handler` return type. Exposed as `result` in `render`.\n * @param config - Interrupt configuration (renderer, optional handler/filter, and render mode).\n * @returns When `renderInChat` is `false`, returns the interrupt element (or `null` when idle).\n * Otherwise returns `void` and publishes the element into chat. In `render`, `result` is always\n * either the handler's resolved return value or `null` (including when no handler is provided,\n * when filtering skips the interrupt, or when handler execution fails).\n *\n * @example\n * ```tsx\n * import { useInterrupt } from \"@copilotkitnext/react\";\n *\n * function InterruptUI() {\n *   useInterrupt({\n *     render: ({ event, resolve }) => (\n *       <div>\n *         <p>{event.value.question}</p>\n *         <button onClick={() => resolve({ approved: true })}>Approve</button>\n *         <button onClick={() => resolve({ approved: false })}>Reject</button>\n *       </div>\n *     ),\n *   });\n *\n *   return null;\n * }\n * ```\n *\n * @example\n * ```tsx\n * import { useInterrupt } from \"@copilotkitnext/react\";\n *\n * function CustomPanel() {\n *   const interruptElement = useInterrupt({\n *     renderInChat: false,\n *     enabled: (event) => event.value.startsWith(\"approval:\"),\n *     handler: async ({ event }) => ({ label: event.value.toUpperCase() }),\n *     render: ({ event, result, resolve }) => (\n *       <aside>\n *         <strong>{result?.label ?? \"\"}</strong>\n *         <button onClick={() => resolve({ value: event.value })}>Continue</button>\n *       </aside>\n *     ),\n *   });\n *\n *   return <>{interruptElement}</>;\n * }\n * ```\n */\n/* eslint-disable @typescript-eslint/no-explicit-any */\nexport function useInterrupt<\n  TResult = never,\n  TRenderInChat extends InterruptRenderInChat = undefined,\n>(\n  config: UseInterruptConfig<any, TResult, TRenderInChat>,\n): UseInterruptReturn<TRenderInChat> {\n  /* eslint-enable @typescript-eslint/no-explicit-any */\n  const { copilotkit } = useCopilotKit();\n  const { agent } = useAgent({ agentId: config.agentId });\n  const [pendingEvent, setPendingEvent] = useState<InterruptEvent | null>(null);\n  const pendingEventRef = useRef(pendingEvent);\n  pendingEventRef.current = pendingEvent;\n  const [handlerResult, setHandlerResult] =\n    // eslint-disable-next-line @typescript-eslint/no-explicit-any\n    useState<InterruptResult<any, TResult>>(null);\n\n  useEffect(() => {\n    let localInterrupt: InterruptEvent | null = null;\n\n    const subscription = agent.subscribe({\n      onCustomEvent: ({ event }) => {\n        if (event.name === INTERRUPT_EVENT_NAME) {\n          localInterrupt = { name: event.name, value: event.value };\n        }\n      },\n      onRunStartedEvent: () => {\n        localInterrupt = null;\n        setPendingEvent(null);\n      },\n      onRunFinalized: () => {\n        if (localInterrupt) {\n          setPendingEvent(localInterrupt);\n          localInterrupt = null;\n        }\n      },\n      onRunFailed: () => {\n        localInterrupt = null;\n      },\n    });\n\n    return () => subscription.unsubscribe();\n  }, [agent]);\n\n  const resolve = useCallback(\n    (response: unknown) => {\n      setPendingEvent(null);\n      copilotkit.runAgent({\n        agent,\n        forwardedProps: {\n          command: {\n            resume: response,\n            interruptEvent: pendingEventRef.current?.value,\n          },\n        },\n      });\n    },\n    [agent, copilotkit],\n  );\n\n  useEffect(() => {\n    // No interrupt to process — reset any stale handler result from a previous interrupt\n    if (!pendingEvent) {\n      setHandlerResult(null);\n      return;\n    }\n    // Interrupt exists but the consumer's filter rejects it — treat as no-op\n    if (config.enabled && !config.enabled(pendingEvent)) {\n      setHandlerResult(null);\n      return;\n    }\n    const handler = config.handler;\n    // No handler provided — skip straight to rendering with a null result\n    if (!handler) {\n      setHandlerResult(null);\n      return;\n    }\n\n    let cancelled = false;\n    const maybePromise = handler({\n      event: pendingEvent,\n      resolve,\n    });\n\n    // If the handler returns a promise/thenable, wait for resolution before setting result.\n    if (isPromiseLike(maybePromise)) {\n      Promise.resolve(maybePromise)\n        .then((resolved) => {\n          if (!cancelled) setHandlerResult(resolved);\n        })\n        .catch(() => {\n          if (!cancelled) setHandlerResult(null);\n        });\n    } else {\n      setHandlerResult(maybePromise);\n    }\n\n    return () => {\n      cancelled = true;\n    };\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [pendingEvent, config.enabled, config.handler, resolve]);\n\n  const element = useMemo(() => {\n    if (!pendingEvent) return null;\n    if (config.enabled && !config.enabled(pendingEvent)) return null;\n\n    return config.render({\n      event: pendingEvent,\n      result: handlerResult,\n      resolve,\n    });\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n  }, [pendingEvent, handlerResult, config.enabled, config.render, resolve]);\n\n  // Publish to core for in-chat rendering\n  useEffect(() => {\n    if (config.renderInChat === false) return;\n    copilotkit.setInterruptElement(element);\n    return () => copilotkit.setInterruptElement(null);\n  }, [element, config.renderInChat, copilotkit]);\n\n  // Only return element when rendering outside chat\n  if (config.renderInChat === false) {\n    return element as UseInterruptReturn<TRenderInChat>;\n  }\n\n  return undefined as UseInterruptReturn<TRenderInChat>;\n}\n"],"mappings":";;;;;;AAiBA,MAAM,uBAAuB;AA2B7B,SAAgB,cACd,OAC8B;AAC9B,SACG,OAAO,UAAU,YAAY,OAAO,UAAU,eAC/C,UAAU,QACV,OAAO,QAAQ,IAAI,OAAO,OAAO,KAAK;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+H1C,SAAgB,aAId,QACmC;CAEnC,MAAM,EAAE,eAAeA,0CAAe;CACtC,MAAM,EAAE,UAAUC,2BAAS,EAAE,SAAS,OAAO,SAAS,CAAC;CACvD,MAAM,CAAC,cAAc,uCAAmD,KAAK;CAC7E,MAAM,oCAAyB,aAAa;AAC5C,iBAAgB,UAAU;CAC1B,MAAM,CAAC,eAAe,wCAEoB,KAAK;AAE/C,4BAAgB;EACd,IAAI,iBAAwC;EAE5C,MAAM,eAAe,MAAM,UAAU;GACnC,gBAAgB,EAAE,YAAY;AAC5B,QAAI,MAAM,SAAS,qBACjB,kBAAiB;KAAE,MAAM,MAAM;KAAM,OAAO,MAAM;KAAO;;GAG7D,yBAAyB;AACvB,qBAAiB;AACjB,oBAAgB,KAAK;;GAEvB,sBAAsB;AACpB,QAAI,gBAAgB;AAClB,qBAAgB,eAAe;AAC/B,sBAAiB;;;GAGrB,mBAAmB;AACjB,qBAAiB;;GAEpB,CAAC;AAEF,eAAa,aAAa,aAAa;IACtC,CAAC,MAAM,CAAC;CAEX,MAAM,kCACH,aAAsB;AACrB,kBAAgB,KAAK;AACrB,aAAW,SAAS;GAClB;GACA,gBAAgB,EACd,SAAS;IACP,QAAQ;IACR,gBAAgB,gBAAgB,SAAS;IAC1C,EACF;GACF,CAAC;IAEJ,CAAC,OAAO,WAAW,CACpB;AAED,4BAAgB;AAEd,MAAI,CAAC,cAAc;AACjB,oBAAiB,KAAK;AACtB;;AAGF,MAAI,OAAO,WAAW,CAAC,OAAO,QAAQ,aAAa,EAAE;AACnD,oBAAiB,KAAK;AACtB;;EAEF,MAAM,UAAU,OAAO;AAEvB,MAAI,CAAC,SAAS;AACZ,oBAAiB,KAAK;AACtB;;EAGF,IAAI,YAAY;EAChB,MAAM,eAAe,QAAQ;GAC3B,OAAO;GACP;GACD,CAAC;AAGF,MAAI,cAAc,aAAa,CAC7B,SAAQ,QAAQ,aAAa,CAC1B,MAAM,aAAa;AAClB,OAAI,CAAC,UAAW,kBAAiB,SAAS;IAC1C,CACD,YAAY;AACX,OAAI,CAAC,UAAW,kBAAiB,KAAK;IACtC;MAEJ,kBAAiB,aAAa;AAGhC,eAAa;AACX,eAAY;;IAGb;EAAC;EAAc,OAAO;EAAS,OAAO;EAAS;EAAQ,CAAC;CAE3D,MAAM,mCAAwB;AAC5B,MAAI,CAAC,aAAc,QAAO;AAC1B,MAAI,OAAO,WAAW,CAAC,OAAO,QAAQ,aAAa,CAAE,QAAO;AAE5D,SAAO,OAAO,OAAO;GACnB,OAAO;GACP,QAAQ;GACR;GACD,CAAC;IAED;EAAC;EAAc;EAAe,OAAO;EAAS,OAAO;EAAQ;EAAQ,CAAC;AAGzE,4BAAgB;AACd,MAAI,OAAO,iBAAiB,MAAO;AACnC,aAAW,oBAAoB,QAAQ;AACvC,eAAa,WAAW,oBAAoB,KAAK;IAChD;EAAC;EAAS,OAAO;EAAc;EAAW,CAAC;AAG9C,KAAI,OAAO,iBAAiB,MAC1B,QAAO"}