Epicenter
Local-first, open source apps
Confirmation asks the person for a yes or no before a tool runs. The question is an Item with Deny and Approve buttons. Once the person answers, the call becomes a Tool line.
Installation
Confirmation is built with the Item and Button components.
Usage
<Item.Root variant="outline" size="sm">
<Item.Media variant="icon">
<TerminalIcon />
</Item.Media>
<Item.Content>
<Item.Title>Run {part.input.command}?</Item.Title>
<Item.Description>The command runs in this project.</Item.Description>
</Item.Content>
<Item.Actions>
<Button size="sm" variant="outline" onclick={deny}>Deny</Button>
<Button size="sm" onclick={approve}>Approve</Button>
</Item.Actions>
</Item.Root> AI SDK
While the part is approval-requested, answer with the approval id. Pass sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses so the chat continues once every approval has an answer.
chat.addToolApprovalResponse({
id: part.approval.id,
approved: true,
}); const chat = new Chat({
sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithApprovalResponses,
}); Approve streams the tool output. Deny streams output-denied. Either way, the part becomes a Tool line.
See Tool.
AI SDK Helper
To script an approval with createChat, set needsApproval on the tool. The output streams after approval, and the next agent turn reads toolCall.approved.
const chat = createChat()
.user("Clean up the build folder.")
.assistant(({ writer }) => {
writer.tool("runCommand", {
input: { command: "rm -rf dist" },
needsApproval: true,
output: { stdout: "" },
});
})
.assistant(({ writer, toolCall }) => {
writer.text(
toolCall?.approved
? "Removed the dist folder."
: "Okay, I left the dist folder in place."
);
});