1use serde::{Deserialize, Serialize};
7use std::sync::Arc;
8use tokio::sync::RwLock;
9
10use crate::settings::{PermissionChecker, PermissionDecision};
11use claude_code_agent_sdk::PermissionMode as SdkPermissionMode;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
17#[serde(rename_all = "camelCase")]
18pub enum PermissionMode {
19 Default,
21 AcceptEdits,
23 Plan,
25 DontAsk,
27 #[default]
29 BypassPermissions,
30}
31
32impl PermissionMode {
33 pub fn parse(s: &str) -> Option<Self> {
35 match s {
36 "default" => Some(Self::Default),
37 "acceptEdits" => Some(Self::AcceptEdits),
38 "plan" => Some(Self::Plan),
39 "dontAsk" => Some(Self::DontAsk),
40 "bypassPermissions" => Some(Self::BypassPermissions),
41 _ => None,
42 }
43 }
44
45 pub fn as_str(&self) -> &'static str {
47 match self {
48 Self::Default => "default",
49 Self::AcceptEdits => "acceptEdits",
50 Self::Plan => "plan",
51 Self::DontAsk => "dontAsk",
52 Self::BypassPermissions => "bypassPermissions",
53 }
54 }
55
56 pub fn to_sdk_mode(&self) -> SdkPermissionMode {
60 match self {
61 PermissionMode::Default => SdkPermissionMode::Default,
62 PermissionMode::AcceptEdits => SdkPermissionMode::AcceptEdits,
63 PermissionMode::Plan => SdkPermissionMode::Plan,
64 PermissionMode::DontAsk => {
65 SdkPermissionMode::Default
67 }
68 PermissionMode::BypassPermissions => SdkPermissionMode::BypassPermissions,
69 }
70 }
71
72 pub fn allows_writes(&self) -> bool {
74 matches!(
75 self,
76 Self::Default | Self::AcceptEdits | Self::BypassPermissions
77 )
78 }
79
80 pub fn auto_approve_edits(&self) -> bool {
82 matches!(self, Self::AcceptEdits | Self::BypassPermissions)
83 }
84}
85
86#[derive(Debug, Clone, PartialEq, Eq)]
88pub enum ToolPermissionResult {
89 Allowed,
91 Blocked { reason: String },
93 NeedsPermission,
95}
96
97#[derive(Debug)]
104pub struct PermissionHandler {
105 mode: PermissionMode,
106 checker: Option<Arc<RwLock<PermissionChecker>>>,
108}
109
110impl Default for PermissionHandler {
111 fn default() -> Self {
112 Self {
113 mode: PermissionMode::BypassPermissions,
114 checker: None,
115 }
116 }
117}
118
119impl PermissionHandler {
120 pub fn new() -> Self {
122 Self::default()
123 }
124
125 pub fn with_mode(mode: PermissionMode) -> Self {
127 Self {
128 mode,
129 checker: None,
130 }
131 }
132
133 pub fn with_checker(checker: Arc<RwLock<PermissionChecker>>) -> Self {
135 Self {
136 mode: PermissionMode::BypassPermissions,
137 checker: Some(checker),
138 }
139 }
140
141 pub fn with_checker_owned(checker: PermissionChecker) -> Self {
143 Self {
144 mode: PermissionMode::BypassPermissions,
145 checker: Some(Arc::new(RwLock::new(checker))),
146 }
147 }
148
149 pub fn mode(&self) -> PermissionMode {
151 self.mode
152 }
153
154 pub fn set_mode(&mut self, mode: PermissionMode) {
156 self.mode = mode;
157 }
158
159 pub fn set_checker(&mut self, checker: Arc<RwLock<PermissionChecker>>) {
161 self.checker = Some(checker);
162 }
163
164 pub async fn checker_mut(
166 &mut self,
167 ) -> Option<tokio::sync::RwLockWriteGuard<'_, PermissionChecker>> {
168 if let Some(ref checker) = self.checker {
169 Some(checker.write().await)
170 } else {
171 None
172 }
173 }
174
175 pub fn should_auto_approve(&self, tool_name: &str, _input: &serde_json::Value) -> bool {
179 match self.mode {
180 PermissionMode::BypassPermissions => true,
181 PermissionMode::AcceptEdits => {
182 matches!(
184 tool_name,
185 "Read" | "Edit" | "Write" | "Glob" | "Grep" | "NotebookRead" | "NotebookEdit"
186 )
187 }
188 PermissionMode::Plan => {
189 matches!(tool_name, "Read" | "Glob" | "Grep" | "NotebookRead")
191 }
192 PermissionMode::DontAsk => {
193 false
196 }
197 PermissionMode::Default => {
198 matches!(tool_name, "Read" | "Glob" | "Grep" | "NotebookRead")
200 }
201 }
202 }
203
204 pub fn is_tool_blocked(&self, tool_name: &str) -> bool {
206 if self.mode == PermissionMode::Plan {
207 matches!(tool_name, "Edit" | "Write" | "Bash" | "NotebookEdit")
209 } else {
210 false
211 }
212 }
213
214 pub async fn check_permission(
219 &self,
220 tool_name: &str,
221 tool_input: &serde_json::Value,
222 ) -> ToolPermissionResult {
223 if self.mode == PermissionMode::BypassPermissions {
225 return ToolPermissionResult::Allowed;
226 }
227
228 if self.is_tool_blocked(tool_name) {
230 return ToolPermissionResult::Blocked {
231 reason: format!(
232 "Tool {} is blocked in {} mode",
233 tool_name,
234 self.mode.as_str()
235 ),
236 };
237 }
238
239 if let Some(ref checker) = self.checker {
241 let checker_read = checker.read().await;
242 let result = checker_read.check_permission(tool_name, tool_input);
243 match result.decision {
244 PermissionDecision::Deny => {
245 return ToolPermissionResult::Blocked {
246 reason: result
247 .rule
248 .map(|r| format!("Denied by rule: {}", r))
249 .unwrap_or_else(|| "Denied by settings".to_string()),
250 };
251 }
252 PermissionDecision::Allow => {
253 return ToolPermissionResult::Allowed;
254 }
255 PermissionDecision::Ask => {
256 }
258 }
259 }
260
261 if matches!(
264 tool_name,
265 "AskUserQuestion" | "Task" | "TodoWrite" | "SlashCommand"
266 ) {
267 return ToolPermissionResult::Allowed;
268 }
269
270 if self.should_auto_approve(tool_name, tool_input) {
272 return ToolPermissionResult::Allowed;
273 }
274
275 ToolPermissionResult::NeedsPermission
277 }
278
279 pub async fn add_allow_rule(&self, tool_name: &str) {
281 if let Some(ref checker) = self.checker {
282 let mut checker_write = checker.write().await;
283 checker_write.add_allow_rule(tool_name);
284 }
285 }
286}
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291 use serde_json::json;
292
293 #[test]
294 fn test_permission_mode_parse() {
295 assert_eq!(
296 PermissionMode::parse("default"),
297 Some(PermissionMode::Default)
298 );
299 assert_eq!(
300 PermissionMode::parse("acceptEdits"),
301 Some(PermissionMode::AcceptEdits)
302 );
303 assert_eq!(PermissionMode::parse("plan"), Some(PermissionMode::Plan));
304 assert_eq!(
305 PermissionMode::parse("bypassPermissions"),
306 Some(PermissionMode::BypassPermissions)
307 );
308 assert_eq!(PermissionMode::parse("invalid"), None);
309 }
310
311 #[test]
312 fn test_permission_mode_str() {
313 assert_eq!(PermissionMode::Default.as_str(), "default");
314 assert_eq!(PermissionMode::AcceptEdits.as_str(), "acceptEdits");
315 }
316
317 #[test]
318 fn test_permission_handler_default() {
319 let handler = PermissionHandler::new();
320 let input = json!({});
321
322 assert!(handler.should_auto_approve("Read", &input));
324 assert!(handler.should_auto_approve("Glob", &input));
325 assert!(handler.should_auto_approve("Edit", &input));
326 assert!(handler.should_auto_approve("Bash", &input));
327 }
328
329 #[test]
330 fn test_permission_handler_explicit_default_mode() {
331 let handler = PermissionHandler::with_mode(PermissionMode::Default);
333 let input = json!({});
334
335 assert!(handler.should_auto_approve("Read", &input));
337 assert!(handler.should_auto_approve("Glob", &input));
338 assert!(!handler.should_auto_approve("Edit", &input));
340 assert!(!handler.should_auto_approve("Bash", &input));
341 }
342
343 #[test]
344 fn test_permission_handler_accept_edits() {
345 let handler = PermissionHandler::with_mode(PermissionMode::AcceptEdits);
346 let input = json!({});
347
348 assert!(handler.should_auto_approve("Read", &input));
349 assert!(handler.should_auto_approve("Edit", &input));
350 assert!(handler.should_auto_approve("Write", &input));
351 assert!(!handler.should_auto_approve("Bash", &input));
353 }
354
355 #[test]
356 fn test_permission_handler_bypass() {
357 let handler = PermissionHandler::with_mode(PermissionMode::BypassPermissions);
358 let input = json!({});
359
360 assert!(handler.should_auto_approve("Read", &input));
362 assert!(handler.should_auto_approve("Edit", &input));
363 assert!(handler.should_auto_approve("Bash", &input));
364 }
365
366 #[test]
367 fn test_permission_handler_plan_mode() {
368 let handler = PermissionHandler::with_mode(PermissionMode::Plan);
369 let input = json!({});
370
371 assert!(handler.should_auto_approve("Read", &input));
373 assert!(!handler.should_auto_approve("Edit", &input));
374
375 assert!(handler.is_tool_blocked("Edit"));
377 assert!(handler.is_tool_blocked("Bash"));
378 assert!(!handler.is_tool_blocked("Read"));
379 }
380}