1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
7pub struct Capability {
8 pub kind: CapabilityKind,
9 pub scope: CapabilityScope,
10 pub grant_mode: GrantMode,
11}
12
13impl Capability {
14 pub fn new(kind: CapabilityKind, scope: CapabilityScope, grant_mode: GrantMode) -> Self {
15 Self {
16 kind,
17 scope,
18 grant_mode,
19 }
20 }
21
22 pub fn covers_file_action(&self, action_kind: &CapabilityKind, path: &PathBuf) -> bool {
24 if std::mem::discriminant(&self.kind) != std::mem::discriminant(action_kind) {
25 return false;
26 }
27 match &self.scope {
28 CapabilityScope::Directory(dir) => path.starts_with(dir),
29 CapabilityScope::Global => true,
30 CapabilityScope::Session(_) => true,
31 }
32 }
33}
34
35#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(tag = "type", rename_all = "snake_case")]
38pub enum CapabilityKind {
39 FileRead {
40 patterns: Vec<String>,
41 },
42 FileWrite {
43 patterns: Vec<String>,
44 },
45 FileDelete {
46 patterns: Vec<String>,
47 },
48 ShellExecute {
49 allowed_commands: Option<Vec<String>>,
50 },
51 NetworkAccess {
52 allowed_hosts: Vec<String>,
53 },
54 AgentSpawn,
55 ToolUse {
56 tool_names: Vec<String>,
57 },
58 HumanPrompt,
59}
60
61impl CapabilityKind {
62 pub fn display_name(&self) -> &'static str {
63 match self {
64 Self::FileRead { .. } => "File Read",
65 Self::FileWrite { .. } => "File Write",
66 Self::FileDelete { .. } => "File Delete",
67 Self::ShellExecute { .. } => "Shell Execute",
68 Self::NetworkAccess { .. } => "Network Access",
69 Self::AgentSpawn => "Spawn Agent",
70 Self::ToolUse { .. } => "Tool Use",
71 Self::HumanPrompt => "Ask Human",
72 }
73 }
74}
75
76#[derive(Debug, Clone, Serialize, Deserialize)]
78#[serde(tag = "type", rename_all = "snake_case")]
79pub enum CapabilityScope {
80 Directory(PathBuf),
81 Session(String),
82 Global,
83}
84
85#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "snake_case")]
88pub enum GrantMode {
89 Always,
91 Once,
93 PerUse,
95 Never,
97}
98
99#[derive(Debug, Clone, Default, Serialize, Deserialize)]
101pub struct CapabilitySet {
102 pub capabilities: Vec<Capability>,
103}
104
105impl CapabilitySet {
106 pub fn add(&mut self, cap: Capability) {
107 self.capabilities.push(cap);
108 }
109
110 pub fn grant_mode_for(
113 &self,
114 kind: &CapabilityKind,
115 path: Option<&PathBuf>,
116 ) -> Option<&GrantMode> {
117 self.capabilities
118 .iter()
119 .find(|cap| {
120 std::mem::discriminant(&cap.kind) == std::mem::discriminant(kind)
121 && path
122 .map(|p| cap.covers_file_action(kind, p))
123 .unwrap_or(true)
124 })
125 .map(|cap| &cap.grant_mode)
126 }
127
128 pub fn default_coding_agent(workspace: PathBuf) -> Self {
130 let mut set = Self::default();
131 set.add(Capability::new(
132 CapabilityKind::FileRead {
133 patterns: vec!["**".to_string()],
134 },
135 CapabilityScope::Directory(workspace.clone()),
136 GrantMode::Always,
137 ));
138 set.add(Capability::new(
139 CapabilityKind::FileWrite {
140 patterns: vec!["**".to_string()],
141 },
142 CapabilityScope::Directory(workspace.clone()),
143 GrantMode::Once,
144 ));
145 set.add(Capability::new(
146 CapabilityKind::ShellExecute {
147 allowed_commands: None,
148 },
149 CapabilityScope::Global,
150 GrantMode::PerUse,
151 ));
152 set.add(Capability::new(
153 CapabilityKind::NetworkAccess {
154 allowed_hosts: vec![],
155 },
156 CapabilityScope::Global,
157 GrantMode::Never,
158 ));
159 set
160 }
161}