use serde::{Deserialize, Serialize};
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Capability {
pub kind: CapabilityKind,
pub scope: CapabilityScope,
pub grant_mode: GrantMode,
}
impl Capability {
pub fn new(kind: CapabilityKind, scope: CapabilityScope, grant_mode: GrantMode) -> Self {
Self {
kind,
scope,
grant_mode,
}
}
pub fn covers_file_action(&self, action_kind: &CapabilityKind, path: &PathBuf) -> bool {
if std::mem::discriminant(&self.kind) != std::mem::discriminant(action_kind) {
return false;
}
match &self.scope {
CapabilityScope::Directory(dir) => path.starts_with(dir),
CapabilityScope::Global => true,
CapabilityScope::Session(_) => true,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CapabilityKind {
FileRead {
patterns: Vec<String>,
},
FileWrite {
patterns: Vec<String>,
},
FileDelete {
patterns: Vec<String>,
},
ShellExecute {
allowed_commands: Option<Vec<String>>,
},
NetworkAccess {
allowed_hosts: Vec<String>,
},
AgentSpawn,
ToolUse {
tool_names: Vec<String>,
},
HumanPrompt,
}
impl CapabilityKind {
pub fn display_name(&self) -> &'static str {
match self {
Self::FileRead { .. } => "File Read",
Self::FileWrite { .. } => "File Write",
Self::FileDelete { .. } => "File Delete",
Self::ShellExecute { .. } => "Shell Execute",
Self::NetworkAccess { .. } => "Network Access",
Self::AgentSpawn => "Spawn Agent",
Self::ToolUse { .. } => "Tool Use",
Self::HumanPrompt => "Ask Human",
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum CapabilityScope {
Directory(PathBuf),
Session(String),
Global,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum GrantMode {
Always,
Once,
PerUse,
Never,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CapabilitySet {
pub capabilities: Vec<Capability>,
}
impl CapabilitySet {
pub fn add(&mut self, cap: Capability) {
self.capabilities.push(cap);
}
pub fn grant_mode_for(
&self,
kind: &CapabilityKind,
path: Option<&PathBuf>,
) -> Option<&GrantMode> {
self.capabilities
.iter()
.find(|cap| {
std::mem::discriminant(&cap.kind) == std::mem::discriminant(kind)
&& path
.map(|p| cap.covers_file_action(kind, p))
.unwrap_or(true)
})
.map(|cap| &cap.grant_mode)
}
pub fn default_coding_agent(workspace: PathBuf) -> Self {
let mut set = Self::default();
set.add(Capability::new(
CapabilityKind::FileRead {
patterns: vec!["**".to_string()],
},
CapabilityScope::Directory(workspace.clone()),
GrantMode::Always,
));
set.add(Capability::new(
CapabilityKind::FileWrite {
patterns: vec!["**".to_string()],
},
CapabilityScope::Directory(workspace.clone()),
GrantMode::Once,
));
set.add(Capability::new(
CapabilityKind::ShellExecute {
allowed_commands: None,
},
CapabilityScope::Global,
GrantMode::PerUse,
));
set.add(Capability::new(
CapabilityKind::NetworkAccess {
allowed_hosts: vec![],
},
CapabilityScope::Global,
GrantMode::Never,
));
set
}
}