use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum PermissionDecision {
AllowOnce,
AllowAlways,
DenyOnce,
DenyAlways,
}
impl PermissionDecision {
pub fn is_allowed(self) -> bool {
matches!(self, Self::AllowOnce | Self::AllowAlways)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PermissionRequest {
pub tool_name: String,
pub description: String,
pub dangerous: bool,
pub arguments: serde_json::Value,
}
pub enum PermissionMode {
AutoApprove,
Callback(Box<dyn Fn(&PermissionRequest) -> PermissionDecision + Send + Sync>),
}
impl std::fmt::Debug for PermissionMode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PermissionMode::AutoApprove => write!(f, "AutoApprove"),
PermissionMode::Callback(_) => write!(f, "Callback(...)"),
}
}
}
pub struct PermissionTracker {
mode: PermissionMode,
always_allowed: std::collections::HashSet<String>,
always_denied: std::collections::HashSet<String>,
}
impl PermissionTracker {
pub fn new(mode: PermissionMode) -> Self {
Self {
mode,
always_allowed: std::collections::HashSet::new(),
always_denied: std::collections::HashSet::new(),
}
}
pub fn check(&mut self, request: &PermissionRequest) -> bool {
if self.always_allowed.contains(&request.tool_name) {
return true;
}
if self.always_denied.contains(&request.tool_name) {
return false;
}
match &self.mode {
PermissionMode::AutoApprove => true,
PermissionMode::Callback(cb) => {
let decision = cb(request);
match decision {
PermissionDecision::AllowAlways => {
self.always_allowed.insert(request.tool_name.clone());
true
}
PermissionDecision::DenyAlways => {
self.always_denied.insert(request.tool_name.clone());
false
}
PermissionDecision::AllowOnce => true,
PermissionDecision::DenyOnce => false,
}
}
}
}
}