get_text_context

This commit is contained in:
moosecrap 2026-07-23 14:38:45 -07:00
parent 03e47bad42
commit 504794f9a8
4 changed files with 128 additions and 27 deletions

View File

@ -21,6 +21,7 @@ from .read_metadata import handle as read_png_metadata_handler
from .contact_sheet import handle as contact_sheet_handler from .contact_sheet import handle as contact_sheet_handler
from .list_directory import handle as list_directory_details_handler from .list_directory import handle as list_directory_details_handler
from .preview_image import handle as preview_image_handler from .preview_image import handle as preview_image_handler
from .get_text_context import handle as get_text_context_handler
# Central registry of all available tools # Central registry of all available tools
TOOL_REGISTRY = [ TOOL_REGISTRY = [
@ -87,4 +88,19 @@ TOOL_REGISTRY = [
}, },
handler=preview_image_handler handler=preview_image_handler
), ),
Tool(
name="get_text_context",
description="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.",
schema={
"type": "object",
"properties": {
"path": {"type": "string", "description": "Path to the file"},
"pattern": {"type": "string", "description": "Regex pattern to search for"},
"lines": {"type": "string", "description": "1-based indices or ranges (e.g., '10-20, 45')"},
"context": {"type": "integer", "description": "Number of lines of context to show above and below", "default": 1},
},
"required": ["path"],
},
handler=get_text_context_handler
),
] ]

86
tools/get_text_context.py Normal file
View File

@ -0,0 +1,86 @@
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("MattCP")
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)}

View File

@ -6,36 +6,10 @@ import datetime
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Tuple
from PIL import Image, ImageOps from PIL import Image, ImageOps
from tools.utils import format_relative_time, ToolError, get_file_info_list, sort_file_list from tools.utils import format_relative_time, ToolError, get_file_info_list, sort_file_list, parse_indices
logger = logging.getLogger("MattCP") logger = logging.getLogger("MattCP")
def parse_indices(indices_str: str) -> List[int]:
"""
Parses a string like '11, 31-33, 44' into a list of 1-based indices.
Preserves the order specified in the string.
"""
indices = []
parts = [p.strip() for p in indices_str.split(',')]
for part in parts:
if not part:
continue
if '-' in part:
try:
start, end = map(int, part.split('-'))
# Expand range in order. Handle reverse ranges as well.
step = 1 if start <= end else -1
for i in range(start, end + step, step):
indices.append(i)
except ValueError:
raise ToolError(f"Invalid range format: {part}")
else:
try:
indices.append(int(part))
except ValueError:
raise ToolError(f"Invalid index format: {part}")
return indices
def calculate_patch_dimensions(orig_w: int, orig_h: int, target_tokens: int = 70): def calculate_patch_dimensions(orig_w: int, orig_h: int, target_tokens: int = 70):
""" """
Calculates optimal pixel dimensions to hit a token budget of at least target_tokens. Calculates optimal pixel dimensions to hit a token budget of at least target_tokens.

View File

@ -96,3 +96,28 @@ def get_paginated_list(items: List[Any], page: int, page_size: int) -> List[Any]
start_idx = (page - 1) * page_size start_idx = (page - 1) * page_size
end_idx = start_idx + page_size end_idx = start_idx + page_size
return items[start_idx:end_idx] return items[start_idx:end_idx]
def parse_indices(indices_str: str) -> List[int]:
"""
Parses a string like '11, 31-33, 44' into a list of 1-based indices.
Preserves the order specified in the string.
"""
indices = []
parts = [p.strip() for p in indices_str.split(',')]
for part in parts:
if not part:
continue
if '-' in part:
try:
start, end = map(int, part.split('-'))
step = 1 if start <= end else -1
for i in range(start, end + step, step):
indices.append(i)
except ValueError:
raise ToolError(f"Invalid range format: {part}")
else:
try:
indices.append(int(part))
except ValueError:
raise ToolError(f"Invalid index format: {part}")
return indices