use super::types::{AutonomyGrant, Permission, ToolPattern};
impl AutonomyGrant {
pub fn builder() -> AutonomyGrantBuilder {
AutonomyGrantBuilder::default()
}
pub fn check(&self, tool_name: &str, argument: Option<&str>) -> Permission {
if self.matches_any(&self.forbidden, tool_name, argument) {
return Permission::Forbidden;
}
if self.matches_any(&self.require_approval, tool_name, argument) {
return Permission::RequiresApproval;
}
if self.matches_any(&self.auto_approve, tool_name, argument) {
return Permission::Allowed;
}
Permission::RequiresApproval
}
pub fn is_allowed(&self, tool_name: &str, argument: Option<&str>) -> bool {
self.check(tool_name, argument) == Permission::Allowed
}
pub fn is_forbidden(&self, tool_name: &str, argument: Option<&str>) -> bool {
self.check(tool_name, argument) == Permission::Forbidden
}
pub fn requires_approval(&self, tool_name: &str, argument: Option<&str>) -> bool {
self.check(tool_name, argument) == Permission::RequiresApproval
}
pub fn narrow_with(&self, client: &AutonomyGrant) -> AutonomyGrant {
let mut result = self.clone();
for pattern in &client.forbidden {
if !result.forbidden.iter().any(|p| p.same_as(pattern)) {
result.forbidden.push(pattern.clone());
}
}
for pattern in &client.require_approval {
if !result.require_approval.iter().any(|p| p.same_as(pattern)) {
result.require_approval.push(pattern.clone());
}
}
result
}
pub fn forbidden_patterns(&self) -> &[ToolPattern] {
&self.forbidden
}
pub fn allowed_patterns(&self) -> &[ToolPattern] {
&self.auto_approve
}
fn matches_any(
&self,
patterns: &[ToolPattern],
tool_name: &str,
argument: Option<&str>,
) -> bool {
patterns.iter().any(|p| p.matches(tool_name, argument))
}
}
impl ToolPattern {
pub fn matches(&self, tool_name: &str, argument: Option<&str>) -> bool {
match self {
Self::Read(glob) => {
matches!(tool_name, "read_file" | "read")
&& argument.map_or(false, |a| glob_matches(glob, a))
},
Self::Write(glob) => {
matches!(tool_name, "write_file" | "edit_file" | "write" | "edit")
&& argument.map_or(false, |a| glob_matches(glob, a))
},
Self::Bash(glob) => {
matches!(tool_name, "bash" | "shell")
&& argument.map_or(false, |a| glob_matches(glob, a))
},
Self::Tool(name) => glob_matches(name, tool_name),
}
}
pub fn same_as(&self, other: &ToolPattern) -> bool {
match (self, other) {
(Self::Read(a), Self::Read(b)) => a == b,
(Self::Write(a), Self::Write(b)) => a == b,
(Self::Bash(a), Self::Bash(b)) => a == b,
(Self::Tool(a), Self::Tool(b)) => a == b,
_ => false,
}
}
}
fn glob_matches(pattern: &str, text: &str) -> bool {
if pattern == "**/*" || pattern == "*" {
return true;
}
if !pattern.contains('*') {
return pattern == text;
}
if pattern.contains("**") {
return glob_matches_recursive(pattern, text);
}
let parts: Vec<&str> = pattern.split('*').collect();
if parts.is_empty() {
return true;
}
let mut pos = 0;
if !pattern.starts_with('*') {
if !text.starts_with(parts[0]) {
return false;
}
pos = parts[0].len();
}
for (i, part) in parts.iter().enumerate() {
if part.is_empty() {
continue;
}
if i == 0 && !pattern.starts_with('*') {
continue; }
if let Some(idx) = text[pos..].find(part) {
pos += idx + part.len();
} else {
return false;
}
}
if !pattern.ends_with('*') {
if let Some(last) = parts.last() {
if !last.is_empty() {
return text.ends_with(last);
}
}
}
true
}
fn glob_matches_recursive(pattern: &str, text: &str) -> bool {
let parts: Vec<&str> = pattern.split("**").collect();
if parts.len() == 1 {
return glob_matches(parts[0], text);
}
let prefix = parts[0].trim_end_matches('/');
if !prefix.is_empty() && !text.starts_with(prefix) {
return false;
}
let suffix = parts.last().unwrap_or(&"").trim_start_matches('/');
if !suffix.is_empty() {
if suffix.contains('*') {
for i in 0..=text.len() {
if glob_matches(suffix, &text[i..]) {
return true;
}
}
return false;
}
return text.ends_with(suffix);
}
true
}
#[derive(Debug, Default)]
pub struct AutonomyGrantBuilder {
auto_approve: Vec<ToolPattern>,
require_approval: Vec<ToolPattern>,
forbidden: Vec<ToolPattern>,
}
impl AutonomyGrantBuilder {
pub fn allow(mut self, pattern: ToolPattern) -> Self {
self.auto_approve.push(pattern);
self
}
pub fn require_approval(mut self, pattern: ToolPattern) -> Self {
self.require_approval.push(pattern);
self
}
pub fn forbid(mut self, pattern: ToolPattern) -> Self {
self.forbidden.push(pattern);
self
}
pub fn build(self) -> AutonomyGrant {
AutonomyGrant {
auto_approve: self.auto_approve,
require_approval: self.require_approval,
forbidden: self.forbidden,
}
}
}