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}