"""share_file tool — expose a local file to the user as a download link."""
import shutil
from pathlib import Path
from navi.config import settings
from navi.session_files import ensure_session_dir
from .base import Tool, ToolResult, current_session_id
def _fmt_size(n: int) -> str:
if n < 1024:
return f"{n} B"
if n < 1024 ** 2:
return f"{n / 1024:.1f} KB"
return f"{n / 1024 ** 2:.1f} MB"
class ShareFileTool(Tool):
name = "share_file"
description = (
"Make a file available for the user to download via a direct HTTP link. "
"Use after generating or producing a file the user will want to keep (report, archive, export, etc.). "
"IMPORTANT — path must be an ABSOLUTE path (e.g. /home/user/file.zip). "
"If you only know a relative path, resolve it first: use filesystem(action='info') or "
"terminal('realpath <relative_path>') to get the absolute path, then call share_file. "
"After a successful call the result contains a URL — you MUST include that URL in your reply "
"to the user as a clickable markdown link: [filename](url)."
)
parameters = {
"type": "object",
"properties": {
"path": {
"type": "string",
"description": "Absolute path to the file to share",
},
"filename": {
"type": "string",
"description": (
"Download filename shown to the user. "
"Defaults to the original filename. "
"Use this to give the file a clean, descriptive name."
),
},
},
"required": ["path"],
}
async def execute(self, params: dict) -> ToolResult:
session_id = current_session_id.get()
if not session_id:
return ToolResult(success=False, output="No active session context.", error="no_session")
src = Path(params["path"]).expanduser().resolve()
if not src.exists():
return ToolResult(success=False, output=f"File not found: {src}", error="not_found")
if not src.is_file():
return ToolResult(success=False, output=f"Path is not a file: {src}", error="not_a_file")
# Determine the download filename
download_name = Path(params.get("filename") or src.name).name or src.name
dest_dir = ensure_session_dir(session_id)
dest = dest_dir / download_name
# If it's already the same file in the session dir, skip copy
if dest.resolve() == src.resolve():
pass
else:
# Avoid clobbering an existing different file with the same name
if dest.exists():
stem = Path(download_name).stem
suffix = Path(download_name).suffix
i = 1
while dest.exists():
dest = dest_dir / f"{stem}_{i}{suffix}"
i += 1
shutil.copy2(str(src), str(dest))
size = dest.stat().st_size
base_url = settings.public_url.rstrip("/")
url = f"{base_url}/sessions/{session_id}/files/{dest.name}"
return ToolResult(
success=True,
output=f"Download ready: {dest.name} ({_fmt_size(size)})\nURL: {url}",
metadata={"url": url, "filename": dest.name, "size": size},
)