
This PR introduces the "Reject All" and "Accept All" buttons in the panel's edit bar, which appears as soon as the agent starts editing a file. I'm also adding here a new method to the thread called `has_pending_edit_tool_uses`, which is a more specific way of knowing, in comparison to the `is_generating` method, whether or not the reject/accept all actions can be triggered. Previously, without this new method, you'd be waiting for the whole generation to end (e.g., the agent would be generating markdown with things like change summary) to be able to click those buttons, when the edit was already there, ready for you. It always felt like waiting for the whole thing was unnecessary when you really wanted to just wait for the _edits_ to be done, as so to avoid any potential conflicting state. <img src="https://github.com/user-attachments/assets/0927f3a6-c9ee-46ae-8f7b-97157d39a7b5" width="500"/> --- Release Notes: - agent: Added ability to reject and accept all changes from the agent panel. --------- Co-authored-by: Agus Zubiaga <hi@aguz.me>
131 lines
4.6 KiB
Rust
131 lines
4.6 KiB
Rust
use crate::schema::json_schema_for;
|
|
use anyhow::{Context as _, Result, anyhow};
|
|
use assistant_tool::{ActionLog, Tool, ToolResult};
|
|
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
|
|
use language_model::{LanguageModel, LanguageModelRequest, LanguageModelToolSchemaFormat};
|
|
use project::Project;
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
use std::{path::Path, sync::Arc};
|
|
use ui::IconName;
|
|
use util::markdown::MarkdownInlineCode;
|
|
|
|
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
|
pub struct MovePathToolInput {
|
|
/// The source path of the file or directory to move/rename.
|
|
///
|
|
/// <example>
|
|
/// If the project has the following files:
|
|
///
|
|
/// - directory1/a/something.txt
|
|
/// - directory2/a/things.txt
|
|
/// - directory3/a/other.txt
|
|
///
|
|
/// You can move the first file by providing a source_path of "directory1/a/something.txt"
|
|
/// </example>
|
|
pub source_path: String,
|
|
|
|
/// The destination path where the file or directory should be moved/renamed to.
|
|
/// If the paths are the same except for the filename, then this will be a rename.
|
|
///
|
|
/// <example>
|
|
/// To move "directory1/a/something.txt" to "directory2/b/renamed.txt",
|
|
/// provide a destination_path of "directory2/b/renamed.txt"
|
|
/// </example>
|
|
pub destination_path: String,
|
|
}
|
|
|
|
pub struct MovePathTool;
|
|
|
|
impl Tool for MovePathTool {
|
|
fn name(&self) -> String {
|
|
"move_path".into()
|
|
}
|
|
|
|
fn needs_confirmation(&self, _: &serde_json::Value, _: &App) -> bool {
|
|
false
|
|
}
|
|
|
|
fn may_perform_edits(&self) -> bool {
|
|
true
|
|
}
|
|
|
|
fn description(&self) -> String {
|
|
include_str!("./move_path_tool/description.md").into()
|
|
}
|
|
|
|
fn icon(&self) -> IconName {
|
|
IconName::ArrowRightLeft
|
|
}
|
|
|
|
fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> Result<serde_json::Value> {
|
|
json_schema_for::<MovePathToolInput>(format)
|
|
}
|
|
|
|
fn ui_text(&self, input: &serde_json::Value) -> String {
|
|
match serde_json::from_value::<MovePathToolInput>(input.clone()) {
|
|
Ok(input) => {
|
|
let src = MarkdownInlineCode(&input.source_path);
|
|
let dest = MarkdownInlineCode(&input.destination_path);
|
|
let src_path = Path::new(&input.source_path);
|
|
let dest_path = Path::new(&input.destination_path);
|
|
|
|
match dest_path
|
|
.file_name()
|
|
.and_then(|os_str| os_str.to_os_string().into_string().ok())
|
|
{
|
|
Some(filename) if src_path.parent() == dest_path.parent() => {
|
|
let filename = MarkdownInlineCode(&filename);
|
|
format!("Rename {src} to {filename}")
|
|
}
|
|
_ => {
|
|
format!("Move {src} to {dest}")
|
|
}
|
|
}
|
|
}
|
|
Err(_) => "Move path".to_string(),
|
|
}
|
|
}
|
|
|
|
fn run(
|
|
self: Arc<Self>,
|
|
input: serde_json::Value,
|
|
_request: Arc<LanguageModelRequest>,
|
|
project: Entity<Project>,
|
|
_action_log: Entity<ActionLog>,
|
|
_model: Arc<dyn LanguageModel>,
|
|
_window: Option<AnyWindowHandle>,
|
|
cx: &mut App,
|
|
) -> ToolResult {
|
|
let input = match serde_json::from_value::<MovePathToolInput>(input) {
|
|
Ok(input) => input,
|
|
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
|
|
};
|
|
let rename_task = project.update(cx, |project, cx| {
|
|
match project
|
|
.find_project_path(&input.source_path, cx)
|
|
.and_then(|project_path| project.entry_for_path(&project_path, cx))
|
|
{
|
|
Some(entity) => match project.find_project_path(&input.destination_path, cx) {
|
|
Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
|
|
None => Task::ready(Err(anyhow!(
|
|
"Destination path {} was outside the project.",
|
|
input.destination_path
|
|
))),
|
|
},
|
|
None => Task::ready(Err(anyhow!(
|
|
"Source path {} was not found in the project.",
|
|
input.source_path
|
|
))),
|
|
}
|
|
});
|
|
|
|
cx.background_spawn(async move {
|
|
let _ = rename_task.await.with_context(|| {
|
|
format!("Moving {} to {}", input.source_path, input.destination_path)
|
|
})?;
|
|
Ok(format!("Moved {} to {}", input.source_path, input.destination_path).into())
|
|
})
|
|
.into()
|
|
}
|
|
}
|