Skip to main content

steer_core/tools/static_tools/
edit.rs

1use super::workspace_op_error;
2use crate::tools::capability::Capabilities;
3use crate::tools::static_tool::{StaticTool, StaticToolContext, StaticToolError};
4use async_trait::async_trait;
5use steer_tools::result::{EditResult, MultiEditResult};
6use steer_tools::tools::edit::multi_edit::{MultiEditError, MultiEditParams, MultiEditToolSpec};
7use steer_tools::tools::edit::{EditError, EditParams, EditToolSpec};
8use steer_workspace::{ApplyEditsRequest, EditOperation, WorkspaceOpContext};
9
10pub struct EditTool;
11
12const EDIT_DESCRIPTION: &str = r"This is a tool for editing files. For moving or renaming files, you should generally use the Bash tool with the 'mv' command instead. For larger edits, use the replace tool to overwrite files.
13
14Before using this tool:
15
161. Use the View tool to understand the file's contents and context
17
182. Verify the directory path is correct (only applicable when creating new files):
19 - Use the LS tool to verify the parent directory exists and is the correct location
20
21To make a file edit, provide the following:
221. file_path: The absolute path to the file to modify (must be absolute, not relative)
232. old_string: The text to replace (must be unique within the file, and must match the file contents exactly, including all whitespace and indentation)
243. new_string: The edited text to replace the old_string
25
26The tool will replace ONE occurrence of old_string with new_string in the specified file.
27
28CRITICAL REQUIREMENTS FOR USING THIS TOOL:
29
301. UNIQUENESS: The old_string MUST uniquely identify the specific instance you want to change. This means:
31 - Include AT LEAST 3-5 lines of context BEFORE the change point
32 - Include AT LEAST 3-5 lines of context AFTER the change point
33 - Include all whitespace, indentation, and surrounding code exactly as it appears in the file
34
352. SINGLE INSTANCE: This tool can only change ONE instance at a time. If you need to change multiple instances:
36 - Make separate calls to this tool for each instance
37 - Each call must uniquely identify its specific instance using extensive context
38
393. VERIFICATION: Before using this tool:
40 - Check how many instances of the target text exist in the file
41 - If multiple instances exist, gather enough context to uniquely identify each one
42 - Plan separate tool calls for each instance
43
44WARNING: If you do not follow these requirements:
45 - The tool will fail if old_string matches multiple locations
46 - The tool will fail if old_string doesn't match exactly (including whitespace)
47 - You may change the wrong instance if you don't include enough context
48
49When making edits:
50 - Ensure the edit results in idiomatic, correct code
51 - Do not leave the code in a broken state
52 - Always use absolute file paths (starting with /)
53
54If you want to create a new file, use:
55 - A new file path, including dir name if needed
56 - An empty old_string
57 - The new file's contents as new_string
58
59Remember: when making multiple file edits in a row to the same file, you should prefer to send all edits in a single message with multiple calls to this tool, rather than multiple messages with a single call each.";
60
61#[async_trait]
62impl StaticTool for EditTool {
63    type Params = EditParams;
64    type Output = EditResult;
65    type Spec = EditToolSpec;
66
67    const DESCRIPTION: &'static str = EDIT_DESCRIPTION;
68    const REQUIRES_APPROVAL: bool = true;
69    const REQUIRED_CAPABILITIES: Capabilities = Capabilities::WORKSPACE;
70
71    async fn execute(
72        &self,
73        params: Self::Params,
74        ctx: &StaticToolContext,
75    ) -> Result<Self::Output, StaticToolError<EditError>> {
76        let request = ApplyEditsRequest {
77            file_path: params.file_path,
78            edits: vec![EditOperation {
79                old_string: params.old_string,
80                new_string: params.new_string,
81            }],
82        };
83        let op_ctx =
84            WorkspaceOpContext::new(ctx.tool_call_id.0.clone(), ctx.cancellation_token.clone());
85        ctx.services
86            .workspace
87            .apply_edits(request, &op_ctx)
88            .await
89            .map_err(|e| StaticToolError::execution(EditError::Workspace(workspace_op_error(e))))
90    }
91}
92
93pub struct MultiEditTool;
94
95#[async_trait]
96impl StaticTool for MultiEditTool {
97    type Params = MultiEditParams;
98    type Output = MultiEditResult;
99    type Spec = MultiEditToolSpec;
100
101    const DESCRIPTION: &'static str = "This is a tool for making multiple edits to a single file in one operation. Prefer this tool over the edit_file tool when you need to make multiple edits to the same file.";
102    const REQUIRES_APPROVAL: bool = true;
103    const REQUIRED_CAPABILITIES: Capabilities = Capabilities::WORKSPACE;
104
105    async fn execute(
106        &self,
107        params: Self::Params,
108        ctx: &StaticToolContext,
109    ) -> Result<Self::Output, StaticToolError<MultiEditError>> {
110        let request = ApplyEditsRequest {
111            file_path: params.file_path,
112            edits: params
113                .edits
114                .into_iter()
115                .map(|e| EditOperation {
116                    old_string: e.old_string,
117                    new_string: e.new_string,
118                })
119                .collect(),
120        };
121        let op_ctx =
122            WorkspaceOpContext::new(ctx.tool_call_id.0.clone(), ctx.cancellation_token.clone());
123        let result = ctx
124            .services
125            .workspace
126            .apply_edits(request, &op_ctx)
127            .await
128            .map_err(|e| {
129                StaticToolError::execution(MultiEditError::Workspace(workspace_op_error(e)))
130            })?;
131        Ok(MultiEditResult(result))
132    }
133}