import re import logging from pathlib import Path from typing import Any, Dict, List, Set from tools.utils import ToolError, parse_indices logger = logging.getLogger("MooseCP") async def handle(args: Dict[str, Any]): """ Extracts specific sections of a file with absolute line numbers and surrounding context. This tool MUST be called immediately before edit_file to verify the exact line numbers and content of the block being replaced, preventing misalignment errors. """ path_str = args.get("path") pattern = args.get("pattern") lines_str = args.get("lines") context = int(args.get("context", 1)) if not path_str: raise ToolError("Missing path argument") path = Path(path_str) if not path.is_file(): raise ToolError(f"{path_str} is not a file.") try: with open(path, "r", encoding="utf-8", errors="replace") as f: all_lines = f.readlines() except Exception as e: raise ToolError(f"Error reading file {path_str}: {e}") total_lines = len(all_lines) # Case 1: No pattern and no lines provided -> Dump whole file if not pattern and not lines_str: output = [f"{i+1}: {line}" for i, line in enumerate(all_lines)] return {"text": "".join(output)} # Determine target line numbers (1-based) target_lines: Set[int] = set() if pattern: try: regex = re.compile(pattern) for i, line in enumerate(all_lines): if regex.search(line): target_lines.add(i + 1) except re.error as e: raise ToolError(f"Invalid regex pattern: {e}") if lines_str: try: indices = parse_indices(lines_str) for idx in indices: if 1 <= idx <= total_lines: target_lines.add(idx) except ToolError as e: raise e except Exception as e: raise ToolError(f"Error parsing line indices: {e}") if not target_lines: return {"text": "No matching lines found."} # Expand targets with context lines_to_show: Set[int] = set() for line in target_lines: for offset in range(-context, context + 1): ln = line + offset if 1 <= ln <= total_lines: lines_to_show.add(ln) # Sort and format output sorted_lines = sorted(list(lines_to_show)) output = [] last_line = None for ln in sorted_lines: if last_line is not None and ln > last_line + 1: output.append("...\n") output.append(f"{ln}: {all_lines[ln-1]}") last_line = ln return {"text": "".join(output)}