bash_tool.rs

 1use anyhow::{anyhow, Result};
 2use assistant_tool::Tool;
 3use gpui::{App, Entity, Task};
 4use language_model::LanguageModelRequestMessage;
 5use project::Project;
 6use schemars::JsonSchema;
 7use serde::{Deserialize, Serialize};
 8use std::sync::Arc;
 9
10#[derive(Debug, Serialize, Deserialize, JsonSchema)]
11pub struct BashToolInput {
12    /// The bash command to execute as a one-liner.
13    command: String,
14}
15
16pub struct BashTool;
17
18impl Tool for BashTool {
19    fn name(&self) -> String {
20        "bash".to_string()
21    }
22
23    fn description(&self) -> String {
24        include_str!("./bash_tool/description.md").to_string()
25    }
26
27    fn input_schema(&self) -> serde_json::Value {
28        let schema = schemars::schema_for!(BashToolInput);
29        serde_json::to_value(&schema).unwrap()
30    }
31
32    fn run(
33        self: Arc<Self>,
34        input: serde_json::Value,
35        _messages: &[LanguageModelRequestMessage],
36        _project: Entity<Project>,
37        cx: &mut App,
38    ) -> Task<Result<String>> {
39        let input: BashToolInput = match serde_json::from_value(input) {
40            Ok(input) => input,
41            Err(err) => return Task::ready(Err(anyhow!(err))),
42        };
43
44        cx.spawn(|_| async move {
45            // Add 2>&1 to merge stderr into stdout for proper interleaving
46            let command = format!("{} 2>&1", input.command);
47
48            // Spawn a blocking task to execute the command
49            let output = futures::executor::block_on(async {
50                std::process::Command::new("bash")
51                    .arg("-c")
52                    .arg(&command)
53                    .output()
54                    .map_err(|err| anyhow!("Failed to execute bash command: {}", err))
55            })?;
56
57            let output_string = String::from_utf8_lossy(&output.stdout).to_string();
58
59            if output.status.success() {
60                Ok(output_string)
61            } else {
62                Ok(format!(
63                    "Command failed with exit code {}\n{}",
64                    output.status.code().unwrap_or(-1),
65                    &output_string
66                ))
67            }
68        })
69    }
70}