telos_agent/tools/
approval.rs1use std::path::PathBuf;
8use std::sync::Arc;
9
10use async_trait::async_trait;
11use serde_json::Value;
12
13use crate::model::message::Message;
14
15#[derive(Debug, Clone)]
17pub struct ApprovalRequest {
18 pub tool_call_id: String,
20 pub tool_name: String,
22 pub arguments: Value,
24 pub cwd: PathBuf,
26 pub messages: Arc<Vec<Message>>,
28 pub reason: String,
30}
31
32#[derive(Debug, Clone, PartialEq, Eq)]
34pub enum ApprovalDecision {
35 Allow,
37 Deny { reason: String },
39 Modify { arguments: Value },
41}
42
43#[async_trait]
45pub trait ApprovalHandler: Send + Sync + std::fmt::Debug {
46 async fn ask(&self, request: ApprovalRequest) -> ApprovalDecision;
48}
49
50#[derive(Debug, Clone, Copy, Default)]
54pub struct AutoDenyHandler;
55
56#[async_trait]
57impl ApprovalHandler for AutoDenyHandler {
58 async fn ask(&self, _request: ApprovalRequest) -> ApprovalDecision {
59 ApprovalDecision::Deny { reason: "no approval handler configured".into() }
60 }
61}
62
63#[derive(Debug, Clone)]
65pub struct FixedDecisionHandler {
66 pub decision: ApprovalDecision,
67}
68
69#[async_trait]
70impl ApprovalHandler for FixedDecisionHandler {
71 async fn ask(&self, _request: ApprovalRequest) -> ApprovalDecision {
72 self.decision.clone()
73 }
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use serde_json::json;
80
81 #[tokio::test]
82 async fn auto_deny_handler_always_denies() {
83 let handler = AutoDenyHandler;
84 let decision = handler
85 .ask(ApprovalRequest {
86 tool_call_id: "call-1".into(),
87 tool_name: "Bash".into(),
88 arguments: json!({"command": "rm -rf /"}),
89 cwd: PathBuf::from("/tmp"),
90 messages: Arc::new(vec![]),
91 reason: "destructive command".into(),
92 })
93 .await;
94 assert!(matches!(decision, ApprovalDecision::Deny { .. }));
95 }
96
97 #[tokio::test]
98 async fn fixed_handler_returns_configured_decision() {
99 let handler = FixedDecisionHandler { decision: ApprovalDecision::Allow };
100 let decision = handler
101 .ask(ApprovalRequest {
102 tool_call_id: "call-1".into(),
103 tool_name: "Read".into(),
104 arguments: json!({"file_path": "/etc/passwd"}),
105 cwd: PathBuf::from("/tmp"),
106 messages: Arc::new(vec![]),
107 reason: "test".into(),
108 })
109 .await;
110 assert_eq!(decision, ApprovalDecision::Allow);
111 }
112}