ricecoder_permissions/
agent.rs

1//! Agent execution with permission checking
2//!
3//! This module provides integration between the permissions system and agent execution.
4//! It handles permission checking, prompting, and audit logging for tool execution.
5
6use crate::audit::AuditLogger;
7use crate::error::Result;
8use crate::permission::{PermissionChecker, PermissionConfig, PermissionDecision};
9use crate::prompt::{PermissionPrompt, PromptResult};
10use std::sync::Arc;
11
12/// Result of agent tool execution with permission decision
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum AgentExecutionResult {
15    /// Tool was allowed to execute
16    Allowed,
17    /// Tool execution was denied
18    Denied,
19    /// User was prompted and approved
20    Approved,
21    /// User was prompted and denied
22    UserDenied,
23}
24
25impl std::fmt::Display for AgentExecutionResult {
26    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27        match self {
28            AgentExecutionResult::Allowed => write!(f, "allowed"),
29            AgentExecutionResult::Denied => write!(f, "denied"),
30            AgentExecutionResult::Approved => write!(f, "approved"),
31            AgentExecutionResult::UserDenied => write!(f, "user_denied"),
32        }
33    }
34}
35
36/// Agent execution context with permission checking
37pub struct AgentExecutor {
38    /// Permission configuration
39    config: Arc<PermissionConfig>,
40    /// Audit logger for recording decisions
41    audit_logger: Arc<AuditLogger>,
42    /// Optional agent name for per-agent overrides
43    agent_name: Option<String>,
44}
45
46impl AgentExecutor {
47    /// Create a new agent executor
48    pub fn new(config: Arc<PermissionConfig>, audit_logger: Arc<AuditLogger>) -> Self {
49        Self {
50            config,
51            audit_logger,
52            agent_name: None,
53        }
54    }
55
56    /// Create a new agent executor with a specific agent name
57    pub fn with_agent(
58        config: Arc<PermissionConfig>,
59        audit_logger: Arc<AuditLogger>,
60        agent_name: String,
61    ) -> Self {
62        Self {
63            config,
64            audit_logger,
65            agent_name: Some(agent_name),
66        }
67    }
68
69    /// Check permission and execute tool with permission handling
70    ///
71    /// # Arguments
72    /// * `tool_name` - Name of the tool to execute
73    /// * `tool_description` - Optional description of the tool
74    /// * `action_description` - Optional description of what the tool will do
75    /// * `execute_fn` - Closure that executes the tool
76    ///
77    /// # Returns
78    /// Result containing the execution result and tool output
79    pub fn execute_with_permission<F, T>(
80        &self,
81        tool_name: &str,
82        tool_description: Option<&str>,
83        action_description: Option<&str>,
84        execute_fn: F,
85    ) -> Result<(AgentExecutionResult, Option<T>)>
86    where
87        F: FnOnce() -> Result<T>,
88    {
89        // Check permission for this tool
90        let decision = self.check_permission(tool_name)?;
91
92        match decision {
93            PermissionDecision::Allow => {
94                // Log the execution
95                self.audit_logger
96                    .log_execution(tool_name.to_string(), self.agent_name.clone(), None)
97                    .map_err(crate::error::Error::Internal)?;
98
99                // Execute the tool
100                let result = execute_fn()?;
101                Ok((AgentExecutionResult::Allowed, Some(result)))
102            }
103            PermissionDecision::Ask => {
104                // Show permission prompt
105                let prompt = PermissionPrompt::new(tool_name.to_string());
106                let prompt = if let Some(desc) = tool_description {
107                    prompt.with_description(desc.to_string())
108                } else {
109                    prompt
110                };
111                let prompt = if let Some(action) = action_description {
112                    prompt.with_action(action.to_string())
113                } else {
114                    prompt
115                };
116
117                // Log the prompt
118                self.audit_logger
119                    .log_prompt(tool_name.to_string(), self.agent_name.clone(), None)
120                    .map_err(crate::error::Error::Internal)?;
121
122                // Collect user decision
123                let prompt_result = prompt
124                    .execute()
125                    .map_err(|e| crate::error::Error::PromptError(e.to_string()))?;
126
127                match prompt_result {
128                    PromptResult::Approved => {
129                        // Log the execution
130                        self.audit_logger
131                            .log_execution(
132                                tool_name.to_string(),
133                                self.agent_name.clone(),
134                                Some("User approved via prompt".to_string()),
135                            )
136                            .map_err(crate::error::Error::Internal)?;
137
138                        // Execute the tool
139                        let result = execute_fn()?;
140                        Ok((AgentExecutionResult::Approved, Some(result)))
141                    }
142                    PromptResult::Denied => {
143                        // Log the denial
144                        self.audit_logger
145                            .log_denial(
146                                tool_name.to_string(),
147                                self.agent_name.clone(),
148                                Some("User denied via prompt".to_string()),
149                            )
150                            .map_err(crate::error::Error::Internal)?;
151
152                        Ok((AgentExecutionResult::UserDenied, None))
153                    }
154                }
155            }
156            PermissionDecision::Deny => {
157                // Log the denial
158                self.audit_logger
159                    .log_denial(
160                        tool_name.to_string(),
161                        self.agent_name.clone(),
162                        Some("Permission denied".to_string()),
163                    )
164                    .map_err(crate::error::Error::Internal)?;
165
166                Ok((AgentExecutionResult::Denied, None))
167            }
168        }
169    }
170
171    /// Check permission for a tool without executing it
172    pub fn check_permission(&self, tool_name: &str) -> Result<PermissionDecision> {
173        let permissions = self.config.get_permissions_for_tool(tool_name)?;
174        let default_level = self.config.default_permission_level();
175
176        let decision = PermissionChecker::check_permission(
177            &permissions,
178            self.agent_name.as_deref(),
179            default_level,
180        )?;
181
182        Ok(decision)
183    }
184
185    /// Get the agent name
186    pub fn agent_name(&self) -> Option<&str> {
187        self.agent_name.as_deref()
188    }
189
190    /// Get the audit logger
191    pub fn audit_logger(&self) -> Arc<AuditLogger> {
192        Arc::clone(&self.audit_logger)
193    }
194}
195
196#[cfg(test)]
197mod tests {
198    use super::*;
199    use crate::permission::{PermissionLevel, ToolPermission};
200
201    #[test]
202    fn test_agent_executor_creation() {
203        let config = Arc::new(PermissionConfig::new());
204        let logger = Arc::new(AuditLogger::new());
205        let executor = AgentExecutor::new(config, logger);
206
207        assert_eq!(executor.agent_name(), None);
208    }
209
210    #[test]
211    fn test_agent_executor_with_agent_name() {
212        let config = Arc::new(PermissionConfig::new());
213        let logger = Arc::new(AuditLogger::new());
214        let executor = AgentExecutor::with_agent(config, logger, "agent1".to_string());
215
216        assert_eq!(executor.agent_name(), Some("agent1"));
217    }
218
219    #[test]
220    fn test_check_permission_allow() {
221        let mut config = PermissionConfig::new();
222        config.add_permission(ToolPermission::new(
223            "test_tool".to_string(),
224            PermissionLevel::Allow,
225        ));
226
227        let config = Arc::new(config);
228        let logger = Arc::new(AuditLogger::new());
229        let executor = AgentExecutor::new(config, logger);
230
231        let decision = executor.check_permission("test_tool").unwrap();
232        assert_eq!(decision, PermissionDecision::Allow);
233    }
234
235    #[test]
236    fn test_check_permission_deny() {
237        let mut config = PermissionConfig::new();
238        config.add_permission(ToolPermission::new(
239            "test_tool".to_string(),
240            PermissionLevel::Deny,
241        ));
242
243        let config = Arc::new(config);
244        let logger = Arc::new(AuditLogger::new());
245        let executor = AgentExecutor::new(config, logger);
246
247        let decision = executor.check_permission("test_tool").unwrap();
248        assert_eq!(decision, PermissionDecision::Deny);
249    }
250
251    #[test]
252    fn test_execute_with_permission_allowed() {
253        let mut config = PermissionConfig::new();
254        config.add_permission(ToolPermission::new(
255            "test_tool".to_string(),
256            PermissionLevel::Allow,
257        ));
258
259        let config = Arc::new(config);
260        let logger = Arc::new(AuditLogger::new());
261        let executor = AgentExecutor::new(config, logger.clone());
262
263        let (result, output) = executor
264            .execute_with_permission("test_tool", None, None, || Ok(42))
265            .unwrap();
266
267        assert_eq!(result, AgentExecutionResult::Allowed);
268        assert_eq!(output, Some(42));
269
270        // Check that execution was logged
271        let entries = logger.entries().unwrap();
272        assert_eq!(entries.len(), 1);
273    }
274
275    #[test]
276    fn test_execute_with_permission_denied() {
277        let mut config = PermissionConfig::new();
278        config.add_permission(ToolPermission::new(
279            "test_tool".to_string(),
280            PermissionLevel::Deny,
281        ));
282
283        let config = Arc::new(config);
284        let logger = Arc::new(AuditLogger::new());
285        let executor = AgentExecutor::new(config, logger.clone());
286
287        let (result, output) = executor
288            .execute_with_permission("test_tool", None, None, || Ok(42))
289            .unwrap();
290
291        assert_eq!(result, AgentExecutionResult::Denied);
292        assert_eq!(output, None);
293
294        // Check that denial was logged
295        let entries = logger.entries().unwrap();
296        assert_eq!(entries.len(), 1);
297    }
298
299    #[test]
300    fn test_execute_with_permission_per_agent_override() {
301        let mut config = PermissionConfig::new();
302        config.add_permission(ToolPermission::new(
303            "test_tool".to_string(),
304            PermissionLevel::Allow,
305        ));
306        config.add_permission(ToolPermission::with_agent(
307            "test_tool".to_string(),
308            PermissionLevel::Deny,
309            "agent1".to_string(),
310        ));
311
312        let config = Arc::new(config);
313        let logger = Arc::new(AuditLogger::new());
314
315        // For agent1, should be denied
316        let executor =
317            AgentExecutor::with_agent(config.clone(), logger.clone(), "agent1".to_string());
318        let (result, _) = executor
319            .execute_with_permission("test_tool", None, None, || Ok(42))
320            .unwrap();
321        assert_eq!(result, AgentExecutionResult::Denied);
322
323        // For agent2, should be allowed
324        let executor = AgentExecutor::with_agent(config, logger, "agent2".to_string());
325        let (result, _) = executor
326            .execute_with_permission("test_tool", None, None, || Ok(42))
327            .unwrap();
328        assert_eq!(result, AgentExecutionResult::Allowed);
329    }
330}