本页还没有译文
本页 15 段还没译成Русский,下面显示的是英文原文。Ask before a tool runs
还没翻译译自英文(tanstack.com)本页 15 段都还没译
Add needsApproval: true to the tool. The run pauses and your UI gets an interrupt prop.
const deleteFile = toolDefinition({
name: 'deleteFile',
description: 'Delete a file',
needsApproval: true,
inputSchema: z.object({ path: z.string() }),
outputSchema: z.object({ deleted: z.boolean() }),
}).client()You choose where the approval appears. Two places are available.
Next to the tool
The tool component receives interrupt alongside part. Render the buttons there.
import { fetchServerSentEvents } from '@tanstack/ai-react'
import { createChatHook } from '@tanstack/ai-react/ui'
import { toolDefinition } from '@tanstack/ai'
import { z } from 'zod'
const deleteFile = toolDefinition({
name: 'deleteFile',
description: 'Delete a file',
needsApproval: true,
inputSchema: z.object({ path: z.string() }),
outputSchema: z.object({ deleted: z.boolean() }),
}).client()
const chatOptions = {
connection: fetchServerSentEvents('/api/chat'),
tools: [deleteFile],
}
const { useAppChat } = createChatHook({
options: chatOptions,
components: {
layout: ({ Messages }) => <main><Messages /></main>,
message: ({ Parts }) => <article><Parts /></article>,
},
partsComponents: {
text: ({ part }) => <p>{part.content}</p>,
fallback: () => null,
},
toolsComponents: {
deleteFile: ({ part, interrupt }) => {
if (interrupt?.status === 'pending') {
return (
<div>
<p>Delete {part.input?.path}?</p>
<button onClick={() => interrupt.resolveInterrupt(true)}>
Delete
</button>
<button onClick={() => interrupt.resolveInterrupt(false)}>
Keep
</button>
</div>
)
}
if (part.state === 'complete') {
return <p>{part.output?.deleted ? 'Deleted' : 'Kept'}</p>
}
return <p>{part.input?.path}</p>
},
},
})
export function FileChat() {
const chat = useAppChat()
return <chat.AppChat />
}resolveInterrupt(true) approves. resolveInterrupt(false) denies. The run continues either way.
In a list instead
Some apps collect every pending decision in one place. Register the tool name under interruptsComponents.tools and the approval moves out of the message.
import type { InterruptProps } from '@tanstack/ai-react/ui'
export function DeleteApproval({
interrupt,
}: InterruptProps<typeof chatOptions, 'deleteFile'>) {
return (
<div>
<p>Delete {interrupt.originalArgs.path}?</p>
<button onClick={() => interrupt.resolveInterrupt(true)}>Delete</button>
<button onClick={() => interrupt.resolveInterrupt(false)}>Keep</button>
</div>
)
}Then wire it up and render Interrupts in the layout:
const { useAppChat } = createChatHook({
options: chatOptions,
components: {
layout: ({ Messages, Interrupts }) => (
<main>
<Messages />
<Interrupts />
</main>
),
message: ({ Parts }) => <article><Parts /></article>,
},
partsComponents: { fallback: () => null },
toolsComponents: {
deleteFile: ({ part }) => <p>{part.input?.path}</p>,
},
interruptsComponents: {
tools: { deleteFile: DeleteApproval },
},
})The tool component no longer reads interrupt. Its approval is in the list.
Pick one place per tool
Register the tool under interruptsComponents.tools and the approval renders in the list. Leave it out and the approval reaches the tool component instead. Do not do both for one tool.
interrupt.originalArgs holds the arguments the model proposed. It is typed from the tool's inputSchema, so path is a string here.
Next
- A decision that is not about a tool? See a custom interrupt.
- More on the protocol behind this: tool approval.