- 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
94 lines
3.3 KiB
Python
94 lines
3.3 KiB
Python
"""
|
|
Read file tool
|
|
"""
|
|
|
|
import os
|
|
from typing import Dict, Any
|
|
from .registry import Tool
|
|
|
|
|
|
class ReadTool(Tool):
|
|
"""Tool for reading files with optional offset/limit"""
|
|
|
|
def __init__(self):
|
|
super().__init__(
|
|
name="read",
|
|
description="Read the contents of a file. Supports optional offset/limit for reading specific lines.",
|
|
parameters={
|
|
"type": "object",
|
|
"properties": {
|
|
"file_path": {
|
|
"type": "string",
|
|
"description": "Path to the file to read (relative or absolute)"
|
|
},
|
|
"offset": {
|
|
"type": "integer",
|
|
"description": "Line number to start reading from (1-indexed, optional)",
|
|
"minimum": 1
|
|
},
|
|
"limit": {
|
|
"type": "integer",
|
|
"description": "Maximum number of lines to read (optional)",
|
|
"minimum": 1
|
|
}
|
|
},
|
|
"required": ["file_path"]
|
|
}
|
|
)
|
|
|
|
async def execute(self, args: Dict[str, Any]) -> str:
|
|
"""Execute the read tool"""
|
|
file_path = args.get("file_path")
|
|
offset = args.get("offset", 1) # 1-indexed
|
|
limit = args.get("limit")
|
|
|
|
if not file_path:
|
|
return "Error: file_path is 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"
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as f:
|
|
lines = f.readlines()
|
|
|
|
# Apply offset (convert from 1-indexed to 0-indexed)
|
|
start_idx = max(0, offset - 1)
|
|
|
|
# Apply limit
|
|
if limit is not None:
|
|
end_idx = start_idx + limit
|
|
selected_lines = lines[start_idx:end_idx]
|
|
else:
|
|
selected_lines = lines[start_idx:]
|
|
|
|
content = ''.join(selected_lines)
|
|
|
|
# Add metadata
|
|
total_lines = len(lines)
|
|
shown_lines = len(selected_lines)
|
|
|
|
if offset > 1 or limit is not None:
|
|
metadata = f"# File: {file_path} (showing lines {offset}-{offset + shown_lines - 1} of {total_lines})\n\n"
|
|
else:
|
|
metadata = f"# File: {file_path} ({total_lines} lines)\n\n"
|
|
|
|
return metadata + content
|
|
|
|
except UnicodeDecodeError:
|
|
try:
|
|
# Try with different encoding
|
|
with open(file_path, 'r', encoding='latin1') as f:
|
|
content = f.read()
|
|
return f"# File: {file_path} (read with latin1 encoding)\n\n{content}"
|
|
except Exception as e:
|
|
return f"Error: Could not read file '{file_path}': {e}"
|
|
except Exception as e:
|
|
return f"Error: Could not read file '{file_path}': {e}" |