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::Default,
115 checker: None,
116 }
117 }
118}
119
120impl PermissionHandler {
121 pub fn new() -> Self {
125 Self::default()
126 }
127
128 pub fn with_mode(mode: PermissionMode) -> Self {
130 Self {
131 mode,
132 checker: None,
133 }
134 }
135
136 pub fn with_checker(checker: Arc<RwLock<PermissionChecker>>) -> Self {
140 Self {
141 mode: PermissionMode::Default,
142 checker: Some(checker),
143 }
144 }
145
146 pub fn with_checker_owned(checker: PermissionChecker) -> Self {
150 Self {
151 mode: PermissionMode::Default,
152 checker: Some(Arc::new(RwLock::new(checker))),
153 }
154 }
155
156 pub fn mode(&self) -> PermissionMode {
158 self.mode
159 }
160
161 pub fn set_mode(&mut self, mode: PermissionMode) {
163 self.mode = mode;
164 }
165
166 pub fn set_checker(&mut self, checker: Arc<RwLock<PermissionChecker>>) {
168 self.checker = Some(checker);
169 }
170
171 pub async fn checker_mut(
173 &mut self,
174 ) -> Option<tokio::sync::RwLockWriteGuard<'_, PermissionChecker>> {
175 if let Some(ref checker) = self.checker {
176 Some(checker.write().await)
177 } else {
178 None
179 }
180 }
181
182 pub fn should_auto_approve(&self, tool_name: &str, _input: &serde_json::Value) -> bool {
190 match self.mode {
191 PermissionMode::BypassPermissions => true,
192 PermissionMode::AcceptEdits => {
193 true
196 }
197 PermissionMode::Plan => {
198 matches!(tool_name, "Read" | "Glob" | "Grep" | "NotebookRead")
200 }
201 PermissionMode::DontAsk => {
202 false
205 }
206 PermissionMode::Default => {
207 matches!(tool_name, "Read" | "Glob" | "Grep" | "NotebookRead")
209 }
210 }
211 }
212
213 pub fn is_tool_blocked(&self, tool_name: &str) -> bool {
215 if self.mode == PermissionMode::Plan {
216 matches!(tool_name, "Edit" | "Write" | "Bash" | "NotebookEdit")
218 } else {
219 false
220 }
221 }
222
223 pub async fn check_permission(
232 &self,
233 tool_name: &str,
234 tool_input: &serde_json::Value,
235 ) -> ToolPermissionResult {
236 if matches!(
239 self.mode,
240 PermissionMode::BypassPermissions | PermissionMode::AcceptEdits
241 ) {
242 return ToolPermissionResult::Allowed;
243 }
244
245 if self.is_tool_blocked(tool_name) {
247 return ToolPermissionResult::Blocked {
248 reason: format!(
249 "Tool {} is blocked in {} mode",
250 tool_name,
251 self.mode.as_str()
252 ),
253 };
254 }
255
256 if let Some(ref checker) = self.checker {
258 let checker_read = checker.read().await;
259 let result = checker_read.check_permission(tool_name, tool_input);
260 match result.decision {
261 PermissionDecision::Deny => {
262 return ToolPermissionResult::Blocked {
263 reason: result
264 .rule
265 .map(|r| format!("Denied by rule: {}", r))
266 .unwrap_or_else(|| "Denied by settings".to_string()),
267 };
268 }
269 PermissionDecision::Allow => {
270 return ToolPermissionResult::Allowed;
271 }
272 PermissionDecision::Ask => {
273 }
275 }
276 }
277
278 if matches!(
281 tool_name,
282 "AskUserQuestion" | "Task" | "TodoWrite" | "SlashCommand"
283 ) {
284 return ToolPermissionResult::Allowed;
285 }
286
287 if self.should_auto_approve(tool_name, tool_input) {
289 return ToolPermissionResult::Allowed;
290 }
291
292 ToolPermissionResult::NeedsPermission
294 }
295
296 pub async fn add_allow_rule(&self, tool_name: &str) {
298 if let Some(ref checker) = self.checker {
299 let mut checker_write = checker.write().await;
300 checker_write.add_allow_rule(tool_name);
301 }
302 }
303
304 pub fn add_allow_rule_for_tool_call(&self, tool_name: &str, tool_input: &serde_json::Value) {
307 if let Some(ref checker) = self.checker {
308 if let Ok(mut checker_write) = checker.try_write() {
310 checker_write.add_allow_rule_for_tool_call(tool_name, tool_input);
311 }
312 }
313 }
314}
315
316#[cfg(test)]
317mod tests {
318 use super::*;
319 use serde_json::json;
320
321 #[test]
322 fn test_permission_mode_parse() {
323 assert_eq!(
324 PermissionMode::parse("default"),
325 Some(PermissionMode::Default)
326 );
327 assert_eq!(
328 PermissionMode::parse("acceptEdits"),
329 Some(PermissionMode::AcceptEdits)
330 );
331 assert_eq!(PermissionMode::parse("plan"), Some(PermissionMode::Plan));
332 assert_eq!(
333 PermissionMode::parse("bypassPermissions"),
334 Some(PermissionMode::BypassPermissions)
335 );
336 assert_eq!(PermissionMode::parse("invalid"), None);
337 }
338
339 #[test]
340 fn test_permission_mode_str() {
341 assert_eq!(PermissionMode::Default.as_str(), "default");
342 assert_eq!(PermissionMode::AcceptEdits.as_str(), "acceptEdits");
343 }
344
345 #[test]
346 fn test_permission_handler_default() {
347 let handler = PermissionHandler::new();
348 let input = json!({});
349
350 assert!(handler.should_auto_approve("Read", &input));
352 assert!(handler.should_auto_approve("Glob", &input));
353 assert!(!handler.should_auto_approve("Edit", &input));
355 assert!(!handler.should_auto_approve("Bash", &input));
356 }
357
358 #[test]
359 fn test_permission_handler_accept_edits() {
360 let handler = PermissionHandler::with_mode(PermissionMode::AcceptEdits);
361 let input = json!({});
362
363 assert!(handler.should_auto_approve("Read", &input));
366 assert!(handler.should_auto_approve("Edit", &input));
367 assert!(handler.should_auto_approve("Write", &input));
368 assert!(handler.should_auto_approve("Bash", &input));
369 }
370
371 #[test]
372 fn test_permission_handler_bypass() {
373 let handler = PermissionHandler::with_mode(PermissionMode::BypassPermissions);
374 let input = json!({});
375
376 assert!(handler.should_auto_approve("Read", &input));
378 assert!(handler.should_auto_approve("Edit", &input));
379 assert!(handler.should_auto_approve("Bash", &input));
380 }
381
382 #[test]
383 fn test_permission_handler_plan_mode() {
384 let handler = PermissionHandler::with_mode(PermissionMode::Plan);
385 let input = json!({});
386
387 assert!(handler.should_auto_approve("Read", &input));
389 assert!(!handler.should_auto_approve("Edit", &input));
390
391 assert!(handler.is_tool_blocked("Edit"));
393 assert!(handler.is_tool_blocked("Bash"));
394 assert!(!handler.is_tool_blocked("Read"));
395 }
396}