agent: Remove `edit_files` tool (#28041)

Agus Zubiaga created

Release Notes:

- agent: Remove `edit_files` tool  in favor of `find_replace`

Change summary

Cargo.lock                                                |   5 
crates/assistant_tools/Cargo.toml                         |   6 
crates/assistant_tools/src/assistant_tools.rs             |   4 
crates/assistant_tools/src/edit_files_tool.rs             | 559 -----
crates/assistant_tools/src/edit_files_tool/description.md |  11 
crates/assistant_tools/src/edit_files_tool/edit_action.rs | 967 ---------
crates/assistant_tools/src/edit_files_tool/edit_prompt.md | 134 -
crates/assistant_tools/src/edit_files_tool/log.rs         | 417 ---
8 files changed, 2,103 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -738,7 +738,6 @@ dependencies = [
  "assistant_tool",
  "chrono",
  "collections",
- "feature_flags",
  "futures 0.3.31",
  "gpui",
  "html_to_markdown",
@@ -746,18 +745,14 @@ dependencies = [
  "itertools 0.14.0",
  "language",
  "language_model",
- "log",
  "lsp",
  "open",
  "project",
  "rand 0.8.5",
  "regex",
- "release_channel",
  "schemars",
  "serde",
  "serde_json",
- "settings",
- "theme",
  "ui",
  "unindent",
  "util",

crates/assistant_tools/Cargo.toml 🔗

@@ -16,7 +16,6 @@ anyhow.workspace = true
 assistant_tool.workspace = true
 chrono.workspace = true
 collections.workspace = true
-feature_flags.workspace = true
 futures.workspace = true
 gpui.workspace = true
 html_to_markdown.workspace = true
@@ -24,19 +23,14 @@ http_client.workspace = true
 itertools.workspace = true
 language.workspace = true
 language_model.workspace = true
-log.workspace = true
 lsp.workspace = true
 project.workspace = true
 regex.workspace = true
-release_channel.workspace = true
 schemars.workspace = true
 serde.workspace = true
 serde_json.workspace = true
-settings.workspace = true
-theme.workspace = true
 ui.workspace = true
 util.workspace = true
-workspace.workspace = true
 worktree.workspace = true
 open = { workspace = true }
 workspace-hack.workspace = true

crates/assistant_tools/src/assistant_tools.rs 🔗

@@ -7,7 +7,6 @@ mod create_directory_tool;
 mod create_file_tool;
 mod delete_path_tool;
 mod diagnostics_tool;
-mod edit_files_tool;
 mod fetch_tool;
 mod find_replace_file_tool;
 mod list_directory_tool;
@@ -37,7 +36,6 @@ use crate::create_directory_tool::CreateDirectoryTool;
 use crate::create_file_tool::CreateFileTool;
 use crate::delete_path_tool::DeletePathTool;
 use crate::diagnostics_tool::DiagnosticsTool;
-use crate::edit_files_tool::EditFilesTool;
 use crate::fetch_tool::FetchTool;
 use crate::find_replace_file_tool::FindReplaceFileTool;
 use crate::list_directory_tool::ListDirectoryTool;
@@ -51,7 +49,6 @@ use crate::thinking_tool::ThinkingTool;
 
 pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
     assistant_tool::init(cx);
-    crate::edit_files_tool::log::init(cx);
 
     let registry = ToolRegistry::global(cx);
     registry.register_tool(BashTool);
@@ -64,7 +61,6 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
     registry.register_tool(SymbolInfoTool);
     registry.register_tool(MovePathTool);
     registry.register_tool(DiagnosticsTool);
-    registry.register_tool(EditFilesTool);
     registry.register_tool(ListDirectoryTool);
     registry.register_tool(NowTool);
     registry.register_tool(OpenTool);

crates/assistant_tools/src/edit_files_tool.rs 🔗

@@ -1,559 +0,0 @@
-mod edit_action;
-pub mod log;
-
-use crate::replace::{replace_exact, replace_with_flexible_indent};
-use crate::schema::json_schema_for;
-use anyhow::{Context, Result, anyhow};
-use assistant_tool::{ActionLog, Tool};
-use collections::HashSet;
-use edit_action::{EditAction, EditActionParser, edit_model_prompt};
-use futures::{SinkExt, StreamExt, channel::mpsc};
-use gpui::{App, AppContext, AsyncApp, Entity, Task};
-use language_model::{ConfiguredModel, LanguageModelToolSchemaFormat};
-use language_model::{
-    LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, MessageContent, Role,
-};
-use log::{EditToolLog, EditToolRequestId};
-use project::Project;
-use schemars::JsonSchema;
-use serde::{Deserialize, Serialize};
-use std::fmt::Write;
-use std::sync::Arc;
-use ui::IconName;
-use util::ResultExt;
-
-#[derive(Debug, Serialize, Deserialize, JsonSchema)]
-pub struct EditFilesToolInput {
-    /// High-level edit instructions. These will be interpreted by a smaller
-    /// model, so explain the changes you want that model to make and which
-    /// file paths need changing. The description should be concise and clear.
-    ///
-    /// WARNING: When specifying which file paths need changing, you MUST
-    /// start each path with one of the project's root directories.
-    ///
-    /// WARNING: NEVER include code blocks or snippets in edit instructions.
-    /// Only provide natural language descriptions of the changes needed! The tool will
-    /// reject any instructions that contain code blocks or snippets.
-    ///
-    /// The following examples assume we have two root directories in the project:
-    /// - root-1
-    /// - root-2
-    ///
-    /// <example>
-    /// If you want to introduce a new quit function to kill the process, your
-    /// instructions should be: "Add a new `quit` function to
-    /// `root-1/src/main.rs` to kill the process".
-    ///
-    /// Notice how the file path starts with root-1. Without that, the path
-    /// would be ambiguous and the call would fail!
-    /// </example>
-    ///
-    /// <example>
-    /// If you want to change documentation to always start with a capital
-    /// letter, your instructions should be: "In `root-2/db.js`,
-    /// `root-2/inMemory.js` and `root-2/sql.js`, change all the documentation
-    /// to start with a capital letter".
-    ///
-    /// Notice how we never specify code snippets in the instructions!
-    /// </example>
-    pub edit_instructions: String,
-
-    /// A user-friendly description of what changes are being made.
-    /// This will be shown to the user in the UI to describe the edit operation. The screen real estate for this UI will be extremely
-    /// constrained, so make the description extremely terse.
-    ///
-    /// <example>
-    /// For fixing a broken authentication system:
-    /// "Fix auth bug in login flow"
-    /// </example>
-    ///
-    /// <example>
-    /// For adding unit tests to a module:
-    /// "Add tests for user profile logic"
-    /// </example>
-    pub display_description: String,
-}
-
-pub struct EditFilesTool;
-
-impl Tool for EditFilesTool {
-    fn name(&self) -> String {
-        "edit_files".into()
-    }
-
-    fn needs_confirmation(&self) -> bool {
-        false
-    }
-
-    fn description(&self) -> String {
-        include_str!("./edit_files_tool/description.md").into()
-    }
-
-    fn icon(&self) -> IconName {
-        IconName::Pencil
-    }
-
-    fn input_schema(&self, format: LanguageModelToolSchemaFormat) -> serde_json::Value {
-        json_schema_for::<EditFilesToolInput>(format)
-    }
-
-    fn ui_text(&self, input: &serde_json::Value) -> String {
-        match serde_json::from_value::<EditFilesToolInput>(input.clone()) {
-            Ok(input) => input.display_description,
-            Err(_) => "Edit files".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 = match serde_json::from_value::<EditFilesToolInput>(input) {
-            Ok(input) => input,
-            Err(err) => return Task::ready(Err(anyhow!(err))),
-        };
-
-        match EditToolLog::try_global(cx) {
-            Some(log) => {
-                let req_id = log.update(cx, |log, cx| {
-                    log.new_request(input.edit_instructions.clone(), cx)
-                });
-
-                let task = EditToolRequest::new(
-                    input,
-                    messages,
-                    project,
-                    action_log,
-                    Some((log.clone(), req_id)),
-                    cx,
-                );
-
-                cx.spawn(async move |cx| {
-                    let result = task.await;
-
-                    let str_result = match &result {
-                        Ok(out) => Ok(out.clone()),
-                        Err(err) => Err(err.to_string()),
-                    };
-
-                    log.update(cx, |log, cx| log.set_tool_output(req_id, str_result, cx))
-                        .log_err();
-
-                    result
-                })
-            }
-
-            None => EditToolRequest::new(input, messages, project, action_log, None, cx),
-        }
-    }
-}
-
-struct EditToolRequest {
-    parser: EditActionParser,
-    editor_response: EditorResponse,
-    project: Entity<Project>,
-    action_log: Entity<ActionLog>,
-    tool_log: Option<(Entity<EditToolLog>, EditToolRequestId)>,
-}
-
-enum EditorResponse {
-    /// The editor model hasn't produced any actions yet.
-    /// If we don't have any by the end, we'll return its message to the architect model.
-    Message(String),
-    /// The editor model produced at least one action.
-    Actions {
-        applied: Vec<AppliedAction>,
-        search_errors: Vec<SearchError>,
-    },
-}
-
-struct AppliedAction {
-    source: String,
-    buffer: Entity<language::Buffer>,
-}
-
-#[derive(Debug)]
-enum DiffResult {
-    Diff(language::Diff),
-    SearchError(SearchError),
-}
-
-#[derive(Debug)]
-enum SearchError {
-    NoMatch {
-        file_path: String,
-        search: String,
-    },
-    EmptyBuffer {
-        file_path: String,
-        search: String,
-        exists: bool,
-    },
-}
-
-impl EditToolRequest {
-    fn new(
-        input: EditFilesToolInput,
-        messages: &[LanguageModelRequestMessage],
-        project: Entity<Project>,
-        action_log: Entity<ActionLog>,
-        tool_log: Option<(Entity<EditToolLog>, EditToolRequestId)>,
-        cx: &mut App,
-    ) -> Task<Result<String>> {
-        let model_registry = LanguageModelRegistry::read_global(cx);
-        let Some(ConfiguredModel { model, .. }) = model_registry.default_model() else {
-            return Task::ready(Err(anyhow!("No model configured")));
-        };
-
-        let mut messages = messages.to_vec();
-        // Remove the last tool use (this run) to prevent an invalid request
-        'outer: for message in messages.iter_mut().rev() {
-            for (index, content) in message.content.iter().enumerate().rev() {
-                match content {
-                    MessageContent::ToolUse(_) => {
-                        message.content.remove(index);
-                        break 'outer;
-                    }
-                    MessageContent::ToolResult(_) => {
-                        // If we find any tool results before a tool use, the request is already valid
-                        break 'outer;
-                    }
-                    MessageContent::Text(_) | MessageContent::Image(_) => {}
-                }
-            }
-        }
-
-        messages.push(LanguageModelRequestMessage {
-            role: Role::User,
-            content: vec![edit_model_prompt().into(), input.edit_instructions.into()],
-            cache: false,
-        });
-
-        cx.spawn(async move |cx| {
-            let llm_request = LanguageModelRequest {
-                messages,
-                tools: vec![],
-                stop: vec![],
-                temperature: Some(0.0),
-            };
-
-            let (mut tx, mut rx) = mpsc::channel::<String>(32);
-            let stream = model.stream_completion_text(llm_request, &cx);
-            let reader_task = cx.background_spawn(async move {
-                let mut chunks = stream.await?;
-
-                while let Some(chunk) = chunks.stream.next().await {
-                    if let Some(chunk) = chunk.log_err() {
-                        // we don't process here because the API fails
-                        // if we take too long between reads
-                        tx.send(chunk).await?
-                    }
-                }
-                tx.close().await?;
-                anyhow::Ok(())
-            });
-
-            let mut request = Self {
-                parser: EditActionParser::new(),
-                editor_response: EditorResponse::Message(String::with_capacity(256)),
-                action_log,
-                project,
-                tool_log,
-            };
-
-            while let Some(chunk) = rx.next().await {
-                request.process_response_chunk(&chunk, cx).await?;
-            }
-
-            reader_task.await?;
-
-            request.finalize(cx).await
-        })
-    }
-
-    async fn process_response_chunk(&mut self, chunk: &str, cx: &mut AsyncApp) -> Result<()> {
-        let new_actions = self.parser.parse_chunk(chunk);
-
-        if let EditorResponse::Message(ref mut message) = self.editor_response {
-            if new_actions.is_empty() {
-                message.push_str(chunk);
-            }
-        }
-
-        if let Some((ref log, req_id)) = self.tool_log {
-            log.update(cx, |log, cx| {
-                log.push_editor_response_chunk(req_id, chunk, &new_actions, cx)
-            })
-            .log_err();
-        }
-
-        for action in new_actions {
-            self.apply_action(action, cx).await?;
-        }
-
-        Ok(())
-    }
-
-    async fn apply_action(
-        &mut self,
-        (action, source): (EditAction, String),
-        cx: &mut AsyncApp,
-    ) -> Result<()> {
-        let project_path = self.project.read_with(cx, |project, cx| {
-            project
-                .find_project_path(action.file_path(), cx)
-                .context("Path not found in project")
-        })??;
-
-        let buffer = self
-            .project
-            .update(cx, |project, cx| project.open_buffer(project_path, cx))?
-            .await?;
-
-        let result = match action {
-            EditAction::Replace {
-                old,
-                new,
-                file_path,
-            } => {
-                let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
-
-                cx.background_executor()
-                    .spawn(Self::replace_diff(old, new, file_path, snapshot))
-                    .await
-            }
-            EditAction::Write { content, .. } => Ok(DiffResult::Diff(
-                buffer
-                    .read_with(cx, |buffer, cx| buffer.diff(content, cx))?
-                    .await,
-            )),
-        }?;
-
-        match result {
-            DiffResult::SearchError(error) => {
-                self.push_search_error(error);
-            }
-            DiffResult::Diff(diff) => {
-                cx.update(|cx| {
-                    self.action_log
-                        .update(cx, |log, cx| log.buffer_read(buffer.clone(), cx));
-                    buffer.update(cx, |buffer, cx| {
-                        buffer.finalize_last_transaction();
-                        buffer.apply_diff(diff, cx);
-                        buffer.finalize_last_transaction();
-                    });
-                    self.action_log
-                        .update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
-                })?;
-
-                self.push_applied_action(AppliedAction { source, buffer });
-            }
-        }
-
-        anyhow::Ok(())
-    }
-
-    fn push_search_error(&mut self, error: SearchError) {
-        match &mut self.editor_response {
-            EditorResponse::Message(_) => {
-                self.editor_response = EditorResponse::Actions {
-                    applied: Vec::new(),
-                    search_errors: vec![error],
-                };
-            }
-            EditorResponse::Actions { search_errors, .. } => {
-                search_errors.push(error);
-            }
-        }
-    }
-
-    fn push_applied_action(&mut self, action: AppliedAction) {
-        match &mut self.editor_response {
-            EditorResponse::Message(_) => {
-                self.editor_response = EditorResponse::Actions {
-                    applied: vec![action],
-                    search_errors: Vec::new(),
-                };
-            }
-            EditorResponse::Actions { applied, .. } => {
-                applied.push(action);
-            }
-        }
-    }
-
-    async fn replace_diff(
-        old: String,
-        new: String,
-        file_path: std::path::PathBuf,
-        snapshot: language::BufferSnapshot,
-    ) -> Result<DiffResult> {
-        if snapshot.is_empty() {
-            let exists = snapshot
-                .file()
-                .map_or(false, |file| file.disk_state().exists());
-
-            let error = SearchError::EmptyBuffer {
-                file_path: file_path.display().to_string(),
-                exists,
-                search: old,
-            };
-
-            return Ok(DiffResult::SearchError(error));
-        }
-
-        let replace_result =
-            // Try to match exactly
-            replace_exact(&old, &new, &snapshot)
-            .await
-            // If that fails, try being flexible about indentation
-            .or_else(|| replace_with_flexible_indent(&old, &new, &snapshot));
-
-        let Some(diff) = replace_result else {
-            let error = SearchError::NoMatch {
-                search: old,
-                file_path: file_path.display().to_string(),
-            };
-
-            return Ok(DiffResult::SearchError(error));
-        };
-
-        Ok(DiffResult::Diff(diff))
-    }
-
-    async fn finalize(self, cx: &mut AsyncApp) -> Result<String> {
-        match self.editor_response {
-            EditorResponse::Message(message) => Err(anyhow!(
-                "No edits were applied! You might need to provide more context.\n\n{}",
-                message
-            )),
-            EditorResponse::Actions {
-                applied,
-                search_errors,
-            } => {
-                let mut output = String::with_capacity(1024);
-
-                let parse_errors = self.parser.errors();
-                let has_errors = !search_errors.is_empty() || !parse_errors.is_empty();
-
-                if has_errors {
-                    let error_count = search_errors.len() + parse_errors.len();
-
-                    if applied.is_empty() {
-                        writeln!(
-                            &mut output,
-                            "{} errors occurred! No edits were applied.",
-                            error_count,
-                        )?;
-                    } else {
-                        writeln!(
-                            &mut output,
-                            "{} errors occurred, but {} edits were correctly applied.",
-                            error_count,
-                            applied.len(),
-                        )?;
-
-                        writeln!(
-                            &mut output,
-                            "# {} SEARCH/REPLACE block(s) applied:\n\nDo not re-send these since they are already applied!\n",
-                            applied.len()
-                        )?;
-                    }
-                } else {
-                    write!(
-                        &mut output,
-                        "Successfully applied! Here's a list of applied edits:"
-                    )?;
-                }
-
-                let mut changed_buffers = HashSet::default();
-
-                for action in applied {
-                    changed_buffers.insert(action.buffer.clone());
-                    write!(&mut output, "\n\n{}", action.source)?;
-                }
-
-                for buffer in &changed_buffers {
-                    self.project
-                        .update(cx, |project, cx| project.save_buffer(buffer.clone(), cx))?
-                        .await?;
-                }
-
-                if !search_errors.is_empty() {
-                    writeln!(
-                        &mut output,
-                        "\n\n## {} SEARCH/REPLACE block(s) failed to match:\n",
-                        search_errors.len()
-                    )?;
-
-                    for error in search_errors {
-                        match error {
-                            SearchError::NoMatch { file_path, search } => {
-                                writeln!(
-                                    &mut output,
-                                    "### No exact match in: `{}`\n```\n{}\n```\n",
-                                    file_path, search,
-                                )?;
-                            }
-                            SearchError::EmptyBuffer {
-                                file_path,
-                                exists: true,
-                                search,
-                            } => {
-                                writeln!(
-                                    &mut output,
-                                    "### No match because `{}` is empty:\n```\n{}\n```\n",
-                                    file_path, search,
-                                )?;
-                            }
-                            SearchError::EmptyBuffer {
-                                file_path,
-                                exists: false,
-                                search,
-                            } => {
-                                writeln!(
-                                    &mut output,
-                                    "### No match because `{}` does not exist:\n```\n{}\n```\n",
-                                    file_path, search,
-                                )?;
-                            }
-                        }
-                    }
-
-                    write!(
-                        &mut output,
-                        "The SEARCH section must exactly match an existing block of lines including all white \
-                        space, comments, indentation, docstrings, etc."
-                    )?;
-                }
-
-                if !parse_errors.is_empty() {
-                    writeln!(
-                        &mut output,
-                        "\n\n## {} SEARCH/REPLACE blocks failed to parse:",
-                        parse_errors.len()
-                    )?;
-
-                    for error in parse_errors {
-                        writeln!(&mut output, "- {}", error)?;
-                    }
-                }
-
-                if has_errors {
-                    writeln!(
-                        &mut output,
-                        "\n\nYou can fix errors by running the tool again. You can include instructions, \
-                        but errors are part of the conversation so you don't need to repeat them.",
-                    )?;
-
-                    Err(anyhow!(output))
-                } else {
-                    Ok(output)
-                }
-            }
-        }
-    }
-}

crates/assistant_tools/src/edit_files_tool/description.md 🔗

@@ -1,11 +0,0 @@
-Edit files in the current project by specifying instructions in natural language.
-
-IMPORTANT NOTE: If there is a find-replace tool, use that instead of this tool! This tool is only to be used as a fallback in case that tool is unavailable. Always prefer that tool if it is available.
-
-When using this tool, you should suggest one coherent edit that can be made to the codebase.
-
-When the set of edits you want to make is large or complex, feel free to invoke this tool multiple times, each time focusing on a specific change you wanna make.
-
-You should use this tool when you want to edit a subset of a file's contents, but not the entire file. You should not use this tool when you want to replace the entire contents of a file with completely different contents, and you absolutely must never use this tool to create new files from scratch. If you ever consider using this tool to create a new file from scratch, for any reason, instead you must reconsider and choose a different approach.
-
-DO NOT call this tool until the code to be edited appears in the conversation! You must use the `read-files` tool or ask the user to add it to context first.

crates/assistant_tools/src/edit_files_tool/edit_action.rs 🔗

@@ -1,967 +0,0 @@
-use std::{
-    mem::take,
-    ops::Range,
-    path::{Path, PathBuf},
-};
-use util::ResultExt;
-
-/// Represents an edit action to be performed on a file.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub enum EditAction {
-    /// Replace specific content in a file with new content
-    Replace {
-        file_path: PathBuf,
-        old: String,
-        new: String,
-    },
-    /// Write content to a file (create or overwrite)
-    Write { file_path: PathBuf, content: String },
-}
-
-impl EditAction {
-    pub fn file_path(&self) -> &Path {
-        match self {
-            EditAction::Replace { file_path, .. } => file_path,
-            EditAction::Write { file_path, .. } => file_path,
-        }
-    }
-}
-
-/// Parses edit actions from an LLM response.
-/// See system.md for more details on the format.
-#[derive(Debug)]
-pub struct EditActionParser {
-    state: State,
-    line: usize,
-    column: usize,
-    marker_ix: usize,
-    action_source: Vec<u8>,
-    fence_start_offset: usize,
-    block_range: Range<usize>,
-    old_range: Range<usize>,
-    new_range: Range<usize>,
-    errors: Vec<ParseError>,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-enum State {
-    /// Anywhere outside an action
-    Default,
-    /// After opening ```, in optional language tag
-    OpenFence,
-    /// In SEARCH marker
-    SearchMarker,
-    /// In search block or divider
-    SearchBlock,
-    /// In replace block or REPLACE marker
-    ReplaceBlock,
-    /// In closing ```
-    CloseFence,
-}
-
-/// used to avoid having source code that looks like git-conflict markers
-macro_rules! marker_sym {
-    ($char:expr) => {
-        concat!($char, $char, $char, $char, $char, $char, $char)
-    };
-}
-
-const SEARCH_MARKER: &str = concat!(marker_sym!('<'), " SEARCH");
-const DIVIDER: &str = marker_sym!('=');
-const NL_DIVIDER: &str = concat!("\n", marker_sym!('='));
-const REPLACE_MARKER: &str = concat!(marker_sym!('>'), " REPLACE");
-const NL_REPLACE_MARKER: &str = concat!("\n", marker_sym!('>'), " REPLACE");
-const FENCE: &str = "```";
-
-impl EditActionParser {
-    /// Creates a new `EditActionParser`
-    pub fn new() -> Self {
-        Self {
-            state: State::Default,
-            line: 1,
-            column: 0,
-            action_source: Vec::new(),
-            fence_start_offset: 0,
-            marker_ix: 0,
-            block_range: Range::default(),
-            old_range: Range::default(),
-            new_range: Range::default(),
-            errors: Vec::new(),
-        }
-    }
-
-    /// Processes a chunk of input text and returns any completed edit actions.
-    ///
-    /// This method can be called repeatedly with fragments of input. The parser
-    /// maintains its state between calls, allowing you to process streaming input
-    /// as it becomes available. Actions are only inserted once they are fully parsed.
-    ///
-    /// If a block fails to parse, it will simply be skipped and an error will be recorded.
-    /// All errors can be accessed through the `EditActionsParser::errors` method.
-    pub fn parse_chunk(&mut self, input: &str) -> Vec<(EditAction, String)> {
-        use State::*;
-
-        let mut actions = Vec::new();
-
-        for byte in input.bytes() {
-            // Update line and column tracking
-            if byte == b'\n' {
-                self.line += 1;
-                self.column = 0;
-            } else {
-                self.column += 1;
-            }
-
-            let action_offset = self.action_source.len();
-
-            match &self.state {
-                Default => match self.match_marker(byte, FENCE, false) {
-                    MarkerMatch::Complete => {
-                        self.fence_start_offset = action_offset + 1 - FENCE.len();
-                        self.to_state(OpenFence);
-                    }
-                    MarkerMatch::Partial => {}
-                    MarkerMatch::None => {
-                        if self.marker_ix > 0 {
-                            self.marker_ix = 0;
-                        } else if self.action_source.ends_with(b"\n") {
-                            self.action_source.clear();
-                        }
-                    }
-                },
-                OpenFence => {
-                    // skip language tag
-                    if byte == b'\n' {
-                        self.to_state(SearchMarker);
-                    }
-                }
-                SearchMarker => {
-                    if self.expect_marker(byte, SEARCH_MARKER, true) {
-                        self.to_state(SearchBlock);
-                    }
-                }
-                SearchBlock => {
-                    if self.extend_block_range(byte, DIVIDER, NL_DIVIDER) {
-                        self.old_range = take(&mut self.block_range);
-                        self.to_state(ReplaceBlock);
-                    }
-                }
-                ReplaceBlock => {
-                    if self.extend_block_range(byte, REPLACE_MARKER, NL_REPLACE_MARKER) {
-                        self.new_range = take(&mut self.block_range);
-                        self.to_state(CloseFence);
-                    }
-                }
-                CloseFence => {
-                    if self.expect_marker(byte, FENCE, false) {
-                        self.action_source.push(byte);
-
-                        if let Some(action) = self.action() {
-                            actions.push(action);
-                        }
-
-                        self.errors();
-                        self.reset();
-
-                        continue;
-                    }
-                }
-            };
-
-            self.action_source.push(byte);
-        }
-
-        actions
-    }
-
-    /// Returns a reference to the errors encountered during parsing.
-    pub fn errors(&self) -> &[ParseError] {
-        &self.errors
-    }
-
-    fn action(&mut self) -> Option<(EditAction, String)> {
-        let old_range = take(&mut self.old_range);
-        let new_range = take(&mut self.new_range);
-
-        let action_source = take(&mut self.action_source);
-        let action_source = String::from_utf8(action_source).log_err()?;
-
-        let mut file_path_bytes = action_source[..self.fence_start_offset].to_owned();
-
-        if file_path_bytes.ends_with("\n") {
-            file_path_bytes.pop();
-            if file_path_bytes.ends_with("\r") {
-                file_path_bytes.pop();
-            }
-        }
-
-        let file_path = PathBuf::from(file_path_bytes);
-
-        if old_range.is_empty() {
-            return Some((
-                EditAction::Write {
-                    file_path,
-                    content: action_source[new_range].to_owned(),
-                },
-                action_source,
-            ));
-        }
-
-        let old = action_source[old_range].to_owned();
-        let new = action_source[new_range].to_owned();
-
-        let action = EditAction::Replace {
-            file_path,
-            old,
-            new,
-        };
-
-        Some((action, action_source))
-    }
-
-    fn to_state(&mut self, state: State) {
-        self.state = state;
-        self.marker_ix = 0;
-    }
-
-    fn reset(&mut self) {
-        self.action_source.clear();
-        self.block_range = Range::default();
-        self.old_range = Range::default();
-        self.new_range = Range::default();
-        self.fence_start_offset = 0;
-        self.marker_ix = 0;
-        self.to_state(State::Default);
-    }
-
-    fn expect_marker(&mut self, byte: u8, marker: &'static str, trailing_newline: bool) -> bool {
-        match self.match_marker(byte, marker, trailing_newline) {
-            MarkerMatch::Complete => true,
-            MarkerMatch::Partial => false,
-            MarkerMatch::None => {
-                self.errors.push(ParseError {
-                    line: self.line,
-                    column: self.column,
-                    expected: marker,
-                    found: byte,
-                });
-
-                self.reset();
-                false
-            }
-        }
-    }
-
-    fn extend_block_range(&mut self, byte: u8, marker: &str, nl_marker: &str) -> bool {
-        let marker = if self.block_range.is_empty() {
-            // do not require another newline if block is empty
-            marker
-        } else {
-            nl_marker
-        };
-
-        let offset = self.action_source.len();
-
-        match self.match_marker(byte, marker, true) {
-            MarkerMatch::Complete => {
-                if self.action_source[self.block_range.clone()].ends_with(b"\r") {
-                    self.block_range.end -= 1;
-                }
-
-                true
-            }
-            MarkerMatch::Partial => false,
-            MarkerMatch::None => {
-                if self.marker_ix > 0 {
-                    self.marker_ix = 0;
-                    self.block_range.end = offset;
-
-                    // The beginning of marker might match current byte
-                    match self.match_marker(byte, marker, true) {
-                        MarkerMatch::Complete => return true,
-                        MarkerMatch::Partial => return false,
-                        MarkerMatch::None => { /* no match, keep collecting */ }
-                    }
-                }
-
-                if self.block_range.is_empty() {
-                    self.block_range.start = offset;
-                }
-                self.block_range.end = offset + 1;
-
-                false
-            }
-        }
-    }
-
-    fn match_marker(&mut self, byte: u8, marker: &str, trailing_newline: bool) -> MarkerMatch {
-        if trailing_newline && self.marker_ix >= marker.len() {
-            if byte == b'\n' {
-                MarkerMatch::Complete
-            } else if byte == b'\r' {
-                MarkerMatch::Partial
-            } else {
-                MarkerMatch::None
-            }
-        } else if byte == marker.as_bytes()[self.marker_ix] {
-            self.marker_ix += 1;
-
-            if self.marker_ix < marker.len() || trailing_newline {
-                MarkerMatch::Partial
-            } else {
-                MarkerMatch::Complete
-            }
-        } else {
-            MarkerMatch::None
-        }
-    }
-}
-
-#[derive(Debug)]
-enum MarkerMatch {
-    None,
-    Partial,
-    Complete,
-}
-
-#[derive(Debug, PartialEq, Eq)]
-pub struct ParseError {
-    line: usize,
-    column: usize,
-    expected: &'static str,
-    found: u8,
-}
-
-impl std::fmt::Display for ParseError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(
-            f,
-            "input:{}:{}: Expected marker {:?}, found {:?}",
-            self.line, self.column, self.expected, self.found as char
-        )
-    }
-}
-
-pub fn edit_model_prompt() -> String {
-    include_str!("edit_prompt.md")
-        .to_string()
-        .replace("{{SEARCH_MARKER}}", SEARCH_MARKER)
-        .replace("{{DIVIDER}}", DIVIDER)
-        .replace("{{REPLACE_MARKER}}", REPLACE_MARKER)
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-    use rand::prelude::*;
-    use util::line_endings;
-
-    const WRONG_MARKER: &str = concat!(marker_sym!('<'), " WRONG_MARKER");
-
-    #[test]
-    fn test_simple_edit_action() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"src/main.rs
-```
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn original() {}".to_string(),
-                new: "fn replacement() {}".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_with_language_tag() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn original() {}".to_string(),
-                new: "fn replacement() {}".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_with_surrounding_text() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"Here's a modification I'd like to make to the file:
-
-src/main.rs
-```rust
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-```
-
-This change makes the function better.
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn original() {}".to_string(),
-                new: "fn replacement() {}".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_multiple_edit_actions() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"First change:
-src/main.rs
-```
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-```
-
-Second change:
-src/utils.rs
-```rust
-{}
-fn old_util() -> bool {{ false }}
-{}
-fn new_util() -> bool {{ true }}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER, SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 2);
-
-        let (action, _) = &actions[0];
-        assert_eq!(
-            action,
-            &EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn original() {}".to_string(),
-                new: "fn replacement() {}".to_string(),
-            }
-        );
-        let (action2, _) = &actions[1];
-        assert_eq!(
-            action2,
-            &EditAction::Replace {
-                file_path: PathBuf::from("src/utils.rs"),
-                old: "fn old_util() -> bool { false }".to_string(),
-                new: "fn new_util() -> bool { true }".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_multiline() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-fn original() {{
-    println!("This is the original function");
-    let x = 42;
-    if x > 0 {{
-        println!("Positive number");
-    }}
-}}
-{}
-fn replacement() {{
-    println!("This is the replacement function");
-    let x = 100;
-    if x > 50 {{
-        println!("Large number");
-    }} else {{
-        println!("Small number");
-    }}
-}}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-
-        let (action, _) = &actions[0];
-        assert_eq!(
-            action,
-            &EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn original() {\n    println!(\"This is the original function\");\n    let x = 42;\n    if x > 0 {\n        println!(\"Positive number\");\n    }\n}".to_string(),
-                new: "fn replacement() {\n    println!(\"This is the replacement function\");\n    let x = 100;\n    if x > 50 {\n        println!(\"Large number\");\n    } else {\n        println!(\"Small number\");\n    }\n}".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_write_action() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"Create a new main.rs file:
-
-src/main.rs
-```rust
-{}
-{}
-fn new_function() {{
-    println!("This function is being added");
-}}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Write {
-                file_path: PathBuf::from("src/main.rs"),
-                content: "fn new_function() {\n    println!(\"This function is being added\");\n}"
-                    .to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_empty_replace() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-fn this_will_be_deleted() {{
-    println!("Deleting this function");
-}}
-{}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn this_will_be_deleted() {\n    println!(\"Deleting this function\");\n}"
-                    .to_string(),
-                new: "".to_string(),
-            }
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input.replace("\n", "\r\n"));
-        assert_no_errors(&parser);
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old:
-                    "fn this_will_be_deleted() {\r\n    println!(\"Deleting this function\");\r\n}"
-                        .to_string(),
-                new: "".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_empty_both() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-{}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        assert_eq!(actions.len(), 1);
-        assert_eq!(
-            actions[0].0,
-            EditAction::Write {
-                file_path: PathBuf::from("src/main.rs"),
-                content: String::new(),
-            }
-        );
-        assert_no_errors(&parser);
-    }
-
-    #[test]
-    fn test_resumability() {
-        // Construct test input using format with multiline string literals
-        let input_part1 = format!("src/main.rs\n```rust\n{}\nfn ori", SEARCH_MARKER);
-
-        let input_part2 = format!("ginal() {{}}\n{}\nfn replacement() {{}}", DIVIDER);
-
-        let input_part3 = format!("\n{}\n```\n", REPLACE_MARKER);
-
-        let mut parser = EditActionParser::new();
-        let actions1 = parser.parse_chunk(&input_part1);
-        assert_no_errors(&parser);
-        assert_eq!(actions1.len(), 0);
-
-        let actions2 = parser.parse_chunk(&input_part2);
-        // No actions should be complete yet
-        assert_no_errors(&parser);
-        assert_eq!(actions2.len(), 0);
-
-        let actions3 = parser.parse_chunk(&input_part3);
-        // The third chunk should complete the action
-        assert_no_errors(&parser);
-        assert_eq!(actions3.len(), 1);
-        let (action, _) = &actions3[0];
-        assert_eq!(
-            action,
-            &EditAction::Replace {
-                file_path: PathBuf::from("src/main.rs"),
-                old: "fn original() {}".to_string(),
-                new: "fn replacement() {}".to_string(),
-            }
-        );
-    }
-
-    #[test]
-    fn test_parser_state_preservation() {
-        let mut parser = EditActionParser::new();
-        let first_chunk = format!("src/main.rs\n```rust\n{}\n", SEARCH_MARKER);
-        let actions1 = parser.parse_chunk(&first_chunk);
-
-        // Check parser is in the correct state
-        assert_no_errors(&parser);
-        assert_eq!(parser.state, State::SearchBlock);
-        assert_eq!(parser.action_source, first_chunk.as_bytes());
-
-        // Continue parsing
-        let second_chunk = format!("original code\n{}\n", DIVIDER);
-        let actions2 = parser.parse_chunk(&second_chunk);
-
-        assert_no_errors(&parser);
-        assert_eq!(parser.state, State::ReplaceBlock);
-        assert_eq!(
-            &parser.action_source[parser.old_range.clone()],
-            b"original code"
-        );
-
-        let third_chunk = format!("replacement code\n{}\n```\n", REPLACE_MARKER);
-        let actions3 = parser.parse_chunk(&third_chunk);
-
-        // After complete parsing, state should reset
-        assert_no_errors(&parser);
-        assert_eq!(parser.state, State::Default);
-        assert_eq!(parser.action_source, b"\n");
-        assert!(parser.old_range.is_empty());
-        assert!(parser.new_range.is_empty());
-
-        assert_eq!(actions1.len(), 0);
-        assert_eq!(actions2.len(), 0);
-        assert_eq!(actions3.len(), 1);
-    }
-
-    #[test]
-    fn test_invalid_search_marker() {
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-```
-"#,
-            WRONG_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-        assert_eq!(actions.len(), 0);
-
-        assert_eq!(parser.errors().len(), 1);
-        let error = &parser.errors()[0];
-
-        assert_eq!(
-            error.to_string(),
-            format!(
-                "input:3:9: Expected marker \"{}\", found 'W'",
-                SEARCH_MARKER
-            )
-        );
-    }
-
-    #[test]
-    fn test_missing_closing_fence() {
-        // Construct test input using format with multiline string literals
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-<!-- Missing closing fence -->
-
-src/utils.rs
-```rust
-{}
-fn utils_func() {{}}
-{}
-fn new_utils_func() {{}}
-{}
-```
-"#,
-            SEARCH_MARKER, DIVIDER, REPLACE_MARKER, SEARCH_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&input);
-
-        // Only the second block should be parsed
-        assert_eq!(actions.len(), 1);
-        let (action, _) = &actions[0];
-        assert_eq!(
-            action,
-            &EditAction::Replace {
-                file_path: PathBuf::from("src/utils.rs"),
-                old: "fn utils_func() {}".to_string(),
-                new: "fn new_utils_func() {}".to_string(),
-            }
-        );
-        assert_eq!(parser.errors().len(), 1);
-        assert_eq!(
-            parser.errors()[0].to_string(),
-            "input:8:1: Expected marker \"```\", found '<'"
-        );
-
-        // The parser should continue after an error
-        assert_eq!(parser.state, State::Default);
-    }
-
-    #[test]
-    fn test_parse_examples_in_edit_prompt() {
-        let mut parser = EditActionParser::new();
-        let actions = parser.parse_chunk(&edit_model_prompt());
-        assert_examples_in_edit_prompt(&actions, parser.errors());
-    }
-
-    #[gpui::test(iterations = 10)]
-    fn test_random_chunking_of_edit_prompt(mut rng: StdRng) {
-        let mut parser = EditActionParser::new();
-        let mut remaining: &str = &edit_model_prompt();
-        let mut actions = Vec::with_capacity(5);
-
-        while !remaining.is_empty() {
-            let chunk_size = rng.gen_range(1..=std::cmp::min(remaining.len(), 100));
-
-            let (chunk, rest) = remaining.split_at(chunk_size);
-
-            let chunk_actions = parser.parse_chunk(chunk);
-            actions.extend(chunk_actions);
-            remaining = rest;
-        }
-
-        assert_examples_in_edit_prompt(&actions, parser.errors());
-    }
-
-    fn assert_examples_in_edit_prompt(actions: &[(EditAction, String)], errors: &[ParseError]) {
-        assert_eq!(actions.len(), 5);
-
-        assert_eq!(
-            actions[0].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("mathweb/flask/app.py"),
-                old: "from flask import Flask".to_string(),
-                new: line_endings!("import math\nfrom flask import Flask").to_string(),
-            },
-        );
-
-        assert_eq!(
-            actions[1].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("mathweb/flask/app.py"),
-                old: line_endings!("def factorial(n):\n    \"compute factorial\"\n\n    if n == 0:\n        return 1\n    else:\n        return n * factorial(n-1)\n").to_string(),
-                new: "".to_string(),
-            }
-        );
-
-        assert_eq!(
-            actions[2].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("mathweb/flask/app.py"),
-                old: "    return str(factorial(n))".to_string(),
-                new: "    return str(math.factorial(n))".to_string(),
-            },
-        );
-
-        assert_eq!(
-            actions[3].0,
-            EditAction::Write {
-                file_path: PathBuf::from("hello.py"),
-                content: line_endings!(
-                    "def hello():\n    \"print a greeting\"\n\n    print(\"hello\")"
-                )
-                .to_string(),
-            },
-        );
-
-        assert_eq!(
-            actions[4].0,
-            EditAction::Replace {
-                file_path: PathBuf::from("main.py"),
-                old: line_endings!(
-                    "def hello():\n    \"print a greeting\"\n\n    print(\"hello\")"
-                )
-                .to_string(),
-                new: "from hello import hello".to_string(),
-            },
-        );
-
-        // The system prompt includes some text that would produce errors
-        assert_eq!(
-            errors[0].to_string(),
-            format!(
-                "input:102:1: Expected marker \"{}\", found '3'",
-                SEARCH_MARKER
-            )
-        );
-        #[cfg(not(windows))]
-        assert_eq!(
-            errors[1].to_string(),
-            format!(
-                "input:109:0: Expected marker \"{}\", found '\\n'",
-                SEARCH_MARKER
-            )
-        );
-        #[cfg(windows)]
-        assert_eq!(
-            errors[1].to_string(),
-            format!(
-                "input:108:1: Expected marker \"{}\", found '\\r'",
-                SEARCH_MARKER
-            )
-        );
-    }
-
-    #[test]
-    fn test_print_error() {
-        let input = format!(
-            r#"src/main.rs
-```rust
-{}
-fn original() {{}}
-{}
-fn replacement() {{}}
-{}
-```
-"#,
-            WRONG_MARKER, DIVIDER, REPLACE_MARKER
-        );
-
-        let mut parser = EditActionParser::new();
-        parser.parse_chunk(&input);
-
-        assert_eq!(parser.errors().len(), 1);
-        let error = &parser.errors()[0];
-        let expected_error = format!(
-            r#"input:3:9: Expected marker "{}", found 'W'"#,
-            SEARCH_MARKER
-        );
-
-        assert_eq!(format!("{}", error), expected_error);
-    }
-
-    // helpers
-
-    fn assert_no_errors(parser: &EditActionParser) {
-        let errors = parser.errors();
-
-        assert!(
-            errors.is_empty(),
-            "Expected no errors, but found:\n\n{}",
-            errors
-                .iter()
-                .map(|e| e.to_string())
-                .collect::<Vec<String>>()
-                .join("\n")
-        );
-    }
-}

crates/assistant_tools/src/edit_files_tool/edit_prompt.md 🔗

@@ -1,134 +0,0 @@
-Act as an expert software developer.
-Always use best practices when coding.
-Respect and use existing conventions, libraries, etc that are already present in the code base.
-
-Take requests for changes to the supplied code.
-Always reply to the user in the same language they are using.
-
-Once you understand the request, you MUST:
-
-1. Decide if you need to propose *SEARCH/REPLACE* edits to any files that haven't been added to the chat. You can create new files without asking!
-
-2. Think step-by-step and explain the needed changes in a few short sentences.
-
-3. Describe each change with a *SEARCH/REPLACE block* per the examples below.
-
-All changes to files must use this *SEARCH/REPLACE block* format.
-ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!
-
-# Example conversations:
-
-## USER: Change get_factorial() to use math.factorial
-
-## ASSISTANT: To make this change we need to modify `mathweb/flask/app.py` to:
-
-1. Import the math package.
-2. Remove the existing factorial() function.
-3. Update get_factorial() to call math.factorial instead.
-
-Here are the *SEARCH/REPLACE* blocks:
-
-mathweb/flask/app.py
-```python
-{{SEARCH_MARKER}}
-from flask import Flask
-{{DIVIDER}}
-import math
-from flask import Flask
-{{REPLACE_MARKER}}
-```
-
-mathweb/flask/app.py
-```python
-{{SEARCH_MARKER}}
-def factorial(n):
-    "compute factorial"
-
-    if n == 0:
-        return 1
-    else:
-        return n * factorial(n-1)
-
-{{DIVIDER}}
-{{REPLACE_MARKER}}
-```
-
-mathweb/flask/app.py
-```python
-{{SEARCH_MARKER}}
-    return str(factorial(n))
-{{DIVIDER}}
-    return str(math.factorial(n))
-{{REPLACE_MARKER}}
-```
-
-
-## USER: Refactor hello() into its own file.
-
-## ASSISTANT: To make this change we need to modify `main.py` and make a new file `hello.py`:
-
-1. Make a new hello.py file with hello() in it.
-2. Remove hello() from main.py and replace it with an import.
-
-Here are the *SEARCH/REPLACE* blocks:
-
-hello.py
-```python
-{{SEARCH_MARKER}}
-{{DIVIDER}}
-def hello():
-    "print a greeting"
-
-    print("hello")
-{{REPLACE_MARKER}}
-```
-
-main.py
-```python
-{{SEARCH_MARKER}}
-def hello():
-    "print a greeting"
-
-    print("hello")
-{{DIVIDER}}
-from hello import hello
-{{REPLACE_MARKER}}
-```
-# *SEARCH/REPLACE block* Rules:
-
-Every *SEARCH/REPLACE block* must use this format:
-1. The *FULL* file path alone on a line, verbatim. No bold asterisks, no quotes around it, no escaping of characters, etc.
-2. The opening fence and code language, eg: ```python
-3. The start of search block: {{SEARCH_MARKER}}
-4. A contiguous chunk of lines to search for in the existing source code
-5. The dividing line: {{DIVIDER}}
-6. The lines to replace into the source code
-7. The end of the replace block: {{REPLACE_MARKER}}
-8. The closing fence: ```
-
-Use the *FULL* file path, as shown to you by the user. Make sure to include the project's root directory name at the start of the path. *NEVER* specify the absolute path of the file!
-
-Every *SEARCH* section must *EXACTLY MATCH* the existing file content, character for character, including all comments, docstrings, etc.
-If the file contains code or other data wrapped/escaped in json/xml/quotes or other containers, you need to propose edits to the literal contents of the file, including the container markup.
-
-*SEARCH/REPLACE* blocks will *only* replace the first match occurrence.
-Including multiple unique *SEARCH/REPLACE* blocks if needed.
-Include enough lines in each SEARCH section to uniquely match each set of lines that need to change.
-
-Keep *SEARCH/REPLACE* blocks concise.
-Break large *SEARCH/REPLACE* blocks into a series of smaller blocks that each change a small portion of the file.
-Include just the changing lines, and a few surrounding lines if needed for uniqueness.
-Do not include long runs of unchanging lines in *SEARCH/REPLACE* blocks.
-
-Only create *SEARCH/REPLACE* blocks for files that have been read! Even though the conversation includes `read-file` tool results, you *CANNOT* issue your own reads. If the conversation doesn't include the code you need to edit, ask for it to be read explicitly.
-
-To move code within a file, use 2 *SEARCH/REPLACE* blocks: 1 to delete it from its current location, 1 to insert it in the new location.
-
-Pay attention to which filenames the user wants you to edit, especially if they are asking you to create a new file.
-
-If you want to put code in a new file, use a *SEARCH/REPLACE block* with:
-- A new file path, including dir name if needed
-- An empty `SEARCH` section
-- The new file's contents in the `REPLACE` section
-
-ONLY EVER RETURN CODE IN A *SEARCH/REPLACE BLOCK*!

crates/assistant_tools/src/edit_files_tool/log.rs 🔗

@@ -1,417 +0,0 @@
-use std::path::Path;
-
-use collections::HashSet;
-use feature_flags::FeatureFlagAppExt;
-use gpui::{
-    App, Empty, Entity, EventEmitter, FocusHandle, Focusable, Global, ListAlignment, ListState,
-    SharedString, Subscription, Window, actions, list, prelude::*,
-};
-use release_channel::ReleaseChannel;
-use settings::Settings;
-use ui::prelude::*;
-use workspace::{Item, Workspace, WorkspaceId, item::ItemEvent};
-
-use super::edit_action::EditAction;
-
-actions!(debug, [EditTool]);
-
-pub fn init(cx: &mut App) {
-    if cx.is_staff() || ReleaseChannel::global(cx) == ReleaseChannel::Dev {
-        // Track events even before opening the log
-        EditToolLog::global(cx);
-    }
-
-    cx.observe_new(|workspace: &mut Workspace, _, _| {
-        workspace.register_action(|workspace, _: &EditTool, window, cx| {
-            let viewer = cx.new(EditToolLogViewer::new);
-            workspace.add_item_to_active_pane(Box::new(viewer), None, true, window, cx)
-        });
-    })
-    .detach();
-}
-
-pub struct GlobalEditToolLog(Entity<EditToolLog>);
-
-impl Global for GlobalEditToolLog {}
-
-#[derive(Default)]
-pub struct EditToolLog {
-    requests: Vec<EditToolRequest>,
-}
-
-#[derive(Clone, Copy, Hash, Eq, PartialEq)]
-pub struct EditToolRequestId(u32);
-
-impl EditToolLog {
-    pub fn global(cx: &mut App) -> Entity<Self> {
-        match Self::try_global(cx) {
-            Some(entity) => entity,
-            None => {
-                let entity = cx.new(|_cx| Self::default());
-                cx.set_global(GlobalEditToolLog(entity.clone()));
-                entity
-            }
-        }
-    }
-
-    pub fn try_global(cx: &App) -> Option<Entity<Self>> {
-        cx.try_global::<GlobalEditToolLog>()
-            .map(|log| log.0.clone())
-    }
-
-    pub fn new_request(
-        &mut self,
-        instructions: String,
-        cx: &mut Context<Self>,
-    ) -> EditToolRequestId {
-        let id = EditToolRequestId(self.requests.len() as u32);
-        self.requests.push(EditToolRequest {
-            id,
-            instructions,
-            editor_response: None,
-            tool_output: None,
-            parsed_edits: Vec::new(),
-        });
-        cx.emit(EditToolLogEvent::Inserted);
-        id
-    }
-
-    pub fn push_editor_response_chunk(
-        &mut self,
-        id: EditToolRequestId,
-        chunk: &str,
-        new_actions: &[(EditAction, String)],
-        cx: &mut Context<Self>,
-    ) {
-        if let Some(request) = self.requests.get_mut(id.0 as usize) {
-            match &mut request.editor_response {
-                None => {
-                    request.editor_response = Some(chunk.to_string());
-                }
-                Some(response) => {
-                    response.push_str(chunk);
-                }
-            }
-            request
-                .parsed_edits
-                .extend(new_actions.iter().cloned().map(|(action, _)| action));
-
-            cx.emit(EditToolLogEvent::Updated);
-        }
-    }
-
-    pub fn set_tool_output(
-        &mut self,
-        id: EditToolRequestId,
-        tool_output: Result<String, String>,
-        cx: &mut Context<Self>,
-    ) {
-        if let Some(request) = self.requests.get_mut(id.0 as usize) {
-            request.tool_output = Some(tool_output);
-            cx.emit(EditToolLogEvent::Updated);
-        }
-    }
-}
-
-enum EditToolLogEvent {
-    Inserted,
-    Updated,
-}
-
-impl EventEmitter<EditToolLogEvent> for EditToolLog {}
-
-pub struct EditToolRequest {
-    id: EditToolRequestId,
-    instructions: String,
-    // we don't use a result here because the error might have occurred after we got a response
-    editor_response: Option<String>,
-    parsed_edits: Vec<EditAction>,
-    tool_output: Option<Result<String, String>>,
-}
-
-pub struct EditToolLogViewer {
-    focus_handle: FocusHandle,
-    log: Entity<EditToolLog>,
-    list_state: ListState,
-    expanded_edits: HashSet<(EditToolRequestId, usize)>,
-    _subscription: Subscription,
-}
-
-impl EditToolLogViewer {
-    pub fn new(cx: &mut Context<Self>) -> Self {
-        let log = EditToolLog::global(cx);
-
-        let subscription = cx.subscribe(&log, Self::handle_log_event);
-
-        Self {
-            focus_handle: cx.focus_handle(),
-            log: log.clone(),
-            list_state: ListState::new(
-                log.read(cx).requests.len(),
-                ListAlignment::Bottom,
-                px(1024.),
-                {
-                    let this = cx.entity().downgrade();
-                    move |ix, window: &mut Window, cx: &mut App| {
-                        this.update(cx, |this, cx| this.render_request(ix, window, cx))
-                            .unwrap()
-                    }
-                },
-            ),
-            expanded_edits: HashSet::default(),
-            _subscription: subscription,
-        }
-    }
-
-    fn handle_log_event(
-        &mut self,
-        _: Entity<EditToolLog>,
-        event: &EditToolLogEvent,
-        cx: &mut Context<Self>,
-    ) {
-        match event {
-            EditToolLogEvent::Inserted => {
-                let count = self.list_state.item_count();
-                self.list_state.splice(count..count, 1);
-            }
-            EditToolLogEvent::Updated => {}
-        }
-
-        cx.notify();
-    }
-
-    fn render_request(
-        &self,
-        index: usize,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> AnyElement {
-        let requests = &self.log.read(cx).requests;
-        let request = &requests[index];
-
-        v_flex()
-            .gap_3()
-            .child(Self::render_section(IconName::ArrowRight, "Tool Input"))
-            .child(request.instructions.clone())
-            .py_5()
-            .when(index + 1 < requests.len(), |element| {
-                element
-                    .border_b_1()
-                    .border_color(cx.theme().colors().border)
-            })
-            .map(|parent| match &request.editor_response {
-                None => {
-                    if request.tool_output.is_none() {
-                        parent.child("...")
-                    } else {
-                        parent
-                    }
-                }
-                Some(response) => parent
-                    .child(Self::render_section(
-                        IconName::ZedAssistant,
-                        "Editor Response",
-                    ))
-                    .child(Label::new(response.clone()).buffer_font(cx)),
-            })
-            .when(!request.parsed_edits.is_empty(), |parent| {
-                parent
-                    .child(Self::render_section(IconName::Microscope, "Parsed Edits"))
-                    .child(
-                        v_flex()
-                            .gap_2()
-                            .children(request.parsed_edits.iter().enumerate().map(
-                                |(index, edit)| {
-                                    self.render_edit_action(edit, request.id, index, cx)
-                                },
-                            )),
-                    )
-            })
-            .when_some(request.tool_output.as_ref(), |parent, output| {
-                parent
-                    .child(Self::render_section(IconName::ArrowLeft, "Tool Output"))
-                    .child(match output {
-                        Ok(output) => Label::new(output.clone()).color(Color::Success),
-                        Err(error) => Label::new(error.clone()).color(Color::Error),
-                    })
-            })
-            .into_any()
-    }
-
-    fn render_section(icon: IconName, title: &'static str) -> AnyElement {
-        h_flex()
-            .gap_1()
-            .child(Icon::new(icon).color(Color::Muted))
-            .child(Label::new(title).size(LabelSize::Small).color(Color::Muted))
-            .into_any()
-    }
-
-    fn render_edit_action(
-        &self,
-        edit_action: &EditAction,
-        request_id: EditToolRequestId,
-        index: usize,
-        cx: &Context<Self>,
-    ) -> AnyElement {
-        let expanded_id = (request_id, index);
-
-        match edit_action {
-            EditAction::Replace {
-                file_path,
-                old,
-                new,
-            } => self
-                .render_edit_action_container(
-                    expanded_id,
-                    &file_path,
-                    [
-                        Self::render_block(IconName::MagnifyingGlass, "Search", old.clone(), cx)
-                            .border_r_1()
-                            .border_color(cx.theme().colors().border)
-                            .into_any(),
-                        Self::render_block(IconName::Replace, "Replace", new.clone(), cx)
-                            .into_any(),
-                    ],
-                    cx,
-                )
-                .into_any(),
-            EditAction::Write { file_path, content } => self
-                .render_edit_action_container(
-                    expanded_id,
-                    &file_path,
-                    [
-                        Self::render_block(IconName::Pencil, "Write", content.clone(), cx)
-                            .into_any(),
-                    ],
-                    cx,
-                )
-                .into_any(),
-        }
-    }
-
-    fn render_edit_action_container(
-        &self,
-        expanded_id: (EditToolRequestId, usize),
-        file_path: &Path,
-        content: impl IntoIterator<Item = AnyElement>,
-        cx: &Context<Self>,
-    ) -> AnyElement {
-        let is_expanded = self.expanded_edits.contains(&expanded_id);
-
-        v_flex()
-            .child(
-                h_flex()
-                    .bg(cx.theme().colors().element_background)
-                    .border_1()
-                    .border_color(cx.theme().colors().border)
-                    .rounded_t_md()
-                    .when(!is_expanded, |el| el.rounded_b_md())
-                    .py_1()
-                    .px_2()
-                    .gap_1()
-                    .child(
-                        ui::Disclosure::new(ElementId::Integer(expanded_id.1), is_expanded)
-                            .on_click(cx.listener(move |this, _ev, _window, cx| {
-                                if is_expanded {
-                                    this.expanded_edits.remove(&expanded_id);
-                                } else {
-                                    this.expanded_edits.insert(expanded_id);
-                                }
-
-                                cx.notify();
-                            })),
-                    )
-                    .child(Label::new(file_path.display().to_string()).size(LabelSize::Small)),
-            )
-            .child(if is_expanded {
-                h_flex()
-                    .border_1()
-                    .border_t_0()
-                    .border_color(cx.theme().colors().border)
-                    .rounded_b_md()
-                    .children(content)
-                    .into_any()
-            } else {
-                Empty.into_any()
-            })
-            .into_any()
-    }
-
-    fn render_block(icon: IconName, title: &'static str, content: String, cx: &App) -> Div {
-        v_flex()
-            .p_1()
-            .gap_1()
-            .flex_1()
-            .h_full()
-            .child(
-                h_flex()
-                    .gap_1()
-                    .child(Icon::new(icon).color(Color::Muted))
-                    .child(Label::new(title).size(LabelSize::Small).color(Color::Muted)),
-            )
-            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
-            .text_sm()
-            .child(content)
-            .child(div().flex_1())
-    }
-}
-
-impl EventEmitter<()> for EditToolLogViewer {}
-
-impl Focusable for EditToolLogViewer {
-    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
-        self.focus_handle.clone()
-    }
-}
-
-impl Item for EditToolLogViewer {
-    type Event = ();
-
-    fn to_item_events(_: &Self::Event, _: impl FnMut(ItemEvent)) {}
-
-    fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
-        Some("Edit Tool Log".into())
-    }
-
-    fn telemetry_event_text(&self) -> Option<&'static str> {
-        None
-    }
-
-    fn clone_on_split(
-        &self,
-        _workspace_id: Option<WorkspaceId>,
-        _window: &mut Window,
-        cx: &mut Context<Self>,
-    ) -> Option<Entity<Self>>
-    where
-        Self: Sized,
-    {
-        Some(cx.new(Self::new))
-    }
-}
-
-impl Render for EditToolLogViewer {
-    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
-        if self.list_state.item_count() == 0 {
-            return v_flex()
-                .justify_center()
-                .size_full()
-                .gap_1()
-                .bg(cx.theme().colors().editor_background)
-                .text_center()
-                .text_lg()
-                .child("No requests yet")
-                .child(
-                    div()
-                        .text_ui(cx)
-                        .child("Go ask the assistant to perform some edits"),
-                );
-        }
-
-        v_flex()
-            .p_4()
-            .bg(cx.theme().colors().editor_background)
-            .size_full()
-            .child(list(self.list_state.clone()).flex_grow())
-    }
-}