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}