1use std::io::{self, Write};
2
3use async_trait::async_trait;
4use serde_json::Value;
5
6#[derive(Debug, Clone)]
7pub struct ApprovalRequest {
8 pub tool_name: String,
9 pub arguments: Value,
10}
11
12#[derive(Debug, Clone)]
13pub struct ApprovalResponse {
14 pub approved: bool,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum ApprovalPolicy {
19 Always,
20 Never,
21 OnDestructive,
22}
23
24#[async_trait]
25pub trait ApprovalHandler: Send + Sync {
26 async fn approve(&self, request: ApprovalRequest) -> ApprovalResponse;
27}
28
29pub struct AutoApprove;
30
31#[async_trait]
32impl ApprovalHandler for AutoApprove {
33 async fn approve(&self, _request: ApprovalRequest) -> ApprovalResponse {
34 ApprovalResponse { approved: true }
35 }
36}
37
38pub struct PromptApprove;
39
40#[async_trait]
41impl ApprovalHandler for PromptApprove {
42 async fn approve(&self, request: ApprovalRequest) -> ApprovalResponse {
43 eprintln!(
44 "\n[approval] tool={} args={}",
45 request.tool_name, request.arguments
46 );
47 eprint!("Approve? [y/N]: ");
48 let _ = io::stderr().flush();
49 let mut line = String::new();
50 if io::stdin().read_line(&mut line).is_err() {
51 return ApprovalResponse { approved: false };
52 }
53 let approved = line.trim().eq_ignore_ascii_case("y");
54 ApprovalResponse { approved }
55 }
56}
57
58pub struct OnDestructiveApprove {
60 inner: PromptApprove,
61}
62
63#[async_trait]
64impl ApprovalHandler for OnDestructiveApprove {
65 async fn approve(&self, request: ApprovalRequest) -> ApprovalResponse {
66 match request.tool_name.as_str() {
67 "write" | "edit" | "shell" => self.inner.approve(request).await,
68 _ => ApprovalResponse { approved: true },
69 }
70 }
71}
72
73pub fn handler_for_policy(policy: ApprovalPolicy) -> Box<dyn ApprovalHandler> {
74 match policy {
75 ApprovalPolicy::Never => Box::new(AutoApprove),
76 ApprovalPolicy::Always => Box::new(PromptApprove),
77 ApprovalPolicy::OnDestructive => Box::new(OnDestructiveApprove {
78 inner: PromptApprove,
79 }),
80 }
81}