deepseek/agent/builtin_tools/
bash.rs1use 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}