@@ -157,6 +157,10 @@ async def read_file_contents(
157
157
lines , file_content , total_lines = await self ._read_file (
158
158
file_path , encoding = encoding
159
159
)
160
+
161
+ if line_end is not None and line_end < line_start :
162
+ raise ValueError ("End line must be greater than or equal to start line" )
163
+
160
164
line_start = max (1 , line_start ) - 1
161
165
line_end = total_lines if line_end is None else min (line_end , total_lines )
162
166
@@ -245,22 +249,6 @@ async def edit_file_contents(
245
249
current_content = ""
246
250
current_hash = ""
247
251
lines : List [str ] = []
248
- # Create parent directories if they don't exist
249
- parent_dir = os .path .dirname (file_path )
250
- if parent_dir :
251
- try :
252
- os .makedirs (parent_dir , exist_ok = True )
253
- except OSError as e :
254
- return {
255
- "result" : "error" ,
256
- "reason" : f"Failed to create directory: { str (e )} " ,
257
- "file_hash" : None ,
258
- "content" : None ,
259
- }
260
- # Initialize empty state for new file creation
261
- current_content = ""
262
- current_hash = ""
263
- lines = []
264
252
encoding = "utf-8"
265
253
else :
266
254
# Read current file content and verify hash
@@ -273,6 +261,15 @@ async def edit_file_contents(
273
261
current_content = ""
274
262
current_hash = ""
275
263
lines = []
264
+ elif current_hash != expected_hash :
265
+ lines = []
266
+ elif current_content and expected_hash == "" :
267
+ return {
268
+ "result" : "error" ,
269
+ "reason" : "Unexpected error" ,
270
+ "file_hash" : None ,
271
+ "content" : None ,
272
+ }
276
273
elif current_hash != expected_hash :
277
274
return {
278
275
"result" : "error" ,
@@ -281,7 +278,6 @@ async def edit_file_contents(
281
278
"content" : current_content ,
282
279
}
283
280
else :
284
- # Convert content to lines for easier manipulation
285
281
lines = current_content .splitlines (keepends = True )
286
282
287
283
# Sort patches from bottom to top to avoid line number shifts
@@ -318,39 +314,83 @@ async def edit_file_contents(
318
314
# Get line numbers (1-based)
319
315
line_start = patch .get ("line_start" , 1 )
320
316
line_end = patch .get ("line_end" , line_start )
317
+
318
+ # Check for invalid line range
319
+ if line_end is not None and line_end < line_start :
320
+ return {
321
+ "result" : "error" ,
322
+ "reason" : "End line must be greater than or equal to start line" ,
323
+ "file_hash" : None ,
324
+ "content" : current_content ,
325
+ }
326
+
327
+ # Handle unexpected empty hash for existing file
328
+ if (
329
+ os .path .exists (file_path )
330
+ and current_content
331
+ and expected_hash == ""
332
+ ):
333
+ return {
334
+ "result" : "error" ,
335
+ "reason" : "Unexpected error" ,
336
+ "file_hash" : None ,
337
+ "content" : current_content ,
338
+ }
339
+
340
+ # Get expected hash for validation
321
341
expected_range_hash = patch .get ("range_hash" )
322
- is_insertion = False if line_end is None else line_end < line_start
323
342
324
- # Skip range_hash for new files, empty files and insertions
343
+ # Determine if this is an insertion operation
344
+ # Cases:
345
+ # 1. New file
346
+ # 2. Empty file
347
+ # 3. Empty range_hash (explicit insertion)
348
+ is_insertion = (
349
+ not os .path .exists (file_path )
350
+ or not current_content
351
+ or expected_range_hash == ""
352
+ or patch .get ("range_hash" ) == self .calculate_hash ("" )
353
+ )
354
+
355
+ # Skip range_hash check for insertions
356
+ if is_insertion :
357
+ expected_range_hash = ""
358
+
359
+ # For existing, non-empty files and non-insertions, range_hash is required
360
+ if is_insertion :
361
+ expected_range_hash = ""
362
+
363
+ # For existing, non-empty files and non-insertions, range_hash is required
325
364
if not os .path .exists (file_path ) or not current_content or is_insertion :
326
- expected_range_hash = self .calculate_hash ("" )
365
+ expected_range_hash = ""
366
+
327
367
# For existing, non-empty files and non-insertions, range_hash is required
328
- elif expected_range_hash is None :
368
+ elif not expected_range_hash :
329
369
return {
330
370
"result" : "error" ,
331
- "reason" : "range_hash is required for each patch (except for new files and append operations )" ,
371
+ "reason" : "range_hash is required for each patch (except for new files and insertions )" ,
332
372
"hash" : None ,
333
373
"content" : current_content ,
334
374
}
335
375
336
376
# Handle insertion or replacement
337
377
if is_insertion :
338
378
target_content = "" # For insertion, we verify empty content
379
+ line_end = line_start # For insertion operations
339
380
else :
340
381
# Convert to 0-based indexing for existing content
341
382
line_start_zero = line_start - 1
383
+ line_end_zero = (
384
+ len (lines )
385
+ if line_end is None
386
+ else min (line_end - 1 , len (lines ) - 1 )
387
+ )
342
388
343
389
# Calculate target content for hash verification
344
390
if line_start_zero >= len (lines ):
345
391
target_content = ""
346
392
else :
347
- # If line_end is None, we read until the end of the file
348
- if line_end is None :
349
- target_lines = lines [line_start_zero :]
350
- else :
351
- # Adjust to 0-based indexing and make inclusive
352
- line_end_zero = min (line_end - 1 , len (lines ) - 1 )
353
- target_lines = lines [line_start_zero : line_end_zero + 1 ]
393
+ target_lines = lines [line_start_zero : line_end_zero + 1 ]
354
394
target_content = "" .join (target_lines )
355
395
356
396
# Calculate actual range hash and verify only for non-insertions
@@ -359,34 +399,37 @@ async def edit_file_contents(
359
399
return {
360
400
"result" : "error" ,
361
401
"reason" : f"Range hash mismatch for lines { line_start } -{ line_end if line_end else len (lines )} ({ actual_range_hash } != { expected_range_hash } )" ,
362
- "hash " : None ,
402
+ "file_hash " : None ,
363
403
"content" : current_content ,
364
404
}
365
405
366
- # Convert to 0-based indexing for modification
367
- line_start -= 1
368
- if not is_insertion :
369
- # Handle line_end consistently with hash verification
370
- if line_end is None :
371
- # When line_end is None, replace until the end
372
- line_end = len (lines )
373
- else :
374
- line_end = min (line_end , len (lines ))
406
+ # Convert to 0-based indexing
407
+ line_start = line_start - 1
408
+
409
+ # Calculate effective end line for operations
410
+ if is_insertion :
411
+ effective_line_end = line_start
412
+ else :
413
+ effective_line_end = (
414
+ len (lines ) if line_end is None else line_end - 1
415
+ )
375
416
376
- # Apply the changes
417
+ # Prepare new content
377
418
new_content = patch ["contents" ]
378
419
if not new_content .endswith ("\n " ):
379
420
new_content += "\n "
380
421
new_lines = new_content .splitlines (keepends = True )
381
422
423
+ # Apply changes depending on operation type
382
424
if is_insertion :
383
425
lines [line_start :line_start ] = new_lines
384
426
else :
385
427
# For replacement, we replace the range
386
- lines [line_start :line_end ] = new_lines
428
+ effective_line_end = len (lines ) if line_end is None else line_end
429
+ lines [line_start :effective_line_end ] = new_lines
387
430
388
431
print (f"patch: { patch } " )
389
- print (f"line_end : { line_end } " )
432
+ print (f"is_insertion : { is_insertion } " )
390
433
print (f"is_insertion: { is_insertion } " )
391
434
392
435
# Write the final content back to file
@@ -424,7 +467,7 @@ async def edit_file_contents(
424
467
print (f"Traceback:\n { traceback .format_exc ()} " )
425
468
return {
426
469
"result" : "error" ,
427
- "reason" : str ( e ) ,
470
+ "reason" : "Unexpected error occurred" ,
428
471
"file_hash" : None ,
429
472
"content" : None ,
430
473
}
0 commit comments