- Implemented ReAct loop in agent.py - Added Anthropic provider using httpx (no SDK) - Created tools: read, write, edit, bash - Added session management with JSONL format - Included configuration system with env var support - Added CLI interface and example usage - Minimal dependencies: only httpx
91 lines
3.4 KiB
Python
91 lines
3.4 KiB
Python
"""
|
|
Edit file tool (find and replace)
|
|
"""
|
|
|
|
import os
|
|
from typing import Dict, Any
|
|
from .registry import Tool
|
|
|
|
|
|
class EditTool(Tool):
|
|
"""Tool for editing files using find and replace"""
|
|
|
|
def __init__(self):
|
|
super().__init__(
|
|
name="edit",
|
|
description="Edit a file by finding and replacing exact text. The old_string must match exactly (including whitespace).",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_path": {
|
|
"type": "string",
|
|
"description": "Path to the file to edit (relative or absolute)"
|
|
},
|
|
"old_string": {
|
|
"type": "string",
|
|
"description": "Exact text to find and replace (must match exactly)"
|
|
},
|
|
"new_string": {
|
|
"type": "string",
|
|
"description": "New text to replace the old text with"
|
|
}
|
|
},
|
|
"required": ["file_path", "old_string", "new_string"]
|
|
}
|
|
)
|
|
|
|
async def execute(self, args: Dict[str, Any]) -> str:
|
|
"""Execute the edit tool"""
|
|
file_path = args.get("file_path")
|
|
old_string = args.get("old_string")
|
|
new_string = args.get("new_string")
|
|
|
|
if not file_path or old_string is None or new_string is None:
|
|
return "Error: file_path, old_string, and new_string are required"
|
|
|
|
try:
|
|
# Convert to absolute path if relative
|
|
if not os.path.isabs(file_path):
|
|
file_path = os.path.abspath(file_path)
|
|
|
|
if not os.path.exists(file_path):
|
|
return f"Error: File '{file_path}' does not exist"
|
|
|
|
if not os.path.isfile(file_path):
|
|
return f"Error: '{file_path}' is not a file"
|
|
|
|
# Read the current content
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
content = f.read()
|
|
|
|
# Check if old_string exists in the content
|
|
if old_string not in content:
|
|
return f"Error: The specified text was not found in '{file_path}'"
|
|
|
|
# Count occurrences
|
|
occurrence_count = content.count(old_string)
|
|
|
|
# Perform the replacement
|
|
new_content = content.replace(old_string, new_string)
|
|
|
|
# Write the modified content back
|
|
with open(file_path, 'w', encoding='utf-8') as f:
|
|
f.write(new_content)
|
|
|
|
# Calculate changes
|
|
old_lines = len(content.splitlines())
|
|
new_lines = len(new_content.splitlines())
|
|
line_diff = new_lines - old_lines
|
|
|
|
result_parts = [
|
|
f"Successfully edited '{file_path}':",
|
|
f"- Replaced {occurrence_count} occurrence(s) of the specified text",
|
|
f"- File now has {new_lines} lines ({line_diff:+d} lines)"
|
|
]
|
|
|
|
return "\n".join(result_parts)
|
|
|
|
except UnicodeDecodeError:
|
|
return f"Error: Could not read file '{file_path}' - file appears to be binary"
|
|
except Exception as e:
|
|
return f"Error: Could not edit file '{file_path}': {e}" |