use async_trait::async_trait;
use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone)]
pub enum PermissionDecision {
Allow,
Deny(String),
Ask,
}
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum PermissionMode {
#[default]
Default,
AcceptEdits,
Plan,
DontAsk,
BypassPermissions,
}
#[async_trait]
pub trait PreToolHook: Send + Sync {
async fn check(&self, tool_name: &str, args: &Value) -> PermissionDecision;
}
pub(crate) const EDIT_TOOLS: &[&str] = &["Read", "Write", "Edit", "Glob", "Grep"];
impl PermissionMode {
pub fn evaluate(self, tool_name: &str, read_only_hint: bool) -> PermissionDecision {
match self {
Self::BypassPermissions => PermissionDecision::Allow,
Self::Plan => {
if read_only_hint {
PermissionDecision::Allow
} else {
PermissionDecision::Deny(format!(
"Plan mode: tool `{tool_name}` is not read-only"
))
}
}
Self::AcceptEdits => {
if EDIT_TOOLS.contains(&tool_name) || read_only_hint {
PermissionDecision::Allow
} else {
PermissionDecision::Ask
}
}
Self::DontAsk => PermissionDecision::Deny(format!(
"Permission mode dontAsk: `{tool_name}` is not pre-approved"
)),
Self::Default => PermissionDecision::Ask,
}
}
}