Skip to main content

deepseek/agent/builtin_tools/
bash.rs

1use std::time::Duration;
2
3use async_trait::async_trait;
4use serde_json::{json, Value};
5
6use crate::agent::tool::{Tool, ToolDefinition};
7
8const DEFAULT_TIMEOUT_SECS: u64 = 120;
9
10pub struct BashTool;
11
12#[async_trait]
13impl Tool for BashTool {
14    fn name(&self) -> &str {
15        "Bash"
16    }
17
18    fn definition(&self) -> ToolDefinition {
19        ToolDefinition {
20            name: self.name().to_string(),
21            description: "Run a shell command via `/bin/sh -c`. Captures stdout+stderr. \
22                          Default timeout 120s."
23                .into(),
24            parameters: json!({
25                "type": "object",
26                "properties": {
27                    "command":      { "type": "string" },
28                    "timeout_secs": { "type": "integer", "minimum": 1, "maximum": 600 }
29                },
30                "required": ["command"]
31            }),
32        }
33    }
34
35    async fn call_json(&self, args: Value) -> Result<String, String> {
36        let command = args
37            .get("command")
38            .and_then(Value::as_str)
39            .ok_or_else(|| "Bash: missing string `command`".to_string())?;
40        let timeout = args
41            .get("timeout_secs")
42            .and_then(Value::as_u64)
43            .unwrap_or(DEFAULT_TIMEOUT_SECS);
44
45        let mut cmd = tokio::process::Command::new("/bin/sh");
46        cmd.arg("-c").arg(command);
47        cmd.stdout(std::process::Stdio::piped())
48            .stderr(std::process::Stdio::piped());
49
50        let child_fut = cmd.output();
51        let out = match tokio::time::timeout(Duration::from_secs(timeout), child_fut).await {
52            Ok(Ok(out)) => out,
53            Ok(Err(e)) => return Err(format!("Bash: spawn error: {e}")),
54            Err(_) => return Err(format!("Bash: timed out after {timeout}s")),
55        };
56        let stdout = String::from_utf8_lossy(&out.stdout);
57        let stderr = String::from_utf8_lossy(&out.stderr);
58        let status = out.status.code().unwrap_or(-1);
59        Ok(format!(
60            "exit={status}\n--- stdout ---\n{stdout}--- stderr ---\n{stderr}"
61        ))
62    }
63}