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