@@ -3,11 +3,11 @@ use std::path::{
33 PathBuf ,
44} ;
55
6- use schemars:: JsonSchema ;
76use serde:: {
87 Deserialize ,
98 Serialize ,
109} ;
10+ use syntect:: util:: LinesWithEndings ;
1111
1212use super :: {
1313 BuiltInToolName ,
@@ -17,8 +17,8 @@ use super::{
1717} ;
1818use crate :: agent:: util:: path:: canonicalize_path;
1919
20- const FILE_WRITE_TOOL_DESCRIPTION : & str = r#"
21- A tool for creating and editing files.
20+ const FS_WRITE_TOOL_DESCRIPTION : & str = r#"
21+ A tool for creating and editing text files.
2222
2323WHEN TO USE THIS TOOL:
2424- Use when you need to create a new file, or modify an existing file
@@ -33,9 +33,10 @@ HOW TO USE:
3333
3434TIPS:
3535- Read the file first before making modifications to ensure you have the most up-to-date version of the file.
36+ - To append content to the end of a file, use `insert` with no `insert_line`
3637"# ;
3738
38- const FILE_WRITE_SCHEMA : & str = r#"
39+ const FS_WRITE_SCHEMA : & str = r#"
3940{
4041 "type": "object",
4142 "properties": {
@@ -53,7 +54,7 @@ const FILE_WRITE_SCHEMA: &str = r#"
5354 "type": "string"
5455 },
5556 "insert_line": {
56- "description": "Required parameter of `insert` command. The `content` will be inserted AFTER the line `insert_line` of `path` .",
57+ "description": "Optional parameter of `insert` command. Line is 0-indexed. `content` will be inserted at the provided line. If not provided, content will be inserted at the end of the file on a new line, inserting a newline at the end of the file if it is missing .",
5758 "type": "integer"
5859 },
5960 "new_str": {
@@ -76,27 +77,38 @@ const FILE_WRITE_SCHEMA: &str = r#"
7677}
7778"# ;
7879
79- impl BuiltInToolTrait for FileWrite {
80- const DESCRIPTION : & str = FILE_WRITE_TOOL_DESCRIPTION ;
81- const INPUT_SCHEMA : & str = FILE_WRITE_SCHEMA ;
82- const NAME : BuiltInToolName = BuiltInToolName :: FileWrite ;
80+ #[ cfg( unix) ]
81+ const NEWLINE : & str = "\n " ;
82+
83+ impl BuiltInToolTrait for FsWrite {
84+ fn name ( ) -> BuiltInToolName {
85+ BuiltInToolName :: FsWrite
86+ }
87+
88+ fn description ( ) -> std:: borrow:: Cow < ' static , str > {
89+ FS_WRITE_TOOL_DESCRIPTION . into ( )
90+ }
91+
92+ fn input_schema ( ) -> std:: borrow:: Cow < ' static , str > {
93+ FS_WRITE_SCHEMA . into ( )
94+ }
8395}
8496
8597#[ derive( Debug , Clone , Serialize , Deserialize ) ]
8698#[ serde( rename_all = "camelCase" ) ]
8799#[ serde( tag = "command" ) ]
88- pub enum FileWrite {
100+ pub enum FsWrite {
89101 Create ( FileCreate ) ,
90102 StrReplace ( StrReplace ) ,
91103 Insert ( Insert ) ,
92104}
93105
94- impl FileWrite {
106+ impl FsWrite {
95107 pub fn path ( & self ) -> & str {
96108 match self {
97- FileWrite :: Create ( v) => & v. path ,
98- FileWrite :: StrReplace ( v) => & v. path ,
99- FileWrite :: Insert ( v) => & v. path ,
109+ FsWrite :: Create ( v) => & v. path ,
110+ FsWrite :: StrReplace ( v) => & v. path ,
111+ FsWrite :: Insert ( v) => & v. path ,
100112 }
101113 }
102114
@@ -114,15 +126,15 @@ impl FileWrite {
114126 }
115127
116128 match & self {
117- FileWrite :: Create ( _) => ( ) ,
118- FileWrite :: StrReplace ( _) => {
129+ FsWrite :: Create ( _) => ( ) ,
130+ FsWrite :: StrReplace ( _) => {
119131 if !self . canonical_path ( ) ?. exists ( ) {
120132 errors. push (
121133 "The provided path must exist in order to replace or insert contents into it" . to_string ( ) ,
122134 ) ;
123135 }
124136 } ,
125- FileWrite :: Insert ( v) => {
137+ FsWrite :: Insert ( v) => {
126138 if v. content . is_empty ( ) {
127139 errors. push ( "Content to insert must not be empty" . to_string ( ) ) ;
128140 }
@@ -136,35 +148,27 @@ impl FileWrite {
136148 }
137149 }
138150
139- pub async fn make_context ( & self ) -> eyre:: Result < FileWriteContext > {
151+ pub async fn make_context ( & self ) -> eyre:: Result < FsWriteContext > {
140152 // TODO - return file diff context
141- Ok ( FileWriteContext {
153+ Ok ( FsWriteContext {
142154 path : self . path ( ) . to_string ( ) ,
143155 } )
144156 }
145157
146- pub async fn execute ( & self , _state : Option < & mut FileWriteState > ) -> ToolExecutionResult {
158+ pub async fn execute ( & self , _state : Option < & mut FsWriteState > ) -> ToolExecutionResult {
147159 let path = self . canonical_path ( ) . map_err ( ToolExecutionError :: Custom ) ?;
148160
149161 match & self {
150- FileWrite :: Create ( v) => v. execute ( path) . await ?,
151- FileWrite :: StrReplace ( v) => v. execute ( path) . await ?,
152- FileWrite :: Insert ( v) => v. execute ( path) . await ?,
162+ FsWrite :: Create ( v) => v. execute ( path) . await ?,
163+ FsWrite :: StrReplace ( v) => v. execute ( path) . await ?,
164+ FsWrite :: Insert ( v) => v. execute ( path) . await ?,
153165 }
154166
155167 Ok ( Default :: default ( ) )
156168 }
157169}
158170
159- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
160- #[ serde( rename_all = "camelCase" ) ]
161- pub enum FileWriteOp {
162- Create ( FileCreate ) ,
163- StrReplace ( StrReplace ) ,
164- Insert ( Insert ) ,
165- }
166-
167- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
171+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
168172pub struct FileCreate {
169173 path : String ,
170174 content : String ,
@@ -190,7 +194,7 @@ impl FileCreate {
190194 }
191195}
192196
193- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
197+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
194198#[ serde( rename_all = "camelCase" ) ]
195199pub struct StrReplace {
196200 path : String ,
@@ -238,7 +242,7 @@ impl StrReplace {
238242 }
239243}
240244
241- #[ derive( Debug , Clone , Serialize , Deserialize , JsonSchema ) ]
245+ #[ derive( Debug , Clone , Serialize , Deserialize ) ]
242246#[ serde( rename_all = "camelCase" ) ]
243247pub struct Insert {
244248 path : String ,
@@ -247,20 +251,53 @@ pub struct Insert {
247251}
248252
249253impl Insert {
250- async fn execute ( & self , _path : impl AsRef < Path > ) -> Result < ( ) , ToolExecutionError > {
251- panic ! ( "unimplemented" )
254+ async fn execute ( & self , path : impl AsRef < Path > ) -> Result < ( ) , ToolExecutionError > {
255+ let path = path. as_ref ( ) ;
256+
257+ let mut file = tokio:: fs:: read_to_string ( path)
258+ . await
259+ . map_err ( |e| ToolExecutionError :: io ( format ! ( "failed to read {}" , path. to_string_lossy( ) ) , e) ) ?;
260+
261+ let line_count = file. lines ( ) . count ( ) as u32 ;
262+
263+ if let Some ( insert_line) = self . insert_line {
264+ let insert_line = insert_line. clamp ( 0 , line_count) ;
265+
266+ // Get the index to insert at.
267+ let mut i = 0 ;
268+ for line in LinesWithEndings :: from ( & file) . take ( insert_line as usize ) {
269+ i += line. len ( ) ;
270+ }
271+
272+ let mut content = self . content . clone ( ) ;
273+ if !content. ends_with ( NEWLINE ) {
274+ content. push_str ( NEWLINE ) ;
275+ }
276+ file. insert_str ( i, & content) ;
277+ } else {
278+ if !file. ends_with ( NEWLINE ) {
279+ file. push_str ( NEWLINE ) ;
280+ }
281+ file. push_str ( & self . content ) ;
282+ }
283+
284+ tokio:: fs:: write ( path, file)
285+ . await
286+ . map_err ( |e| ToolExecutionError :: io ( format ! ( "failed to write to {}" , path. to_string_lossy( ) ) , e) ) ?;
287+
288+ Ok ( ( ) )
252289 }
253290}
254291
255292#[ derive( Debug , Clone , Serialize , Deserialize ) ]
256293#[ serde( rename_all = "camelCase" ) ]
257- pub struct FileWriteContext {
294+ pub struct FsWriteContext {
258295 path : String ,
259296}
260297
261298#[ derive( Debug , Clone , Default , Serialize , Deserialize ) ]
262299#[ serde( rename_all = "camelCase" ) ]
263- pub struct FileWriteState {
300+ pub struct FsWriteState {
264301 pub line_tracker : FileLineTracker ,
265302}
266303
0 commit comments