openclaw_agents/tools/
mod.rs1use std::collections::HashMap;
4use std::sync::Arc;
5
6use async_trait::async_trait;
7use serde::{Deserialize, Serialize};
8use thiserror::Error;
9
10use openclaw_providers::traits::Tool as ToolDefinition;
11
12#[derive(Error, Debug)]
14pub enum ToolError {
15 #[error("Tool not found: {0}")]
17 NotFound(String),
18
19 #[error("Invalid parameters: {0}")]
21 InvalidParams(String),
22
23 #[error("Execution failed: {0}")]
25 ExecutionFailed(String),
26
27 #[error("Tool timed out")]
29 Timeout,
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ToolResult {
35 pub success: bool,
37 pub content: String,
39 pub error: Option<String>,
41}
42
43impl ToolResult {
44 #[must_use]
46 pub fn success(content: impl Into<String>) -> Self {
47 Self {
48 success: true,
49 content: content.into(),
50 error: None,
51 }
52 }
53
54 #[must_use]
56 pub fn error(error: impl Into<String>) -> Self {
57 Self {
58 success: false,
59 content: String::new(),
60 error: Some(error.into()),
61 }
62 }
63}
64
65#[async_trait]
67pub trait Tool: Send + Sync {
68 fn name(&self) -> &str;
70
71 fn description(&self) -> &str;
73
74 fn input_schema(&self) -> serde_json::Value;
76
77 async fn execute(&self, params: serde_json::Value) -> Result<ToolResult, ToolError>;
79}
80
81pub struct ToolRegistry {
83 tools: HashMap<String, Arc<dyn Tool>>,
84}
85
86impl ToolRegistry {
87 #[must_use]
89 pub fn new() -> Self {
90 Self {
91 tools: HashMap::new(),
92 }
93 }
94
95 pub fn register(&mut self, tool: Arc<dyn Tool>) {
97 self.tools.insert(tool.name().to_string(), tool);
98 }
99
100 #[must_use]
102 pub fn get(&self, name: &str) -> Option<&Arc<dyn Tool>> {
103 self.tools.get(name)
104 }
105
106 #[must_use]
108 pub fn list(&self) -> Vec<&str> {
109 self.tools.keys().map(String::as_str).collect()
110 }
111
112 pub async fn execute(
118 &self,
119 name: &str,
120 params: serde_json::Value,
121 ) -> Result<ToolResult, ToolError> {
122 let tool = self
123 .tools
124 .get(name)
125 .ok_or_else(|| ToolError::NotFound(name.to_string()))?;
126 tool.execute(params).await
127 }
128
129 #[must_use]
131 pub fn as_tool_definitions(&self) -> Vec<ToolDefinition> {
132 self.tools
133 .values()
134 .map(|tool| ToolDefinition {
135 name: tool.name().to_string(),
136 description: tool.description().to_string(),
137 input_schema: tool.input_schema(),
138 })
139 .collect()
140 }
141}
142
143impl Default for ToolRegistry {
144 fn default() -> Self {
145 Self::new()
146 }
147}
148
149pub struct BashTool {
151 sandbox_config: crate::sandbox::SandboxConfig,
152}
153
154impl BashTool {
155 #[must_use]
157 pub fn new() -> Self {
158 Self {
159 sandbox_config: crate::sandbox::SandboxConfig::default(),
160 }
161 }
162
163 #[must_use]
165 pub const fn with_sandbox_config(config: crate::sandbox::SandboxConfig) -> Self {
166 Self {
167 sandbox_config: config,
168 }
169 }
170}
171
172impl Default for BashTool {
173 fn default() -> Self {
174 Self::new()
175 }
176}
177
178#[async_trait]
179impl Tool for BashTool {
180 fn name(&self) -> &'static str {
181 "bash"
182 }
183
184 fn description(&self) -> &'static str {
185 "Execute a bash command in a sandboxed environment"
186 }
187
188 fn input_schema(&self) -> serde_json::Value {
189 serde_json::json!({
190 "type": "object",
191 "properties": {
192 "command": {
193 "type": "string",
194 "description": "The bash command to execute"
195 }
196 },
197 "required": ["command"]
198 })
199 }
200
201 async fn execute(&self, params: serde_json::Value) -> Result<ToolResult, ToolError> {
202 let command = params["command"]
203 .as_str()
204 .ok_or_else(|| ToolError::InvalidParams("Missing 'command' parameter".to_string()))?;
205
206 let output =
208 crate::sandbox::execute_sandboxed("bash", &["-c", command], &self.sandbox_config)
209 .map_err(|e| ToolError::ExecutionFailed(e.to_string()))?;
210
211 if output.exit_code == 0 {
212 Ok(ToolResult::success(output.stdout))
213 } else {
214 let error_msg = if output.stderr.is_empty() {
215 format!("Command failed with exit code {}", output.exit_code)
216 } else {
217 output.stderr
218 };
219 Ok(ToolResult::error(error_msg))
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use super::*;
227
228 #[test]
229 fn test_tool_registry() {
230 let mut registry = ToolRegistry::new();
231 registry.register(Arc::new(BashTool::new()));
232
233 assert!(registry.get("bash").is_some());
234 assert!(registry.get("nonexistent").is_none());
235 }
236
237 #[test]
238 fn test_tool_definitions() {
239 let mut registry = ToolRegistry::new();
240 registry.register(Arc::new(BashTool::new()));
241
242 let defs = registry.as_tool_definitions();
243 assert_eq!(defs.len(), 1);
244 assert_eq!(defs[0].name, "bash");
245 }
246}