Newer
Older
navi-1 / navi / tools / _filesystem / base.py
"""Shared plumbing for the three filesystem tools.

Read, write and AI actions live in separate `Tool` classes so the model picks
an intent first.  Path resolution, sandbox checks and the common error wording
are identical, so they live here.
"""

from __future__ import annotations

from pathlib import Path

from navi.config import settings

from .._internal.base import (
    Tool,
    ToolContext,
    ToolResult,
    current_user_id,
    current_user_role,
)
from .helpers import _check_path


class FilesystemBaseTool(Tool):
    """Base class: sandbox-aware path resolution and shared error results."""

    def __init__(self, ai_helper=None) -> None:
        self._ai = ai_helper

    def _resolve_path(self, raw_path: str, ctx: ToolContext | None) -> Path | None:
        user_id = ctx.user_id if ctx else current_user_id.get(None)
        role = ctx.user_role if ctx else current_user_role.get()
        return _check_path(raw_path, user_id, role, cwd=ctx.cwd if ctx else None)

    def _path_result(self, raw_path: str, path: Path | None) -> ToolResult | None:
        """Return the standard missing/denied result, or None when the path is usable."""
        if path is not None:
            return None
        if not raw_path or not raw_path.strip():
            return ToolResult(
                success=False,
                output="'path' is required. Pass the file or directory path.",
                error="missing_path",
            )
        return ToolResult(
            success=False,
            output=(
                f"Access denied: '{raw_path}' is outside allowed paths "
                f"({settings.fs_allowed_paths}). "
                "Set FS_ALLOWED_PATHS=* in .env to allow all paths."
            ),
            error="access_denied",
        )

    def _redirect(self, action: str, target: str) -> ToolResult:
        return ToolResult(
            success=False,
            output=(
                f"Action '{action}' belongs to '{target}', not this tool. "
                f"Call {target} with action={action!r}."
            ),
            error="wrong_tool",
        )