use serde::{Deserialize, Serialize};
use std::process::Stdio;
use tokio::process::Command;
use tokio::time::{Duration, timeout};
use crate::common::Result;
const MAX_STDOUT_BYTES: usize = 10_000;
const MAX_STDERR_BYTES: usize = 5_000;
#[derive(Debug, Deserialize)]
pub struct BashInput {
pub command: String,
#[serde(default = "default_timeout")]
pub timeout_secs: u64,
}
fn default_timeout() -> u64 {
120
}
#[derive(Debug, Serialize)]
pub struct BashOutput {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
}
pub fn definition() -> serde_json::Value {
serde_json::json!({
"type": "function",
"function": {
"name": "bash",
"description": "Execute a bash command. Use for running shell commands, git operations, builds, tests, etc.",
"parameters": {
"type": "object",
"properties": {
"command": {
"type": "string",
"description": "The bash command to execute"
},
"timeout_secs": {
"type": "integer",
"description": "Timeout in seconds (default: 120)"
}
},
"required": ["command"]
}
}
})
}
pub async fn execute(input: BashInput, working_dir: &str) -> Result<String> {
let duration = Duration::from_secs(input.timeout_secs);
let result = timeout(duration, async {
Command::new("bash")
.arg("-c")
.arg(&input.command)
.current_dir(working_dir)
.stdin(Stdio::null())
.output()
.await
})
.await;
match result {
Ok(Ok(output)) => {
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
let exit_code = output.status.code().unwrap_or(-1);
let result = BashOutput {
exit_code,
stdout: truncate_output(&stdout, MAX_STDOUT_BYTES),
stderr: truncate_output(&stderr, MAX_STDERR_BYTES),
};
Ok(serde_json::to_string_pretty(&result)?)
}
Ok(Err(e)) => Err(crate::common::AgentError::Command(format!(
"Failed to execute command: {}",
e
))),
Err(_) => Err(crate::common::AgentError::Timeout(input.timeout_secs)),
}
}
fn truncate_output(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!(
"{}...\n[truncated, showing first {max_len} of {} bytes]",
crate::util::truncate_bytes(s, max_len),
s.len()
)
}
}