Skip to main content

vtcode_acp/
permissions.rs

1use std::sync::Arc;
2
3use crate::acp;
4use crate::acp::{Client, Error as AcpError};
5use async_trait::async_trait;
6use serde_json::Value;
7use tracing::{error, warn};
8
9use crate::reports::{
10    TOOL_PERMISSION_ALLOW_ALWAYS_OPTION_ID, TOOL_PERMISSION_ALLOW_OPTION_ID,
11    TOOL_PERMISSION_ALLOW_PREFIX, TOOL_PERMISSION_CANCELLED_MESSAGE,
12    TOOL_PERMISSION_DENIED_MESSAGE, TOOL_PERMISSION_DENY_ALWAYS_OPTION_ID,
13    TOOL_PERMISSION_DENY_OPTION_ID, TOOL_PERMISSION_DENY_PREFIX,
14    TOOL_PERMISSION_REQUEST_FAILURE_LOG, TOOL_PERMISSION_REQUEST_FAILURE_MESSAGE,
15    TOOL_PERMISSION_UNKNOWN_OPTION_LOG, ToolExecutionReport,
16};
17
18use super::tooling::{SupportedTool, ToolDescriptor, ToolRegistryProvider};
19
20#[derive(Clone, Copy, Debug)]
21pub struct PermissionToolContext<'a> {
22    name: &'a str,
23    kind: acp::ToolKind,
24    action_label: &'a str,
25}
26
27impl<'a> PermissionToolContext<'a> {
28    #[must_use]
29    pub fn new(name: &'a str, kind: acp::ToolKind, action_label: &'a str) -> Self {
30        Self {
31            name,
32            kind,
33            action_label,
34        }
35    }
36}
37
38#[async_trait(?Send)]
39pub trait AcpPermissionPrompter {
40    fn permission_options(
41        &self,
42        tool: SupportedTool,
43        args: Option<&Value>,
44    ) -> Vec<acp::PermissionOption>;
45
46    async fn request_tool_permission(
47        &self,
48        client: &dyn Client,
49        session_id: &acp::SessionId,
50        call: &acp::ToolCall,
51        tool: SupportedTool,
52        args: &Value,
53    ) -> Result<Option<ToolExecutionReport>, AcpError>;
54
55    async fn request_named_tool_permission(
56        &self,
57        client: &dyn Client,
58        session_id: &acp::SessionId,
59        call: &acp::ToolCall,
60        tool: PermissionToolContext<'_>,
61        args: &Value,
62    ) -> Result<Option<ToolExecutionReport>, AcpError>;
63}
64
65pub struct DefaultPermissionPrompter<P> {
66    registry: P,
67}
68
69impl<P> DefaultPermissionPrompter<P>
70where
71    P: ToolRegistryProvider,
72{
73    pub fn new(registry: P) -> Self {
74        Self { registry }
75    }
76
77    fn render_action_label(&self, tool: SupportedTool, args: Option<&Value>) -> String {
78        if let Some(arguments) = args {
79            self.registry
80                .render_title(ToolDescriptor::Acp(tool), tool.function_name(), arguments)
81        } else {
82            tool.default_title().to_string()
83        }
84    }
85
86    fn permission_options_for_action(&self, action_label: &str) -> Vec<acp::PermissionOption> {
87        let allow_once_option = acp::PermissionOption::new(
88            acp::PermissionOptionId::from(Arc::from(TOOL_PERMISSION_ALLOW_OPTION_ID)),
89            format!("{TOOL_PERMISSION_ALLOW_PREFIX} {action_label} once"),
90            acp::PermissionOptionKind::AllowOnce,
91        );
92
93        let allow_always_option = acp::PermissionOption::new(
94            acp::PermissionOptionId::from(Arc::from(TOOL_PERMISSION_ALLOW_ALWAYS_OPTION_ID)),
95            format!("{TOOL_PERMISSION_ALLOW_PREFIX} {action_label} always"),
96            acp::PermissionOptionKind::AllowAlways,
97        );
98
99        let deny_once_option = acp::PermissionOption::new(
100            acp::PermissionOptionId::from(Arc::from(TOOL_PERMISSION_DENY_OPTION_ID)),
101            format!("{TOOL_PERMISSION_DENY_PREFIX} {action_label} once"),
102            acp::PermissionOptionKind::RejectOnce,
103        );
104
105        let deny_always_option = acp::PermissionOption::new(
106            acp::PermissionOptionId::from(Arc::from(TOOL_PERMISSION_DENY_ALWAYS_OPTION_ID)),
107            format!("{TOOL_PERMISSION_DENY_PREFIX} {action_label} always"),
108            acp::PermissionOptionKind::RejectAlways,
109        );
110
111        vec![
112            allow_once_option,
113            allow_always_option,
114            deny_once_option,
115            deny_always_option,
116        ]
117    }
118}
119
120#[async_trait(?Send)]
121impl<P> AcpPermissionPrompter for DefaultPermissionPrompter<P>
122where
123    P: ToolRegistryProvider,
124{
125    fn permission_options(
126        &self,
127        tool: SupportedTool,
128        args: Option<&Value>,
129    ) -> Vec<acp::PermissionOption> {
130        let action_label = self.render_action_label(tool, args);
131        self.permission_options_for_action(&action_label)
132    }
133
134    async fn request_tool_permission(
135        &self,
136        client: &dyn Client,
137        session_id: &acp::SessionId,
138        call: &acp::ToolCall,
139        tool: SupportedTool,
140        args: &Value,
141    ) -> Result<Option<ToolExecutionReport>, AcpError> {
142        let action_label = self.render_action_label(tool, Some(args));
143        self.request_named_tool_permission(
144            client,
145            session_id,
146            call,
147            PermissionToolContext::new(tool.function_name(), tool.kind(), &action_label),
148            args,
149        )
150        .await
151    }
152
153    async fn request_named_tool_permission(
154        &self,
155        client: &dyn Client,
156        session_id: &acp::SessionId,
157        call: &acp::ToolCall,
158        tool: PermissionToolContext<'_>,
159        args: &Value,
160    ) -> Result<Option<ToolExecutionReport>, AcpError> {
161        let fields = acp::ToolCallUpdateFields::default()
162            .title(call.title.clone())
163            .kind(tool.kind)
164            .status(acp::ToolCallStatus::Pending)
165            .raw_input(args.clone());
166
167        let request = acp::RequestPermissionRequest::new(
168            session_id.clone(),
169            acp::ToolCallUpdate::new(call.tool_call_id.clone(), fields),
170            self.permission_options_for_action(tool.action_label),
171        );
172
173        match client.request_permission(request).await {
174            Ok(response) => match response.outcome {
175                acp::RequestPermissionOutcome::Cancelled => Ok(Some(ToolExecutionReport::failure(
176                    tool.name,
177                    TOOL_PERMISSION_CANCELLED_MESSAGE,
178                ))),
179                acp::RequestPermissionOutcome::Selected(outcome) => {
180                    let option_id_str = outcome.option_id.0.as_ref();
181                    if option_id_str == TOOL_PERMISSION_ALLOW_OPTION_ID
182                        || option_id_str == TOOL_PERMISSION_ALLOW_ALWAYS_OPTION_ID
183                    {
184                        Ok(None)
185                    } else if option_id_str == TOOL_PERMISSION_DENY_OPTION_ID
186                        || option_id_str == TOOL_PERMISSION_DENY_ALWAYS_OPTION_ID
187                    {
188                        Ok(Some(ToolExecutionReport::failure(
189                            tool.name,
190                            TOOL_PERMISSION_DENIED_MESSAGE,
191                        )))
192                    } else {
193                        warn!("{}", TOOL_PERMISSION_UNKNOWN_OPTION_LOG);
194                        Ok(Some(ToolExecutionReport::failure(
195                            tool.name,
196                            TOOL_PERMISSION_DENIED_MESSAGE,
197                        )))
198                    }
199                }
200                _ => Ok(Some(ToolExecutionReport::failure(
201                    tool.name,
202                    TOOL_PERMISSION_DENIED_MESSAGE,
203                ))),
204            },
205            Err(error) => {
206                error!(%error, "{}", TOOL_PERMISSION_REQUEST_FAILURE_LOG);
207                Ok(Some(ToolExecutionReport::failure(
208                    tool.name,
209                    TOOL_PERMISSION_REQUEST_FAILURE_MESSAGE,
210                )))
211            }
212        }
213    }
214}