Reference / Hooks

useInterrupt

React hook for handling agent interrupt events and resuming execution with user input

Overview

useInterrupt listens for agent custom events named on_interrupt, captures the latest interrupt payload for a run, and renders interrupt UI once the run finalizes. Your UI can call resolve(response) to resume the agent with a resume payload.

By default, interrupt UI is rendered inside <CopilotChat> automatically. If you set renderInChat: false, the hook returns the element so you can place it manually.

event.value is typed as any since the interrupt payload shape depends on your agent. Type-narrow it in your callbacks (e.g. handler, enabled, render) as needed.

Signature

function useInterrupt<
  TResult = never,
  TRenderInChat extends boolean | undefined = undefined,
>(
  config: UseInterruptConfig<any, TResult, TRenderInChat>,
): TRenderInChat extends false
  ? React.ReactElement | null
  : TRenderInChat extends true | undefined
    ? void
    : React.ReactElement | null | void;

Parameters

configUseInterruptConfig<any, TResult, TRenderInChat>required

Interrupt configuration.

render(props: InterruptRenderProps<any, TResult | null>) => React.ReactElementrequired

Render callback for interrupt UI. Called when an interrupt is available. The callback receives:

  • event -- interrupt event ({ name, value }). value is any; type-narrow in your callback as needed.
  • result -- inferred from handler return type, or null
  • resolve(response) -- resumes the agent with command.resume = response
handler(props: InterruptHandlerProps) => TResult | PromiseLike<TResult>

Optional preprocessing callback. Runs before rendering and can return sync or async data that is exposed as result in render. TResult is automatically inferred from the handler's return type. If the handler throws/rejects, result is null.

enabled(event: InterruptEvent) => boolean

Optional filter. Return false to ignore matching interrupts for this hook instance.

agentIdstring

Optional agent id. Defaults to the currently configured chat agent.

renderInChatboolean
Default: "true"

Controls where UI renders:

  • true (default): publishes interrupt UI into <CopilotChat>
  • false: returns interrupt element from the hook for manual placement

Return Value

elementConditional by renderInChat

Return type is inferred from renderInChat:

  • renderInChat: false -> React.ReactElement | null
  • renderInChat: true or omitted -> void
  • dynamic boolean -> React.ReactElement | null | void

Usage

In-chat interrupt UI (default)

function ApprovalInterrupt() {
  useInterrupt({
    render: ({ event, resolve }) => (
      <div className="p-3 border rounded">
        <p>{event.value.question}</p>
        <div className="mt-2 flex gap-2">
          <button onClick={() => resolve({ approved: true })}>Approve</button>
          <button onClick={() => resolve({ approved: false })}>Reject</button>
        </div>
      </div>
    ),
  });

  return null;
}

Manual placement with async preprocessing

function SidePanelInterrupt() {
  const element = useInterrupt({
    renderInChat: false,
    enabled: (event) => event.value.startsWith("approval:"),
    handler: async ({ event }) => ({ label: event.value.toUpperCase() }),
    render: ({ event, result, resolve }) => (
      <aside className="rounded border p-3">
        <div className="font-medium">{result?.label ?? ""}</div>
        <div className="mt-2">{event.value}</div>
        <button className="mt-2" onClick={() => resolve({ accepted: true })}>
          Continue
        </button>
      </aside>
    ),
  });

  return <>{element}</>;
}

Behavior

  • Interrupts are collected from agent custom events named on_interrupt.
  • Interrupt UI is surfaced when the run finalizes.
  • Starting a new run clears pending interrupt state.
  • event.value is any -- type-narrow in your callbacks as needed.
  • render.result is inferred from handler return type and is always TResult | null.
  • If handler throws or rejects, result is set to null.

Related