11
11
logger = logging .getLogger (__name__ )
12
12
13
13
14
- def _create_error_response (
15
- error_message : str ,
16
- content_hash : Optional [str ] = None ,
17
- file_path : Optional [str ] = None ,
18
- ) -> Dict [str , Any ]:
19
- """Create a standardized error response.
20
-
21
- Args:
22
- error_message (str): The error message to include
23
- content_hash (Optional[str], optional): Hash of the current content if available
24
- file_path (Optional[str], optional): File path to use as dictionary key
25
-
26
- Returns:
27
- Dict[str, Any]: Standardized error response structure
28
- """
29
- error_response = {
30
- "result" : "error" ,
31
- "reason" : error_message ,
32
- "hash" : content_hash ,
33
- }
34
-
35
- if file_path :
36
- return {file_path : error_response }
37
- return error_response
38
-
39
-
40
14
class TextEditor :
41
15
"""Handles text file operations with security checks and conflict detection."""
42
16
@@ -45,6 +19,44 @@ def __init__(self):
45
19
self ._validate_environment ()
46
20
self .service = TextEditorService ()
47
21
22
+ def create_error_response (
23
+ self ,
24
+ error_message : str ,
25
+ content_hash : Optional [str ] = None ,
26
+ file_path : Optional [str ] = None ,
27
+ suggestion : Optional [str ] = None ,
28
+ hint : Optional [str ] = None ,
29
+ ) -> Dict [str , Any ]:
30
+ """Create a standardized error response.
31
+
32
+ Args:
33
+ error_message (str): The error message to include
34
+ content_hash (Optional[str], optional): Hash of the current content if available
35
+ file_path (Optional[str], optional): File path to use as dictionary key
36
+ suggestion (Optional[str], optional): Suggested operation type
37
+ hint (Optional[str], optional): Hint message for users
38
+
39
+ Returns:
40
+ Dict[str, Any]: Standardized error response structure
41
+ """
42
+ error_response = {
43
+ "result" : "error" ,
44
+ "reason" : error_message ,
45
+ "file_hash" : content_hash ,
46
+ }
47
+
48
+ # Add fields if provided
49
+ if content_hash is not None :
50
+ error_response ["file_hash" ] = content_hash
51
+ if suggestion :
52
+ error_response ["suggestion" ] = suggestion
53
+ if hint :
54
+ error_response ["hint" ] = hint
55
+
56
+ if file_path :
57
+ return {file_path : error_response }
58
+ return error_response
59
+
48
60
def _validate_environment (self ) -> None :
49
61
"""
50
62
Validate environment variables and setup.
@@ -241,20 +253,22 @@ async def edit_file_contents(
241
253
try :
242
254
if not os .path .exists (file_path ):
243
255
if expected_hash not in ["" , None ]: # Allow null hash
244
- return {
245
- "result" : "error" ,
246
- "reason" : "File not found and non-empty hash provided" ,
247
- }
256
+ return self .create_error_response (
257
+ "File not found and non-empty hash provided" ,
258
+ suggestion = "append" ,
259
+ hint = "For new files, please consider using append_text_file_contents" ,
260
+ )
248
261
# Create parent directories if they don't exist
249
262
parent_dir = os .path .dirname (file_path )
250
263
if parent_dir :
251
264
try :
252
265
os .makedirs (parent_dir , exist_ok = True )
253
266
except OSError as e :
254
- return {
255
- "result" : "error" ,
256
- "reason" : f"Failed to create directory: { str (e )} " ,
257
- }
267
+ return self .create_error_response (
268
+ f"Failed to create directory: { str (e )} " ,
269
+ suggestion = "patch" ,
270
+ hint = "Please check file permissions and try again" ,
271
+ )
258
272
# Initialize empty state for new file
259
273
current_content = ""
260
274
current_hash = ""
@@ -277,17 +291,21 @@ async def edit_file_contents(
277
291
current_hash = ""
278
292
lines = []
279
293
elif current_content and expected_hash == "" :
280
- return {
281
- "result" : "error" ,
282
- "reason" : "Unexpected error - Cannot treat existing file as new" ,
283
- }
294
+ return self .create_error_response (
295
+ "Unexpected error - Cannot treat existing file as new" ,
296
+ )
284
297
elif current_hash != expected_hash :
285
- return {
286
- "result" : "error" ,
287
- "reason" : "FileHash mismatch - Please use get_text_file_contents tool to get current content and hashes, then retry with the updated hashes." ,
288
- }
298
+ suggestion = "patch"
299
+ hint = "Please use get_text_file_contents tool to get the current content and hash"
300
+
301
+ return self .create_error_response (
302
+ "FileHash mismatch - Please use get_text_file_contents tool to get current content and hashes, then retry with the updated hashes." ,
303
+ suggestion = suggestion ,
304
+ hint = hint ,
305
+ )
289
306
else :
290
307
lines = current_content .splitlines (keepends = True )
308
+ lines = current_content .splitlines (keepends = True )
291
309
292
310
# Convert patches to EditPatch objects
293
311
patch_objects = [EditPatch .model_validate (p ) for p in patches ]
@@ -314,10 +332,11 @@ async def edit_file_contents(
314
332
if (start1 <= end2 and end1 >= start2 ) or (
315
333
start2 <= end1 and end2 >= start1
316
334
):
317
- return {
318
- "result" : "error" ,
319
- "reason" : "Overlapping patches detected" ,
320
- }
335
+ return self .create_error_response (
336
+ "Overlapping patches detected" ,
337
+ suggestion = "patch" ,
338
+ hint = "Please ensure your patches do not overlap" ,
339
+ )
321
340
322
341
# Apply patches
323
342
for patch in sorted_patches :
@@ -364,7 +383,6 @@ async def edit_file_contents(
364
383
# New file or empty file - treat as insertion
365
384
is_insertion = True
366
385
elif start_zero >= len (lines ):
367
- # Append mode - start exceeds total lines
368
386
is_insertion = True
369
387
else :
370
388
# For modification mode, check the range_hash
@@ -386,6 +404,8 @@ async def edit_file_contents(
386
404
return {
387
405
"result" : "error" ,
388
406
"reason" : "Content range hash mismatch - Please use get_text_file_contents tool with the same start and end to get current content and hashes, then retry with the updated hashes." ,
407
+ "suggestion" : "get" ,
408
+ "hint" : "Please run get_text_file_contents first to get current content and hashes" ,
389
409
}
390
410
391
411
# Prepare new content
@@ -404,18 +424,18 @@ async def edit_file_contents(
404
424
}
405
425
406
426
# Set suggestions for alternative tools
407
- suggestion = None
408
- hint = None
427
+ suggestion_text : Optional [ str ] = None
428
+ hint_text : Optional [ str ] = None
409
429
if not os .path .exists (file_path ) or not current_content :
410
- suggestion = "append"
411
- hint = "For new or empty files, please consider using append_text_file_contents instead"
430
+ suggestion_text = "append"
431
+ hint_text = "For new or empty files, please consider using append_text_file_contents instead"
412
432
elif is_insertion :
413
433
if start_zero >= len (lines ):
414
- suggestion = "append"
415
- hint = "For adding content at the end of file, please consider using append_text_file_contents instead"
434
+ suggestion_text = "append"
435
+ hint_text = "For adding content at the end of file, please consider using append_text_file_contents instead"
416
436
else :
417
- suggestion = "insert"
418
- hint = "For inserting content within file, please consider using insert_text_file_contents instead"
437
+ suggestion_text = "insert"
438
+ hint_text = "For inserting content within file, please consider using insert_text_file_contents instead"
419
439
420
440
# Prepare the content
421
441
new_content = contents if contents .endswith ("\n " ) else contents + "\n "
@@ -441,24 +461,32 @@ async def edit_file_contents(
441
461
"result" : "ok" ,
442
462
"file_hash" : new_hash ,
443
463
"reason" : None ,
444
- "suggestion" : suggestion ,
445
- "hint" : hint ,
464
+ "suggestion" : suggestion_text ,
465
+ "hint" : hint_text ,
446
466
}
447
467
448
468
except FileNotFoundError :
449
- return {"result" : "error" , "reason" : f"File not found: { file_path } " }
469
+ return self .create_error_response (
470
+ f"File not found: { file_path } " ,
471
+ suggestion = "append" ,
472
+ hint = "For new files, please use append_text_file_contents" ,
473
+ )
450
474
except (IOError , UnicodeError , PermissionError ) as e :
451
- return {"result" : "error" , "reason" : f"Error editing file: { str (e )} " }
475
+ return self .create_error_response (
476
+ f"Error editing file: { str (e )} " ,
477
+ suggestion = "patch" ,
478
+ hint = "Please check file permissions and try again" ,
479
+ )
452
480
except Exception as e :
453
481
import traceback
454
482
455
483
logger .error (f"Error: { str (e )} " )
456
484
logger .error (f"Traceback:\n { traceback .format_exc ()} " )
457
- return {
458
- "result" : " error" ,
459
- "reason" : "Unexpected error occurred " ,
460
- "file_hash" : None ,
461
- }
485
+ return self . create_error_response (
486
+ "Unexpected error occurred " ,
487
+ suggestion = "patch " ,
488
+ hint = "Please try again or report the issue if it persists" ,
489
+ )
462
490
463
491
async def insert_text_file_contents (
464
492
self ,
0 commit comments