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
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}