"""Pydantic input/output models for the AI tool layer. Input models double as JSON Schemas advertised to LLMs (via ``model_json_schema``) and as validation for arguments received over MCP. """ from __future__ import annotations from typing import Any from pydantic import BaseModel, Field class ListVaultsInput(BaseModel): """No parameters — lists the vaults the caller can access.""" class ListDirectoryInput(BaseModel): """Browse a directory inside a vault.""" vault: str = Field(..., description="Vault name") path: str = Field("", description="Vault-relative directory path (empty = vault root)") class ReadFileInput(BaseModel): """Read a text file's content from a vault.""" vault: str = Field(..., description="Vault name") path: str = Field(..., description="Vault-relative file path") class SearchFulltextInput(BaseModel): """Full-text search across one or all accessible vaults.""" q: str = Field(..., min_length=1, description="Search query") vault: str = Field("all", description="Vault name or 'all'") tag: str | None = Field(None, description="Optional comma-separated tag filter") limit: int = Field(50, ge=1, le=200, description="Maximum number of results") class ListTagsInput(BaseModel): """List tags and their occurrence counts.""" vault: str | None = Field(None, description="Vault name or 'all' (default: all accessible)") class ListAllFilesInput(BaseModel): """List every file of a vault (optionally under a subdirectory).""" vault: str = Field(..., description="Vault name") dir: str = Field("", description="Vault-relative directory path (empty = vault root)") limit: int = Field(200, ge=1, le=2000, description="Maximum number of files") recursive: bool = Field(True, description="Recurse into subdirectories") class ReadFileRawInput(BaseModel): """Read a text file's raw content (no HTML rendering).""" vault: str = Field(..., description="Vault name") path: str = Field(..., description="Vault-relative file path") class GetBacklinksInput(BaseModel): """List files linking to a target file via wikilinks.""" vault: str = Field(..., description="Vault name") path: str = Field(..., description="Vault-relative path of the target file") class ListBackupsInput(BaseModel): """List available backup versions of a file.""" vault: str = Field(..., description="Vault name") path: str = Field(..., description="Vault-relative file path") class DiffBackupInput(BaseModel): """Unified diff between a backup version and another version or the current file.""" vault: str = Field(..., description="Vault name") path: str = Field(..., description="Vault-relative file path") version: int = Field(..., description="Backup timestamp used as the old/left side") compare_with: int | None = Field( None, description="Backup timestamp used as the new/right side (omit to compare with the current file)", ) class GetGraphInput(BaseModel): """Graph data (nodes and edges) for a vault or directory.""" vault: str = Field(..., description="Vault name") path: str = Field("", description="Vault-relative directory path to focus on (empty = root)") depth: int = Field(1, ge=0, le=3, description="Expansion depth") scope: str = Field("directory", description="'directory' for a subtree, 'full' for the whole vault") tag: str = Field("", description="Only include files carrying this tag") class SearchAdvancedInput(BaseModel): """Advanced full-text search (TF-IDF, facets, operators).""" q: str = Field("", description="Query (supports tag:, vault:, title:, path:, ext: operators)") vault: str = Field("all", description="Vault name or 'all'") tag: str | None = Field(None, description="Optional comma-separated tag filter") limit: int = Field(50, ge=1, le=200, description="Maximum number of results") offset: int = Field(0, ge=0, description="Pagination offset") sort: str = Field("relevance", description="Sort by 'relevance' or 'modified'") case_sensitive: bool = Field(False, description="Match case") whole_word: bool = Field(False, description="Match whole words only") regex: bool = Field(False, description="Treat query as a regular expression") include_paths: str | None = Field(None, description="Comma-separated glob patterns to include") exclude_paths: str | None = Field(None, description="Comma-separated glob patterns to exclude") created: str | None = Field(None, description="Created date filter (>date,