
<img width="639" alt="Screenshot 2025-03-19 at 4 56 47 PM" src="https://github.com/user-attachments/assets/b997f04d-4aff-4070-87b1-ffdb61019bd1" /> Release Notes: - N/A --------- Co-authored-by: Agus Zubiaga <hi@aguz.me>
89 lines
2.9 KiB
Rust
89 lines
2.9 KiB
Rust
use anyhow::{anyhow, Context as _, Result};
|
|
use assistant_tool::{ActionLog, Tool};
|
|
use gpui::{App, Entity, Task};
|
|
use language_model::LanguageModelRequestMessage;
|
|
use project::Project;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::sync::Arc;
|
|
use util::command::new_smol_command;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
pub struct BashToolInput {
|
|
/// The bash command to execute as a one-liner.
|
|
command: String,
|
|
/// Working directory for the command. This must be one of the root directories of the project.
|
|
cd: String,
|
|
}
|
|
|
|
pub struct BashTool;
|
|
|
|
impl Tool for BashTool {
|
|
fn name(&self) -> String {
|
|
"bash".to_string()
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
include_str!("./bash_tool/description.md").to_string()
|
|
}
|
|
|
|
fn input_schema(&self) -> serde_json::Value {
|
|
let schema = schemars::schema_for!(BashToolInput);
|
|
serde_json::to_value(&schema).unwrap()
|
|
}
|
|
|
|
fn ui_text(&self, input: &serde_json::Value) -> String {
|
|
match serde_json::from_value::<BashToolInput>(input.clone()) {
|
|
Ok(input) => format!("`$ {}`", input.command),
|
|
Err(_) => "Run bash command".to_string(),
|
|
}
|
|
}
|
|
|
|
fn run(
|
|
self: Arc<Self>,
|
|
input: serde_json::Value,
|
|
_messages: &[LanguageModelRequestMessage],
|
|
project: Entity<Project>,
|
|
_action_log: Entity<ActionLog>,
|
|
cx: &mut App,
|
|
) -> Task<Result<String>> {
|
|
let input: BashToolInput = match serde_json::from_value(input) {
|
|
Ok(input) => input,
|
|
Err(err) => return Task::ready(Err(anyhow!(err))),
|
|
};
|
|
|
|
let Some(worktree) = project.read(cx).worktree_for_root_name(&input.cd, cx) else {
|
|
return Task::ready(Err(anyhow!("Working directory not found in the project")));
|
|
};
|
|
let working_directory = worktree.read(cx).abs_path();
|
|
|
|
cx.spawn(async move |_| {
|
|
// Add 2>&1 to merge stderr into stdout for proper interleaving.
|
|
let command = format!("({}) 2>&1", input.command);
|
|
|
|
let output = new_smol_command("bash")
|
|
.arg("-c")
|
|
.arg(&command)
|
|
.current_dir(working_directory)
|
|
.output()
|
|
.await
|
|
.context("Failed to execute bash command")?;
|
|
|
|
let output_string = String::from_utf8_lossy(&output.stdout).to_string();
|
|
|
|
if output.status.success() {
|
|
if output_string.is_empty() {
|
|
Ok("Command executed successfully.".to_string())
|
|
} else {
|
|
Ok(output_string)
|
|
}
|
|
} else {
|
|
Ok(format!(
|
|
"Command failed with exit code {}\n{}",
|
|
output.status.code().unwrap_or(-1),
|
|
&output_string
|
|
))
|
|
}
|
|
})
|
|
}
|
|
}
|