deerflow-code/offline-backend-20260512/backend/app/gateway/path_utils.py
2026-09-07 18:24:55 +08:00

48 lines
2.0 KiB
Python

"""Shared path resolution for thread virtual paths (e.g. mnt/user-data/outputs/...)."""
from pathlib import Path
from fastapi import HTTPException
from deerflow.config.paths import get_paths
from deerflow.runtime.thread_paths import aresolve_path_user_id, resolve_path_user_id
def resolve_thread_virtual_path(thread_id: str, virtual_path: str, *, user_id: str | None = None) -> Path:
"""Resolve a virtual path to the actual filesystem path under thread user-data.
Args:
thread_id: The thread ID.
virtual_path: The virtual path as seen inside the sandbox
(e.g., /mnt/user-data/outputs/file.txt).
user_id: Pre-resolved canonical user bucket. When ``None`` it is
resolved synchronously via :func:`resolve_path_user_id` (the
thread creator for task-shared threads, else the current
login). Async callers should pass the result of
:func:`aresolve_path_user_id` to avoid a blocking DB read.
Returns:
The resolved filesystem path.
Raises:
HTTPException: If the path is invalid or outside allowed directories.
"""
if user_id is None:
user_id = resolve_path_user_id(thread_id)
try:
return get_paths().resolve_virtual_path(thread_id, virtual_path, user_id=user_id)
except ValueError as e:
status = 403 if "traversal" in str(e) else 400
raise HTTPException(status_code=status, detail=str(e))
async def aresolve_thread_virtual_path(thread_id: str, virtual_path: str) -> Path:
"""Async variant of :func:`resolve_thread_virtual_path` for FastAPI handlers.
Resolves the canonical user bucket without blocking the event loop, so a
task-shared thread's artifacts resolve to the thread creator's directory
regardless of which account is currently logged in.
"""
user_id = await aresolve_path_user_id(thread_id)
return resolve_thread_virtual_path(thread_id, virtual_path, user_id=user_id)