defineInterrupt pauses the run for a question that is not a tool approval. The component sends the answer.
Two schemas define the exchange:
This page covers the UI. To pause a run on the server, read Generic interrupts.
import { defineInterrupt } from '@tanstack/ai'
import { z } from 'zod'
export const choosePlan = defineInterrupt({
id: 'choosePlan',
payloadSchema: z.object({
options: z.array(z.string()),
}),
responseSchema: z.string(),
})import { fetchServerSentEvents } from '@tanstack/ai-react'
import { createChatHook } from '@tanstack/ai-react/ui'
const chatOptions = {
connection: fetchServerSentEvents('/api/chat'),
interrupts: [choosePlan],
}
const { useAppChat } = createChatHook({
options: chatOptions,
components: {
layout: ({ Messages, Interrupts }) => (
<main>
<Messages />
<Interrupts />
</main>
),
message: ({ Parts }) => <article><Parts /></article>,
},
partsComponents: {
text: ({ part }) => <p>{part.content}</p>,
fallback: () => null,
},
interruptsComponents: {
generic: {
choosePlan: ({ interrupt }) => (
<fieldset>
<legend>Pick a plan</legend>
{interrupt.payload?.options.map((plan) => (
<button key={plan} onClick={() => interrupt.resolveInterrupt(plan)}>
{plan}
</button>
))}
</fieldset>
),
fallback: ({ interrupt }) => <p>{interrupt.reason}</p>,
},
},
})
export function PlanChat() {
const chat = useAppChat({ threadId: 'support-chat' })
return <chat.AppChat />
}When the server asks, the plan buttons show in the interrupt list.
fallback catches two things:
If the copy must be different, branch on interrupt.kind === 'unbound'.