@@ -13,6 +13,7 @@ use eyre::{
1313} ;
1414use fig_os_shim:: Context ;
1515use serde:: Deserialize ;
16+ use tracing:: warn;
1617
1718use super :: {
1819 InvokeOutput ,
@@ -24,8 +25,14 @@ use super::{
2425#[ derive( Debug , Clone , Deserialize ) ]
2526#[ serde( tag = "command" ) ]
2627pub enum FsWrite {
28+ /// The tool spec should only require `file_text`, but the model sometimes doesn't want to
29+ /// provide it. Thus, including `new_str` as a fallback check, if it's available.
2730 #[ serde( rename = "create" ) ]
28- Create { path : String , file_text : String } ,
31+ Create {
32+ path : String ,
33+ file_text : Option < String > ,
34+ new_str : Option < String > ,
35+ } ,
2936 #[ serde( rename = "str_replace" ) ]
3037 StrReplace {
3138 path : String ,
@@ -45,18 +52,15 @@ impl FsWrite {
4552 let fs = ctx. fs ( ) ;
4653 let cwd = ctx. env ( ) . current_dir ( ) ?;
4754 match self {
48- FsWrite :: Create { path, file_text } => {
55+ FsWrite :: Create { path, .. } => {
56+ let file_text = self . canonical_create_command_text ( ) ;
4957 let path = sanitize_path_tool_arg ( ctx, path) ;
50- let relative_path = format_path ( cwd, fs. chroot_path ( & path) ) ;
51- let invoke_description = if fs. exists ( & path) {
52- "Replacing the current file contents at"
53- } else {
54- "Creating a new file at"
55- } ;
58+ let invoke_description = if fs. exists ( & path) { "Replacing: " } else { "Creating: " } ;
5659 queue ! (
5760 updates,
61+ style:: Print ( invoke_description) ,
5862 style:: SetForegroundColor ( Color :: Green ) ,
59- style:: Print ( format! ( "{} {}" , invoke_description , relative_path ) ) ,
63+ style:: Print ( format_path ( cwd , & path ) ) ,
6064 style:: ResetColor ,
6165 style:: Print ( "\n " ) ,
6266 ) ?;
@@ -69,8 +73,9 @@ impl FsWrite {
6973 let matches = file. match_indices ( old_str) . collect :: < Vec < _ > > ( ) ;
7074 queue ! (
7175 updates,
76+ style:: Print ( "Updating: " ) ,
7277 style:: SetForegroundColor ( Color :: Green ) ,
73- style:: Print ( format! ( "Updating {}" , format_path( cwd, & path) ) ) ,
78+ style:: Print ( format_path( cwd, & path) ) ,
7479 style:: ResetColor ,
7580 style:: Print ( "\n " ) ,
7681 ) ?;
@@ -89,19 +94,16 @@ impl FsWrite {
8994 insert_line,
9095 new_str,
9196 } => {
97+ let path = sanitize_path_tool_arg ( ctx, path) ;
98+ let mut file = fs. read_to_string ( & path) . await ?;
9299 queue ! (
93100 updates,
101+ style:: Print ( "Updating: " ) ,
94102 style:: SetForegroundColor ( Color :: Green ) ,
95- style:: Print ( format!(
96- "Inserting at line {} in {}" ,
97- insert_line,
98- format_path( cwd, path)
99- ) ) ,
103+ style:: Print ( format_path( cwd, & path) ) ,
100104 style:: ResetColor ,
101105 style:: Print ( "\n " ) ,
102106 ) ?;
103- let path = fs. chroot_path_str ( path) ;
104- let mut file = fs. read_to_string ( & path) . await ?;
105107
106108 // Get the index of the start of the line to insert at.
107109 let num_lines = file. lines ( ) . enumerate ( ) . map ( |( i, _) | i + 1 ) . last ( ) . unwrap_or ( 1 ) ;
@@ -121,7 +123,8 @@ impl FsWrite {
121123 pub fn queue_description ( & self , ctx : & Context , updates : & mut impl Write ) -> Result < ( ) > {
122124 let cwd = ctx. env ( ) . current_dir ( ) ?;
123125 match self {
124- FsWrite :: Create { path, file_text } => {
126+ FsWrite :: Create { path, .. } => {
127+ let file_text = self . canonical_create_command_text ( ) ;
125128 let relative_path = format_path ( cwd, path) ;
126129 queue ! (
127130 updates,
@@ -134,7 +137,7 @@ impl FsWrite {
134137 if ctx. fs ( ) . exists ( path) {
135138 let prev = ctx. fs ( ) . read_to_string_sync ( path) ?;
136139 let prev = stylize_output_if_able ( ctx, & relative_path, prev. as_str ( ) , None , Some ( "-" ) ) ;
137- let new = stylize_output_if_able ( ctx, & relative_path, file_text, None , Some ( "+" ) ) ;
140+ let new = stylize_output_if_able ( ctx, & relative_path, & file_text, None , Some ( "+" ) ) ;
138141 queue ! (
139142 updates,
140143 style:: Print ( "Replacing:\n " ) ,
@@ -147,7 +150,7 @@ impl FsWrite {
147150 style:: Print ( "\n \n " )
148151 ) ?;
149152 } else {
150- let file = stylize_output_if_able ( ctx, & relative_path, file_text, None , None ) ;
153+ let file = stylize_output_if_able ( ctx, & relative_path, & file_text, None , None ) ;
151154 queue ! (
152155 updates,
153156 style:: Print ( "Contents:\n " ) ,
@@ -223,6 +226,25 @@ impl FsWrite {
223226
224227 Ok ( ( ) )
225228 }
229+
230+ /// Returns the text to use for the [FsWrite::Create] command. This is required since we can't
231+ /// rely on the model always providing `file_text`.
232+ fn canonical_create_command_text ( & self ) -> String {
233+ match self {
234+ FsWrite :: Create { file_text, new_str, .. } => match ( file_text, new_str) {
235+ ( Some ( file_text) , _) => file_text. clone ( ) ,
236+ ( None , Some ( new_str) ) => {
237+ warn ! ( "required field `file_text` is missing, using the provided `new_str` instead" ) ;
238+ new_str. clone ( )
239+ } ,
240+ _ => {
241+ warn ! ( "no content provided for the create command" ) ;
242+ String :: new ( )
243+ } ,
244+ } ,
245+ _ => String :: new ( ) ,
246+ }
247+ }
226248}
227249
228250/// Limits the passed str to `max_len`.
@@ -358,6 +380,20 @@ mod tests {
358380 . unwrap ( ) ;
359381
360382 assert_eq ! ( ctx. fs( ) . read_to_string( "/my-file" ) . await . unwrap( ) , file_text) ;
383+
384+ let file_text = "This is a new string" ;
385+ let v = serde_json:: json!( {
386+ "path" : "/my-file" ,
387+ "command" : "create" ,
388+ "new_str" : file_text
389+ } ) ;
390+ serde_json:: from_value :: < FsWrite > ( v)
391+ . unwrap ( )
392+ . invoke ( & ctx, & mut stdout)
393+ . await
394+ . unwrap ( ) ;
395+
396+ assert_eq ! ( ctx. fs( ) . read_to_string( "/my-file" ) . await . unwrap( ) , file_text) ;
361397 }
362398
363399 #[ tokio:: test]
0 commit comments