Skip to main content

steer_tools/tools/
edit.rs

1use schemars::JsonSchema;
2use serde::{Deserialize, Serialize};
3use thiserror::Error;
4
5use crate::ToolSpec;
6use crate::error::{ToolExecutionError, WorkspaceOpError};
7use crate::result::{EditResult, MultiEditResult};
8
9pub const EDIT_TOOL_NAME: &str = "edit_file";
10
11pub struct EditToolSpec;
12
13impl ToolSpec for EditToolSpec {
14    type Params = EditParams;
15    type Result = EditResult;
16    type Error = EditError;
17
18    const NAME: &'static str = EDIT_TOOL_NAME;
19    const DISPLAY_NAME: &'static str = "Edit File";
20
21    fn execution_error(error: Self::Error) -> ToolExecutionError {
22        ToolExecutionError::Edit(error)
23    }
24}
25
26#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Error)]
27#[serde(tag = "code", content = "details", rename_all = "snake_case")]
28pub enum EditFailure {
29    #[error("file not found: {file_path}")]
30    FileNotFound { file_path: String },
31
32    #[error(
33        "edit #{edit_index} has an empty old_string; use write_file to create or overwrite files"
34    )]
35    EmptyOldString { edit_index: usize },
36
37    #[error("string not found for edit #{edit_index} in file {file_path}")]
38    StringNotFound {
39        file_path: String,
40        edit_index: usize,
41    },
42
43    #[error(
44        "found {occurrences} matches for edit #{edit_index} in file {file_path}; old_string must match exactly once"
45    )]
46    NonUniqueMatch {
47        file_path: String,
48        edit_index: usize,
49        occurrences: usize,
50    },
51}
52
53#[derive(Deserialize, Serialize, Debug, JsonSchema, Clone, Error)]
54#[serde(tag = "code", content = "details", rename_all = "snake_case")]
55pub enum EditError {
56    #[error("{0}")]
57    Workspace(WorkspaceOpError),
58
59    #[error("{0}")]
60    EditFailure(EditFailure),
61}
62
63#[derive(Deserialize, Serialize, Debug, JsonSchema, Clone)]
64pub struct SingleEditOperation {
65    /// The exact string to find and replace. Must be non-empty and match exactly one location.
66    pub old_string: String,
67    /// The string to replace `old_string` with.
68    pub new_string: String,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema)]
72pub struct EditParams {
73    /// The absolute path to the file to edit
74    pub file_path: String,
75    /// The exact string to find and replace. Must be non-empty.
76    pub old_string: String,
77    /// The string to replace `old_string` with.
78    pub new_string: String,
79}
80
81pub mod multi_edit {
82    use super::{
83        Deserialize, EditFailure, Error, JsonSchema, MultiEditResult, Serialize,
84        SingleEditOperation, ToolExecutionError, ToolSpec, WorkspaceOpError,
85    };
86
87    pub const MULTI_EDIT_TOOL_NAME: &str = "multi_edit";
88
89    pub struct MultiEditToolSpec;
90
91    impl ToolSpec for MultiEditToolSpec {
92        type Params = MultiEditParams;
93        type Result = MultiEditResult;
94        type Error = MultiEditError;
95
96        const NAME: &'static str = MULTI_EDIT_TOOL_NAME;
97        const DISPLAY_NAME: &'static str = "Multi Edit";
98
99        fn execution_error(error: Self::Error) -> ToolExecutionError {
100            ToolExecutionError::MultiEdit(error)
101        }
102    }
103
104    #[derive(Deserialize, Serialize, Debug, JsonSchema, Clone, Error)]
105    #[serde(tag = "code", content = "details", rename_all = "snake_case")]
106    pub enum MultiEditError {
107        #[error("{0}")]
108        Workspace(WorkspaceOpError),
109
110        #[error("{0}")]
111        EditFailure(EditFailure),
112    }
113
114    #[derive(Deserialize, Serialize, Debug, JsonSchema)]
115    pub struct MultiEditParams {
116        /// The absolute path to the file to edit.
117        pub file_path: String,
118        /// A list of edit operations to apply sequentially.
119        pub edits: Vec<SingleEditOperation>,
120    }
121}