Skip to main content

acp_cli/client/
permissions.rs

1use serde::{Deserialize, Serialize};
2
3use crate::bridge::{PermissionKind, PermissionOption, PermissionOutcome, ToolCallInfo};
4
5#[derive(Debug, Clone, Default, Deserialize, Serialize)]
6#[serde(rename_all = "snake_case")]
7pub enum PermissionMode {
8    ApproveAll,
9    #[default]
10    ApproveReads,
11    DenyAll,
12}
13
14/// Resolve a permission request based on the current permission mode.
15///
16/// For `ApproveAll`, automatically selects the first allow option.
17/// For `ApproveReads`, only auto-approves read-only tools; write tools are cancelled.
18/// For `DenyAll`, always cancels.
19pub fn resolve_permission(
20    tool: &ToolCallInfo,
21    options: &[PermissionOption],
22    mode: &PermissionMode,
23) -> PermissionOutcome {
24    match mode {
25        PermissionMode::ApproveAll => select_first_allow(options),
26        PermissionMode::ApproveReads => {
27            if is_read_only_tool(&tool.name) {
28                select_first_allow(options)
29            } else {
30                PermissionOutcome::Cancelled
31            }
32        }
33        PermissionMode::DenyAll => PermissionOutcome::Cancelled,
34    }
35}
36
37/// Returns true if the tool name corresponds to a read-only operation.
38pub fn is_read_only_tool(name: &str) -> bool {
39    matches!(
40        name,
41        "Read" | "Glob" | "Grep" | "WebSearch" | "WebFetch" | "LSP"
42    )
43}
44
45fn select_first_allow(options: &[PermissionOption]) -> PermissionOutcome {
46    options
47        .iter()
48        .find(|o| o.kind == PermissionKind::Allow)
49        .map(|o| PermissionOutcome::Selected {
50            option_id: o.option_id.clone(),
51        })
52        .unwrap_or(PermissionOutcome::Cancelled)
53}