feat: add read_many_files tool for batch file reading

Reduces LLM round-trips by allowing multiple files to be read in a
single tool call. Uses best-effort error handling so partial failures
still return successful reads.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 21:56:53 -05:00
parent 0886727437
commit 05754fe06b
4 changed files with 131 additions and 3 deletions

View File

@@ -23,6 +23,12 @@ class ReadFileParams(BaseModel):
file_path: str = Field(description="Path to the file to read (relative to workspace root)")
class ReadManyFilesParams(BaseModel):
"""Parameters for the read_many_files tool."""
file_paths: list[str] = Field(description="List of file paths to read (relative to workspace root)")
class ReadFileTool(BaseTool):
"""Read the contents of a file within the workspace."""
@@ -76,6 +82,58 @@ class ReadFileTool(BaseTool):
)
class ReadManyFilesTool(BaseTool):
"""Read contents of multiple files at once."""
name = "read_many_files"
description = (
"Read contents of multiple files at once. Returns each file's content "
"prefixed with its path header."
)
params_model = ReadManyFilesParams
def execute(self, *, tool_call_id: str, file_paths: list[str], **kwargs: Any) -> ToolResult:
if not file_paths:
return ToolResult(
tool_call_id=tool_call_id,
tool_name=self.name,
status=ToolResultStatus.ERROR,
error="file_paths list is empty",
)
fs_config = self.config.tools.filesystem
sections: list[str] = []
success_count = 0
for fp in file_paths:
try:
content = safe_read_file(
fp,
self.workspace_root,
max_size_bytes=fs_config.max_file_size_bytes,
check_binary=fs_config.binary_detection,
)
sections.append(f"=== {fp} ===\n{content}")
success_count += 1
except (PathSecurityError, FileNotFoundError, FileSizeError, BinaryFileError) as exc:
sections.append(f"=== {fp} ===\n[ERROR] {exc}")
if success_count == 0:
return ToolResult(
tool_call_id=tool_call_id,
tool_name=self.name,
status=ToolResultStatus.ERROR,
error="All files failed to read:\n" + "\n".join(sections),
)
return ToolResult(
tool_call_id=tool_call_id,
tool_name=self.name,
status=ToolResultStatus.SUCCESS,
output="\n".join(sections),
)
class ListDirParams(BaseModel):
"""Parameters for the list_dir tool."""

View File

@@ -99,7 +99,7 @@ def create_default_registry(
skill_runner: Optional SkillRunner for package skill activation.
"""
# Read tools
from app.tools.filesystem import ListDirTool, ReadFileTool
from app.tools.filesystem import ListDirTool, ReadFileTool, ReadManyFilesTool
# Write tools
from app.tools.filesystem import DeleteFileTool, MakeDirTool, WriteFileTool
@@ -120,6 +120,7 @@ def create_default_registry(
# Read
registry.register(ReadFileTool(workspace_root, config))
registry.register(ReadManyFilesTool(workspace_root, config))
registry.register(ListDirTool(workspace_root, config))
# Search