"""List files tool."""
from pydantic import BaseModel, Field
from ..bot import BotContext
from .base import ToolResult
from .constants import MAX_LIST_FILES
from .registry import folder_bot, get_services
[docs]
class ListFilesRequest(BaseModel, frozen=True):
"""Request for listing files in a directory."""
path: str = Field(
default="",
description=(
"Subfolder path relative to root (e.g., 'notes/2024'). "
"Empty string or omit for root folder."
),
)
pattern: str = Field(
default="*",
description="Glob pattern to filter files (e.g., '*.md'). Defaults to all files.",
)
[docs]
@folder_bot.tool(
name="list_files",
request_type=ListFilesRequest,
response_type=ToolResult,
)
async def list_files(
request: ListFilesRequest, _context: BotContext | None = None
) -> ToolResult:
"""List files in the folder or a subfolder.
Returns file paths relative to the root folder.
Use this to discover what files are available before reading them.
"""
services = get_services(_context)
if services is None:
return ToolResult(content="Services not available", is_error=True)
target_dir = services.validate_path(request.path)
if target_dir is None:
return ToolResult(content="Invalid path: access denied", is_error=True)
if not target_dir.exists():
return ToolResult(content=f"Directory not found: {request.path}", is_error=True)
if not target_dir.is_dir():
return ToolResult(content=f"Not a directory: {request.path}", is_error=True)
files: list[str] = []
for file_path in target_dir.rglob(request.pattern):
if file_path.is_file():
rel_path = str(file_path.relative_to(services.root))
if services.is_file_allowed(rel_path):
files.append(rel_path)
files.sort()
if not files:
return ToolResult(content="No matching files found.")
total_count = len(files)
if total_count > MAX_LIST_FILES:
files = files[:MAX_LIST_FILES]
return ToolResult(
content="\n".join(files)
+ f"\n\n[Showing first {MAX_LIST_FILES} of {total_count} files]"
)
return ToolResult(content="\n".join(files))