mirror of
https://github.com/Aider-AI/aider.git
synced 2025-06-09 06:05:00 +00:00
refactor: Improve error handling and reporting in tool executions
This commit is contained in:
parent
10d0f90cb5
commit
9b56e1f099
4 changed files with 104 additions and 143 deletions
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import traceback
|
||||
from .tool_utils import generate_unified_diff_snippet
|
||||
from .tool_utils import ToolError, generate_unified_diff_snippet, handle_tool_error, format_tool_result, apply_change
|
||||
|
||||
def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=False):
|
||||
"""
|
||||
|
@ -15,6 +15,8 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=
|
|||
|
||||
Returns a result message.
|
||||
"""
|
||||
|
||||
tool_name = "DeleteLine"
|
||||
try:
|
||||
# Get absolute file path
|
||||
abs_path = coder.abs_root_path(file_path)
|
||||
|
@ -22,23 +24,19 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=
|
|||
|
||||
# Check if file exists
|
||||
if not os.path.isfile(abs_path):
|
||||
coder.io.tool_error(f"File '{file_path}' not found")
|
||||
return f"Error: File not found"
|
||||
raise ToolError(f"File '{file_path}' not found")
|
||||
|
||||
# Check if file is in editable context
|
||||
if abs_path not in coder.abs_fnames:
|
||||
if abs_path in coder.abs_read_only_fnames:
|
||||
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
return f"Error: File is read-only. Use MakeEditable first."
|
||||
raise ToolError(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
else:
|
||||
coder.io.tool_error(f"File '{file_path}' not in context")
|
||||
return f"Error: File not in context"
|
||||
raise ToolError(f"File '{file_path}' not in context")
|
||||
|
||||
# Reread file content immediately before modification
|
||||
file_content = coder.io.read_text(abs_path)
|
||||
if file_content is None:
|
||||
coder.io.tool_error(f"Could not read file '{file_path}' before DeleteLine operation.")
|
||||
return f"Error: Could not read file '{file_path}'"
|
||||
raise ToolError(f"Could not read file '{file_path}'")
|
||||
|
||||
lines = file_content.splitlines()
|
||||
original_content = file_content
|
||||
|
@ -47,11 +45,10 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=
|
|||
try:
|
||||
line_num_int = int(line_number)
|
||||
if line_num_int < 1 or line_num_int > len(lines):
|
||||
raise ValueError(f"Line number {line_num_int} is out of range (1-{len(lines)})")
|
||||
raise ToolError(f"Line number {line_num_int} is out of range (1-{len(lines)})")
|
||||
line_idx = line_num_int - 1 # Convert to 0-based index
|
||||
except ValueError as e:
|
||||
coder.io.tool_error(f"Invalid line_number: {e}")
|
||||
return f"Error: Invalid line_number '{line_number}'"
|
||||
except ValueError:
|
||||
raise ToolError(f"Invalid line_number value: '{line_number}'. Must be an integer.")
|
||||
|
||||
# Prepare the deletion
|
||||
deleted_line = lines[line_idx]
|
||||
|
@ -62,40 +59,34 @@ def _execute_delete_line(coder, file_path, line_number, change_id=None, dry_run=
|
|||
coder.io.tool_warning(f"No changes made: deleting line {line_num_int} would not change file")
|
||||
return f"Warning: No changes made (deleting line {line_num_int} would not change file)"
|
||||
|
||||
# Generate diff snippet (using the existing delete block helper for simplicity)
|
||||
# Generate diff snippet
|
||||
diff_snippet = generate_unified_diff_snippet(original_content, new_content, rel_path)
|
||||
|
||||
# Handle dry run
|
||||
if dry_run:
|
||||
coder.io.tool_output(f"Dry run: Would delete line {line_num_int} in {file_path}")
|
||||
return f"Dry run: Would delete line {line_num_int}. Diff snippet:\n{diff_snippet}"
|
||||
dry_run_message = f"Dry run: Would delete line {line_num_int} in {file_path}"
|
||||
return format_tool_result(coder, tool_name, "", dry_run=True, dry_run_message=dry_run_message, diff_snippet=diff_snippet)
|
||||
|
||||
# --- Apply Change (Not dry run) ---
|
||||
coder.io.write_text(abs_path, new_content)
|
||||
|
||||
# Track the change
|
||||
try:
|
||||
metadata = {
|
||||
'line_number': line_num_int,
|
||||
'deleted_content': deleted_line
|
||||
}
|
||||
change_id = coder.change_tracker.track_change(
|
||||
file_path=rel_path,
|
||||
change_type='deleteline',
|
||||
original_content=original_content,
|
||||
new_content=new_content,
|
||||
metadata=metadata,
|
||||
change_id=change_id
|
||||
)
|
||||
except Exception as track_e:
|
||||
coder.io.tool_error(f"Error tracking change for DeleteLine: {track_e}")
|
||||
change_id = "TRACKING_FAILED"
|
||||
metadata = {
|
||||
'line_number': line_num_int,
|
||||
'deleted_content': deleted_line
|
||||
}
|
||||
final_change_id = apply_change(
|
||||
coder, abs_path, rel_path, original_content, new_content, 'deleteline', metadata, change_id
|
||||
)
|
||||
|
||||
coder.aider_edited_files.add(rel_path)
|
||||
|
||||
coder.io.tool_output(f"✅ Deleted line {line_num_int} in {file_path} (change_id: {change_id})")
|
||||
return f"Successfully deleted line {line_num_int} (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
||||
# Format and return result
|
||||
success_message = f"Deleted line {line_num_int} in {file_path}"
|
||||
return format_tool_result(
|
||||
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||
)
|
||||
|
||||
except ToolError as e:
|
||||
# Handle errors raised by utility functions (expected errors)
|
||||
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
||||
except Exception as e:
|
||||
coder.io.tool_error(f"Error in DeleteLine: {str(e)}\n{traceback.format_exc()}")
|
||||
return f"Error: {str(e)}"
|
||||
# Handle unexpected errors
|
||||
return handle_tool_error(coder, tool_name, e)
|
|
@ -1,6 +1,6 @@
|
|||
import os
|
||||
import traceback
|
||||
from .tool_utils import generate_unified_diff_snippet
|
||||
from .tool_utils import ToolError, generate_unified_diff_snippet, handle_tool_error, format_tool_result, apply_change
|
||||
|
||||
def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None, dry_run=False):
|
||||
"""
|
||||
|
@ -16,6 +16,7 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None
|
|||
|
||||
Returns a result message.
|
||||
"""
|
||||
tool_name = "DeleteLines"
|
||||
try:
|
||||
# Get absolute file path
|
||||
abs_path = coder.abs_root_path(file_path)
|
||||
|
@ -23,23 +24,19 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None
|
|||
|
||||
# Check if file exists
|
||||
if not os.path.isfile(abs_path):
|
||||
coder.io.tool_error(f"File '{file_path}' not found")
|
||||
return f"Error: File not found"
|
||||
raise ToolError(f"File '{file_path}' not found")
|
||||
|
||||
# Check if file is in editable context
|
||||
if abs_path not in coder.abs_fnames:
|
||||
if abs_path in coder.abs_read_only_fnames:
|
||||
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
return f"Error: File is read-only. Use MakeEditable first."
|
||||
raise ToolError(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
else:
|
||||
coder.io.tool_error(f"File '{file_path}' not in context")
|
||||
return f"Error: File not in context"
|
||||
raise ToolError(f"File '{file_path}' not in context")
|
||||
|
||||
# Reread file content immediately before modification
|
||||
file_content = coder.io.read_text(abs_path)
|
||||
if file_content is None:
|
||||
coder.io.tool_error(f"Could not read file '{file_path}' before DeleteLines operation.")
|
||||
return f"Error: Could not read file '{file_path}'"
|
||||
raise ToolError(f"Could not read file '{file_path}'")
|
||||
|
||||
lines = file_content.splitlines()
|
||||
original_content = file_content
|
||||
|
@ -50,17 +47,16 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None
|
|||
end_line_int = int(end_line)
|
||||
|
||||
if start_line_int < 1 or start_line_int > len(lines):
|
||||
raise ValueError(f"Start line {start_line_int} is out of range (1-{len(lines)})")
|
||||
raise ToolError(f"Start line {start_line_int} is out of range (1-{len(lines)})")
|
||||
if end_line_int < 1 or end_line_int > len(lines):
|
||||
raise ValueError(f"End line {end_line_int} is out of range (1-{len(lines)})")
|
||||
raise ToolError(f"End line {end_line_int} is out of range (1-{len(lines)})")
|
||||
if start_line_int > end_line_int:
|
||||
raise ValueError(f"Start line {start_line_int} cannot be after end line {end_line_int}")
|
||||
raise ToolError(f"Start line {start_line_int} cannot be after end line {end_line_int}")
|
||||
|
||||
start_idx = start_line_int - 1 # Convert to 0-based index
|
||||
end_idx = end_line_int - 1 # Convert to 0-based index
|
||||
except ValueError as e:
|
||||
coder.io.tool_error(f"Invalid line numbers: {e}")
|
||||
return f"Error: Invalid line numbers '{start_line}', '{end_line}'"
|
||||
except ValueError:
|
||||
raise ToolError(f"Invalid line numbers: '{start_line}', '{end_line}'. Must be integers.")
|
||||
|
||||
# Prepare the deletion
|
||||
deleted_lines = lines[start_idx:end_idx+1]
|
||||
|
@ -76,37 +72,31 @@ def _execute_delete_lines(coder, file_path, start_line, end_line, change_id=None
|
|||
|
||||
# Handle dry run
|
||||
if dry_run:
|
||||
coder.io.tool_output(f"Dry run: Would delete lines {start_line_int}-{end_line_int} in {file_path}")
|
||||
return f"Dry run: Would delete lines {start_line_int}-{end_line_int}. Diff snippet:\n{diff_snippet}"
|
||||
dry_run_message = f"Dry run: Would delete lines {start_line_int}-{end_line_int} in {file_path}"
|
||||
return format_tool_result(coder, tool_name, "", dry_run=True, dry_run_message=dry_run_message, diff_snippet=diff_snippet)
|
||||
|
||||
# --- Apply Change (Not dry run) ---
|
||||
coder.io.write_text(abs_path, new_content)
|
||||
metadata = {
|
||||
'start_line': start_line_int,
|
||||
'end_line': end_line_int,
|
||||
'deleted_content': '\n'.join(deleted_lines)
|
||||
}
|
||||
|
||||
# Track the change
|
||||
try:
|
||||
metadata = {
|
||||
'start_line': start_line_int,
|
||||
'end_line': end_line_int,
|
||||
'deleted_content': '\n'.join(deleted_lines)
|
||||
}
|
||||
change_id = coder.change_tracker.track_change(
|
||||
file_path=rel_path,
|
||||
change_type='deletelines',
|
||||
original_content=original_content,
|
||||
new_content=new_content,
|
||||
metadata=metadata,
|
||||
change_id=change_id
|
||||
)
|
||||
except Exception as track_e:
|
||||
coder.io.tool_error(f"Error tracking change for DeleteLines: {track_e}")
|
||||
change_id = "TRACKING_FAILED"
|
||||
final_change_id = apply_change(
|
||||
coder, abs_path, rel_path, original_content, new_content, 'deletelines', metadata, change_id
|
||||
)
|
||||
|
||||
coder.aider_edited_files.add(rel_path)
|
||||
|
||||
num_deleted = end_idx - start_idx + 1
|
||||
coder.io.tool_output(f"✅ Deleted {num_deleted} lines ({start_line_int}-{end_line_int}) in {file_path} (change_id: {change_id})")
|
||||
return f"Successfully deleted {num_deleted} lines ({start_line_int}-{end_line_int}) (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
||||
# Format and return result
|
||||
success_message = f"Deleted {num_deleted} lines ({start_line_int}-{end_line_int}) in {file_path}"
|
||||
return format_tool_result(
|
||||
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||
)
|
||||
|
||||
except ToolError as e:
|
||||
# Handle errors raised by utility functions (expected errors)
|
||||
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
||||
except Exception as e:
|
||||
coder.io.tool_error(f"Error in DeleteLines: {str(e)}\n{traceback.format_exc()}")
|
||||
return f"Error: {str(e)}"
|
||||
# Handle unexpected errors
|
||||
return handle_tool_error(coder, tool_name, e)
|
|
@ -116,16 +116,9 @@ def _execute_indent_lines(coder, file_path, start_pattern, end_pattern=None, lin
|
|||
return format_tool_result(
|
||||
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||
)
|
||||
|
||||
except ToolError as e:
|
||||
# Handle errors raised by utility functions (expected errors)
|
||||
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
||||
except Exception as e:
|
||||
# Handle unexpected errors
|
||||
return handle_tool_error(coder, tool_name, e)
|
||||
coder.io.tool_output(f"✅ {action} {num_lines} lines (from {occurrence_str}start pattern) by {levels} {level_text} in {file_path} (change_id: {change_id})")
|
||||
return f"Successfully {action.lower()} {num_lines} lines by {levels} {level_text} (change_id: {change_id}). Diff snippet:\n{diff_snippet}"
|
||||
|
||||
except Exception as e:
|
||||
coder.io.tool_error(f"Error in IndentLines: {str(e)}\n{traceback.format_exc()}")
|
||||
return f"Error: {str(e)}"
|
|
@ -1,7 +1,6 @@
|
|||
import os
|
||||
import traceback
|
||||
from .tool_utils import generate_unified_diff_snippet
|
||||
from .tool_utils import generate_unified_diff_snippet
|
||||
from .tool_utils import ToolError, generate_unified_diff_snippet, handle_tool_error, format_tool_result, apply_change
|
||||
|
||||
def _execute_replace_lines(coder, file_path, start_line, end_line, new_content, change_id=None, dry_run=False):
|
||||
"""
|
||||
|
@ -18,6 +17,7 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content,
|
|||
|
||||
Returns a result message.
|
||||
"""
|
||||
tool_name = "ReplaceLines"
|
||||
try:
|
||||
# Get absolute file path
|
||||
abs_path = coder.abs_root_path(file_path)
|
||||
|
@ -25,38 +25,30 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content,
|
|||
|
||||
# Check if file exists
|
||||
if not os.path.isfile(abs_path):
|
||||
coder.io.tool_error(f"File '{file_path}' not found")
|
||||
return f"Error: File not found"
|
||||
raise ToolError(f"File '{file_path}' not found")
|
||||
|
||||
# Check if file is in editable context
|
||||
if abs_path not in coder.abs_fnames:
|
||||
if abs_path in coder.abs_read_only_fnames:
|
||||
coder.io.tool_error(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
return f"Error: File is read-only. Use MakeEditable first."
|
||||
raise ToolError(f"File '{file_path}' is read-only. Use MakeEditable first.")
|
||||
else:
|
||||
coder.io.tool_error(f"File '{file_path}' not in context")
|
||||
return f"Error: File not in context"
|
||||
raise ToolError(f"File '{file_path}' not in context")
|
||||
|
||||
# Reread file content immediately before modification
|
||||
file_content = coder.io.read_text(abs_path)
|
||||
if file_content is None:
|
||||
coder.io.tool_error(f"Could not read file '{file_path}' before ReplaceLines operation.")
|
||||
return f"Error: Could not read file '{file_path}'"
|
||||
raise ToolError(f"Could not read file '{file_path}'")
|
||||
|
||||
# Convert line numbers to integers if needed
|
||||
if not isinstance(start_line, int):
|
||||
try:
|
||||
start_line = int(start_line)
|
||||
except ValueError:
|
||||
coder.io.tool_error(f"Invalid start_line value: '{start_line}'. Must be an integer.")
|
||||
return f"Error: Invalid start_line value '{start_line}'"
|
||||
try:
|
||||
start_line = int(start_line)
|
||||
except ValueError:
|
||||
raise ToolError(f"Invalid start_line value: '{start_line}'. Must be an integer.")
|
||||
|
||||
if not isinstance(end_line, int):
|
||||
try:
|
||||
end_line = int(end_line)
|
||||
except ValueError:
|
||||
coder.io.tool_error(f"Invalid end_line value: '{end_line}'. Must be an integer.")
|
||||
return f"Error: Invalid end_line value '{end_line}'"
|
||||
try:
|
||||
end_line = int(end_line)
|
||||
except ValueError:
|
||||
raise ToolError(f"Invalid end_line value: '{end_line}'. Must be an integer.")
|
||||
|
||||
# Split into lines
|
||||
lines = file_content.splitlines()
|
||||
|
@ -64,14 +56,13 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content,
|
|||
# Convert 1-based line numbers to 0-based indices
|
||||
start_idx = start_line - 1
|
||||
end_idx = end_line - 1
|
||||
|
||||
# Validate line numbers
|
||||
if start_idx < 0 or start_idx >= len(lines):
|
||||
coder.io.tool_error(f"Start line {start_line} is out of range for file '{file_path}' (has {len(lines)} lines).")
|
||||
return f"Error: Start line {start_line} out of range"
|
||||
raise ToolError(f"Start line {start_line} is out of range for file '{file_path}' (has {len(lines)} lines).")
|
||||
|
||||
if end_idx < start_idx or end_idx >= len(lines):
|
||||
coder.io.tool_error(f"End line {end_line} is out of range for file '{file_path}' (must be >= start line {start_line} and <= {len(lines)}).")
|
||||
return f"Error: End line {end_line} out of range"
|
||||
raise ToolError(f"End line {end_line} is out of range for file '{file_path}' (must be >= start line {start_line} and <= {len(lines)}).")
|
||||
|
||||
# Store original content for change tracking
|
||||
original_content = file_content
|
||||
|
@ -87,6 +78,8 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content,
|
|||
if original_content == new_content_full:
|
||||
coder.io.tool_warning("No changes made: new content is identical to original")
|
||||
return f"Warning: No changes made (new content identical to original)"
|
||||
|
||||
# Generate diff snippet
|
||||
diff_snippet = generate_unified_diff_snippet(original_content, new_content_full, rel_path)
|
||||
|
||||
# Create a readable diff for the lines replacement
|
||||
|
@ -102,40 +95,34 @@ def _execute_replace_lines(coder, file_path, start_line, end_line, new_content,
|
|||
|
||||
# Handle dry run
|
||||
if dry_run:
|
||||
coder.io.tool_output(f"Dry run: Would replace lines {start_line}-{end_line} in {file_path}")
|
||||
return f"Dry run: Would replace lines {start_line}-{end_line}. Diff snippet:\n{diff_snippet}"
|
||||
dry_run_message = f"Dry run: Would replace lines {start_line}-{end_line} in {file_path}"
|
||||
return format_tool_result(coder, tool_name, "", dry_run=True, dry_run_message=dry_run_message, diff_snippet=diff_snippet)
|
||||
|
||||
# --- Apply Change (Not dry run) ---
|
||||
coder.io.write_text(abs_path, new_content_full)
|
||||
metadata = {
|
||||
'start_line': start_line,
|
||||
'end_line': end_line,
|
||||
'replaced_lines': replaced_lines,
|
||||
'new_lines': new_lines
|
||||
}
|
||||
|
||||
# Track the change
|
||||
try:
|
||||
metadata = {
|
||||
'start_line': start_line,
|
||||
'end_line': end_line,
|
||||
'replaced_lines': replaced_lines,
|
||||
'new_lines': new_lines
|
||||
}
|
||||
change_id = coder.change_tracker.track_change(
|
||||
file_path=rel_path,
|
||||
change_type='replacelines',
|
||||
original_content=original_content,
|
||||
new_content=new_content_full,
|
||||
metadata=metadata,
|
||||
change_id=change_id
|
||||
)
|
||||
except Exception as track_e:
|
||||
coder.io.tool_error(f"Error tracking change for ReplaceLines: {track_e}")
|
||||
change_id = "TRACKING_FAILED"
|
||||
final_change_id = apply_change(
|
||||
coder, abs_path, rel_path, original_content, new_content_full, 'replacelines', metadata, change_id
|
||||
)
|
||||
|
||||
coder.aider_edited_files.add(rel_path)
|
||||
replaced_count = end_line - start_line + 1
|
||||
new_count = len(new_lines)
|
||||
|
||||
# Improve feedback
|
||||
coder.io.tool_output(f"✅ Replaced lines {start_line}-{end_line} ({replaced_count} lines) with {new_count} new lines in {file_path} (change_id: {change_id})")
|
||||
return f"Successfully replaced lines {start_line}-{end_line} with {new_count} new lines (change_id: {change_id}). Diff:\n{diff}"
|
||||
# Format and return result
|
||||
success_message = f"Replaced lines {start_line}-{end_line} ({replaced_count} lines) with {new_count} new lines in {file_path}"
|
||||
return format_tool_result(
|
||||
coder, tool_name, success_message, change_id=final_change_id, diff_snippet=diff_snippet
|
||||
)
|
||||
|
||||
except ToolError as e:
|
||||
# Handle errors raised by utility functions (expected errors)
|
||||
return handle_tool_error(coder, tool_name, e, add_traceback=False)
|
||||
except Exception as e:
|
||||
coder.io.tool_error(f"Error in ReplaceLines: {str(e)}\n{traceback.format_exc()}")
|
||||
return f"Error: {str(e)}"
|
||||
# Handle unexpected errors
|
||||
return handle_tool_error(coder, tool_name, e)
|
Loading…
Add table
Add a link
Reference in a new issue