In 1.0, tool approval, a generic pause, and client-tool execution are AG-UI interrupts. Resolve each one on the item. addToolApprovalResponse and pendingInterrupts are removed.
There is no codemod. Upgrade the server and the client together. For the full list of 1.0 changes, see Upgrade to 1.0.
The sections below show each step.
| 0.x (removed in 1.0) | 1.0 |
|---|---|
| pendingInterrupts | interrupts |
| ChatClient.getPendingInterrupts() | ChatClient.getInterrupts() |
| addToolApprovalResponse({ id, approved }) | Find the bound tool-approval item and call interrupt.resolveInterrupt(approved). Removed from every framework and from ChatClient. |
| Raw resumeInterrupts(entries, state) | Item methods, or root resolveInterrupts(...). Keep resumeInterruptsUnsafe for validated recovery tools. |
| interrupt.error | interrupt.errors[0] |
| approval-requested custom event | RUN_FINISHED interrupt descriptor, reason tool_call |
| tool-input-available custom event | RUN_FINISHED interrupt descriptor, reason tanstack:client_tool_execution |
| Boolean denial treated as cancellation | resolveInterrupt(false) for a denial. cancel() for a cancellation with no payload. |
These stay the same:
// 0.x
await addToolApprovalResponse({ id: approval.id, approved: true })
// 1.0
const interrupt = interrupts.find(
(item) => item.kind === 'tool-approval' && item.toolName === 'transfer',
)
if (interrupt?.kind === 'tool-approval' && interrupt.toolName === 'transfer') {
interrupt.resolveInterrupt(true)
}A single valid item submits at once. For the full component, see Tool Approval.
In 0.x, a boolean approval had no typed data. In 1.0, add an approvalSchema to the tool, and send data under payload:
interrupt.resolveInterrupt(true, {
editedArgs: { amount: 12, recipient: 'Ada' }, // optional, approval only, full replacement
payload: { note: 'Reviewed' },
})
interrupt.resolveInterrupt(false, { payload: { reason: 'Policy limit' } })If you called addToolApprovalResponse({ approved: false }), use resolveInterrupt(false). It is a denial, not a cancellation.
Native batches are all or nothing. Replace loops over approval ids with one of these:
resolveInterrupts((interrupt) => {
if (interrupt.kind === 'tool-approval') {
interrupt.resolveInterrupt(true, { payload: { note: 'Batch review' } })
return
}
interrupt.cancel()
})Other root helpers:
See Multiple Interrupts.
Do not derive a static type from a responseSchema that you received. Parse the value as unknown, convert the schema with z.fromJSONSchema, and then resolve with the validated value. For typed generic interrupts, use defineInterrupt. See Generic Interrupts.
A native server emits these events, in this order:
A continuation has:
Interrupts are ephemeral. The server rebuilds and validates the expected batch from the submitted history and its current tool definitions. So a stateless route needs no persistence. But the batch comes from client input, so this mode does not give authoritative recovery, exactly-once delivery, replay protection, or restart recovery.
resumeInterruptsUnsafe is a low-level escape hatch. It sends validated raw resume entries directly. Do not use it for approval UI.
1.0 does not read the approval-requested and tool-input-available custom events. An interrupt descriptor without a binding (only metadata.kind) shows as kind: 'unbound', and you cannot resume it. Upgrade the server and the client together, and finish or drop the runs that paused on 0.x.
The quick migration covers the client. Also do these:
When this is done, your approvals run on 1.0 interrupts. Start from the Overview for the current flow.