1use anyhow::Result;
2use async_trait::async_trait;
3use serde_json::Value;
4use std::collections::HashMap;
5use std::path::PathBuf;
6use std::sync::Arc;
7
8pub struct ToolContext<'a> {
10 pub cwd: PathBuf,
11 pub session_id: String,
12 pub approve_fn: &'a (dyn Fn(&str) -> bool + Send + Sync),
14 pub yolo: bool,
16 pub identity: Option<aegis_security::Identity>,
20 pub sandbox_enabled: bool,
25}
26
27impl ToolContext<'_> {
28 pub fn approve(&self, command: &str) -> bool {
30 self.yolo || (self.approve_fn)(command)
31 }
32
33 pub fn effective_identity(&self) -> aegis_security::Identity {
36 self.identity
37 .clone()
38 .unwrap_or(aegis_security::Identity::LocalOwner)
39 }
40}
41
42#[async_trait]
44pub trait Tool: Send + Sync {
45 fn name(&self) -> &str;
46 fn description(&self) -> &str;
47 fn parameters(&self) -> Value;
48 async fn execute(&self, args: Value, ctx: &ToolContext<'_>) -> Result<String>;
49}
50
51pub struct ToolRegistry {
53 tools: HashMap<String, Arc<dyn Tool>>,
54}
55
56impl ToolRegistry {
57 pub fn new() -> Self {
59 Self {
60 tools: HashMap::new(),
61 }
62 }
63
64 pub fn register(&mut self, tool: Arc<dyn Tool>) {
66 self.tools.insert(tool.name().to_string(), tool);
67 }
68
69 pub fn get(&self, name: &str) -> Option<&Arc<dyn Tool>> {
71 self.tools.get(name)
72 }
73
74 pub fn names(&self) -> Vec<String> {
76 self.tools.keys().cloned().collect()
77 }
78
79 pub fn to_openai_schema(&self) -> Value {
81 let arr: Vec<Value> = self
82 .tools
83 .values()
84 .map(|t| {
85 serde_json::json!({
86 "type": "function",
87 "function": {
88 "name": t.name(),
89 "description": t.description(),
90 "parameters": t.parameters(),
91 }
92 })
93 })
94 .collect();
95 Value::Array(arr)
96 }
97
98 pub fn tool_descriptions(&self) -> String {
100 let mut names: Vec<_> = self.tools.keys().collect();
101 names.sort();
102 names
103 .iter()
104 .map(|n| {
105 let t = &self.tools[n.as_str()];
106 format!("- {}: {}", t.name(), t.description())
107 })
108 .collect::<Vec<_>>()
109 .join("\n")
110 }
111}
112
113impl Default for ToolRegistry {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119#[cfg(test)]
120mod tests {
121 use super::*;
122
123 struct DummyTool;
124
125 #[async_trait]
126 impl Tool for DummyTool {
127 fn name(&self) -> &str { "dummy" }
128 fn description(&self) -> &str { "A test tool" }
129 fn parameters(&self) -> Value { serde_json::json!({"type": "object", "properties": {}}) }
130 async fn execute(&self, _args: Value, _ctx: &ToolContext<'_>) -> Result<String> {
131 Ok("ok".into())
132 }
133 }
134
135 #[test]
136 fn test_register_and_get() {
137 let mut reg = ToolRegistry::new();
138 reg.register(Arc::new(DummyTool));
139 assert!(reg.get("dummy").is_some());
140 assert!(reg.get("nonexistent").is_none());
141 }
142
143 #[test]
144 fn test_names() {
145 let mut reg = ToolRegistry::new();
146 reg.register(Arc::new(DummyTool));
147 let names = reg.names();
148 assert!(names.contains(&"dummy".to_string()));
149 }
150
151 #[test]
152 fn test_openai_schema() {
153 let mut reg = ToolRegistry::new();
154 reg.register(Arc::new(DummyTool));
155 let schema = reg.to_openai_schema();
156 let arr = schema.as_array().unwrap();
157 assert_eq!(arr.len(), 1);
158 assert_eq!(arr[0]["type"], "function");
159 assert_eq!(arr[0]["function"]["name"], "dummy");
160 }
161
162 #[test]
163 fn test_tool_descriptions() {
164 let mut reg = ToolRegistry::new();
165 reg.register(Arc::new(DummyTool));
166 let desc = reg.tool_descriptions();
167 assert!(desc.contains("dummy: A test tool"));
168 }
169}