@@ -29,6 +29,18 @@ type EditContentResult = {
2929 data ?: Record < string , unknown >
3030}
3131
32+ const CONSUMED_FILE_INTENT_RETRY_GUIDANCE =
33+ 'The workspace_file intent was consumed; call workspace_file again before retrying edit_content.'
34+
35+ function consumedFileIntentFailure ( message : string ) : EditContentResult {
36+ const normalizedMessage = message . trimEnd ( )
37+ const separator = / [ . ! ? ] $ / . test ( normalizedMessage ) ? ' ' : '. '
38+ return {
39+ success : false ,
40+ message : `${ normalizedMessage } ${ separator } ${ CONSUMED_FILE_INTENT_RETRY_GUIDANCE } ` ,
41+ }
42+ }
43+
3244export const editContentServerTool : BaseServerTool < EditContentArgs , EditContentResult > = {
3345 name : 'edit_content' ,
3446 async execute ( params : EditContentArgs , context ?: ServerToolContext ) : Promise < EditContentResult > {
@@ -105,17 +117,16 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
105117 case 'patch' : {
106118 const existing = intent . existingContent ?? ''
107119 if ( ! intent . edit ) {
108- return { success : false , message : 'Patch intent missing edit metadata' }
120+ return consumedFileIntentFailure ( 'Patch intent missing edit metadata' )
109121 }
110122
111123 if ( intent . edit . strategy === 'search_replace' ) {
112124 const search = intent . edit . search !
113125 const firstIdx = existing . indexOf ( search )
114126 if ( firstIdx === - 1 ) {
115- return {
116- success : false ,
117- message : `Patch failed: search string not found in file "${ fileRecord . name } "` ,
118- }
127+ return consumedFileIntentFailure (
128+ `Patch failed: search string not found in file "${ fileRecord . name } "`
129+ )
119130 }
120131 finalContent = intent . edit . replaceAll
121132 ? existing . split ( search ) . join ( content )
@@ -151,24 +162,26 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
151162
152163 if ( intent . edit . mode === 'replace_between' ) {
153164 if ( ! intent . edit . before_anchor || ! intent . edit . after_anchor ) {
154- return {
155- success : false ,
156- message : 'replace_between requires before_anchor and after_anchor' ,
157- }
165+ return consumedFileIntentFailure (
166+ 'replace_between requires before_anchor and after_anchor'
167+ )
158168 }
159169 const before = findAnchorLine ( intent . edit . before_anchor )
160- if ( before . error ) return { success : false , message : `Patch failed: ${ before . error } ` }
170+ if ( before . error ) {
171+ return consumedFileIntentFailure ( `Patch failed: ${ before . error } ` )
172+ }
161173 const after = findAnchorLine (
162174 intent . edit . after_anchor ,
163175 defaultOccurrence ,
164176 before . index
165177 )
166- if ( after . error ) return { success : false , message : `Patch failed: ${ after . error } ` }
178+ if ( after . error ) {
179+ return consumedFileIntentFailure ( `Patch failed: ${ after . error } ` )
180+ }
167181 if ( after . index <= before . index ) {
168- return {
169- success : false ,
170- message : 'Patch failed: after_anchor must appear after before_anchor in the file' ,
171- }
182+ return consumedFileIntentFailure (
183+ 'Patch failed: after_anchor must appear after before_anchor in the file'
184+ )
172185 }
173186 const newLines = [
174187 ...lines . slice ( 0 , before . index + 1 ) ,
@@ -178,10 +191,12 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
178191 finalContent = newLines . join ( '\n' )
179192 } else if ( intent . edit . mode === 'insert_after' ) {
180193 if ( ! intent . edit . anchor ) {
181- return { success : false , message : 'insert_after requires anchor' }
194+ return consumedFileIntentFailure ( 'insert_after requires anchor' )
182195 }
183196 const found = findAnchorLine ( intent . edit . anchor )
184- if ( found . error ) return { success : false , message : `Patch failed: ${ found . error } ` }
197+ if ( found . error ) {
198+ return consumedFileIntentFailure ( `Patch failed: ${ found . error } ` )
199+ }
185200 const newLines = [
186201 ...lines . slice ( 0 , found . index + 1 ) ,
187202 ...content . split ( '\n' ) ,
@@ -190,36 +205,35 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
190205 finalContent = newLines . join ( '\n' )
191206 } else if ( intent . edit . mode === 'delete_between' ) {
192207 if ( ! intent . edit . start_anchor || ! intent . edit . end_anchor ) {
193- return {
194- success : false ,
195- message : 'delete_between requires start_anchor and end_anchor' ,
196- }
208+ return consumedFileIntentFailure (
209+ 'delete_between requires start_anchor and end_anchor'
210+ )
197211 }
198212 const start = findAnchorLine ( intent . edit . start_anchor )
199- if ( start . error ) return { success : false , message : `Patch failed: ${ start . error } ` }
213+ if ( start . error ) {
214+ return consumedFileIntentFailure ( `Patch failed: ${ start . error } ` )
215+ }
200216 const end = findAnchorLine ( intent . edit . end_anchor , defaultOccurrence , start . index )
201- if ( end . error ) return { success : false , message : `Patch failed: ${ end . error } ` }
217+ if ( end . error ) {
218+ return consumedFileIntentFailure ( `Patch failed: ${ end . error } ` )
219+ }
202220 if ( end . index <= start . index ) {
203- return {
204- success : false ,
205- message : 'Patch failed: end_anchor must appear after start_anchor in the file' ,
206- }
221+ return consumedFileIntentFailure (
222+ 'Patch failed: end_anchor must appear after start_anchor in the file'
223+ )
207224 }
208225 const newLines = [ ...lines . slice ( 0 , start . index ) , ...lines . slice ( end . index ) ]
209226 finalContent = newLines . join ( '\n' )
210227 } else {
211- return {
212- success : false ,
213- message : `Unknown anchored patch mode: "${ intent . edit . mode } "` ,
214- }
228+ return consumedFileIntentFailure ( `Unknown anchored patch mode: "${ intent . edit . mode } "` )
215229 }
216230 } else {
217- return { success : false , message : `Unknown patch strategy: "${ intent . edit . strategy } "` }
231+ return consumedFileIntentFailure ( `Unknown patch strategy: "${ intent . edit . strategy } "` )
218232 }
219233 break
220234 }
221235 default :
222- return { success : false , message : `Unsupported operation in intent: ${ operation } ` }
236+ return consumedFileIntentFailure ( `Unsupported operation in intent: ${ operation } ` )
223237 }
224238
225239 // Compile once via the right engine (or isolated-vm fallback) and resolve
@@ -235,7 +249,7 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
235249 fallbackMime : inferContentType ( fileRecord . name , intent . contentType ) ,
236250 } )
237251 if ( ! compiled . ok ) {
238- return { success : false , message : compiled . message }
252+ return consumedFileIntentFailure ( compiled . message )
239253 }
240254
241255 const fileBuffer = Buffer . from ( finalContent , 'utf-8' )
@@ -290,10 +304,7 @@ export const editContentServerTool: BaseServerTool<EditContentArgs, EditContentR
290304 error : errorMessage ,
291305 userId : context . userId ,
292306 } )
293- return {
294- success : false ,
295- message : safeMessage ,
296- }
307+ return consumedFileIntentFailure ( safeMessage )
297308 }
298309 } ,
299310}
0 commit comments