Skip to main content

ai_agent/tools/
bash.rs

1use crate::types::*;
2use std::process::Command;
3
4pub struct BashTool;
5
6impl BashTool {
7    pub fn new() -> Self {
8        Self
9    }
10
11    pub fn name(&self) -> &str {
12        "Bash"
13    }
14
15    pub fn description(&self) -> &str {
16        "Execute a shell command and return its output"
17    }
18
19    pub fn input_schema(&self) -> ToolInputSchema {
20        ToolInputSchema {
21            schema_type: "object".to_string(),
22            properties: serde_json::json!({
23                "command": { "type": "string", "description": "Shell command to execute" },
24                "description": { "type": "string", "description": "What this command does" }
25            }),
26            required: Some(vec!["command".to_string()]),
27        }
28    }
29
30    pub async fn execute(
31        &self,
32        input: serde_json::Value,
33        context: &ToolContext,
34    ) -> Result<ToolResult, crate::error::AgentError> {
35        let command = input["command"]
36            .as_str()
37            .ok_or_else(|| crate::error::AgentError::Tool("command required".to_string()))?
38            .to_string();
39
40        let cwd = context.cwd.clone();
41        let output = tokio::task::spawn_blocking(move || {
42            let mut cmd = Command::new("sh");
43            cmd.arg("-c").arg(&command);
44            if !cwd.is_empty() {
45                cmd.current_dir(&cwd);
46            }
47            cmd.output()
48        })
49        .await
50        .map_err(|e| crate::error::AgentError::Tool(e.to_string()))?
51        .map_err(|e| crate::error::AgentError::Tool(e.to_string()))?;
52
53        let stdout = String::from_utf8_lossy(&output.stdout);
54        let stderr = String::from_utf8_lossy(&output.stderr);
55
56        let content = if !stdout.is_empty() {
57            stdout.to_string()
58        } else {
59            stderr.to_string()
60        };
61
62        let is_error = !output.status.success();
63
64        Ok(ToolResult {
65            result_type: "tool_result".to_string(),
66            tool_use_id: "".to_string(),
67            content,
68            is_error: Some(is_error),
69        })
70    }
71}
72
73#[cfg(test)]
74mod tests {
75    use super::*;
76
77    #[tokio::test]
78    async fn test_bash_tool() {
79        let tool = BashTool::new();
80        let result = tool
81            .execute(
82                serde_json::json!({"command": "echo hello"}),
83                &ToolContext {
84                    cwd: "/tmp".to_string(),
85                    abort_signal: None,
86                },
87            )
88            .await;
89        assert!(result.is_ok());
90    }
91}