mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-04-25 00:36:31 +02:00
feat(filesystem): improve path normalization in SurfSenseFilesystemMiddleware to handle Windows-style paths and mixed separators
This commit is contained in:
parent
30b55a9baa
commit
7063d6d1e4
4 changed files with 117 additions and 6 deletions
|
|
@ -787,17 +787,20 @@ class SurfSenseFilesystemMiddleware(FilesystemMiddleware):
|
|||
) -> str:
|
||||
backend = self._get_backend(runtime)
|
||||
mount_prefix = self._default_mount_prefix(runtime)
|
||||
normalized_candidate = re.sub(r"/+", "/", candidate.strip().replace("\\", "/"))
|
||||
if not mount_prefix or not isinstance(backend, MultiRootLocalFolderBackend):
|
||||
return candidate if candidate.startswith("/") else f"/{candidate.lstrip('/')}"
|
||||
if normalized_candidate.startswith("/"):
|
||||
return normalized_candidate
|
||||
return f"/{normalized_candidate.lstrip('/')}"
|
||||
|
||||
mount_names = set(backend.list_mounts())
|
||||
if candidate.startswith("/"):
|
||||
first_segment = candidate.lstrip("/").split("/", 1)[0]
|
||||
if normalized_candidate.startswith("/"):
|
||||
first_segment = normalized_candidate.lstrip("/").split("/", 1)[0]
|
||||
if first_segment in mount_names:
|
||||
return candidate
|
||||
return f"{mount_prefix}{candidate}"
|
||||
return normalized_candidate
|
||||
return f"{mount_prefix}{normalized_candidate}"
|
||||
|
||||
relative = candidate.lstrip("/")
|
||||
relative = normalized_candidate.lstrip("/")
|
||||
first_segment = relative.split("/", 1)[0]
|
||||
if first_segment in mount_names:
|
||||
return f"/{relative}"
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from langchain_core.messages import AIMessage, HumanMessage
|
|||
from app.agents.new_chat.middleware.file_intent import (
|
||||
FileIntentMiddleware,
|
||||
FileOperationIntent,
|
||||
_fallback_path,
|
||||
)
|
||||
|
||||
pytestmark = pytest.mark.unit
|
||||
|
|
@ -171,3 +172,43 @@ async def test_file_write_infers_directory_from_user_text_when_missing():
|
|||
assert contract["intent"] == FileOperationIntent.FILE_WRITE.value
|
||||
assert contract["suggested_path"] == "/pc_backups/random.md"
|
||||
|
||||
|
||||
def test_fallback_path_normalizes_windows_slashes() -> None:
|
||||
resolved = _fallback_path(
|
||||
suggested_filename="summary.md",
|
||||
suggested_path=r"\reports\q2\summary.md",
|
||||
user_text="create report",
|
||||
)
|
||||
|
||||
assert resolved == "/reports/q2/summary.md"
|
||||
|
||||
|
||||
def test_fallback_path_normalizes_windows_drive_path() -> None:
|
||||
resolved = _fallback_path(
|
||||
suggested_filename=None,
|
||||
suggested_path=r"C:\Users\anish\notes\todo.md",
|
||||
user_text="create note",
|
||||
)
|
||||
|
||||
assert resolved == "/C/Users/anish/notes/todo.md"
|
||||
|
||||
|
||||
def test_fallback_path_normalizes_mixed_separators_and_duplicate_slashes() -> None:
|
||||
resolved = _fallback_path(
|
||||
suggested_filename="summary.md",
|
||||
suggested_path=r"\\reports\\q2//summary.md",
|
||||
user_text="create report",
|
||||
)
|
||||
|
||||
assert resolved == "/reports/q2/summary.md"
|
||||
|
||||
|
||||
def test_fallback_path_keeps_posix_style_absolute_path_for_linux_and_macos() -> None:
|
||||
resolved = _fallback_path(
|
||||
suggested_filename=None,
|
||||
suggested_path="/var/log/surfsense/notes.md",
|
||||
user_text="create note",
|
||||
)
|
||||
|
||||
assert resolved == "/var/log/surfsense/notes.md"
|
||||
|
||||
|
|
|
|||
|
|
@ -97,3 +97,68 @@ def test_normalize_local_mount_path_keeps_explicit_mount(tmp_path: Path) -> None
|
|||
)
|
||||
|
||||
assert resolved == "/pc_backups/notes/random-note.md"
|
||||
|
||||
|
||||
def test_normalize_local_mount_path_windows_backslashes(tmp_path: Path) -> None:
|
||||
root = tmp_path / "PC Backups"
|
||||
root.mkdir()
|
||||
backend = MultiRootLocalFolderBackend((("pc_backups", str(root)),))
|
||||
runtime = _RuntimeNoSuggestedPath()
|
||||
middleware = SurfSenseFilesystemMiddleware.__new__(SurfSenseFilesystemMiddleware)
|
||||
middleware._get_backend = lambda _runtime: backend # type: ignore[method-assign]
|
||||
|
||||
resolved = middleware._normalize_local_mount_path( # type: ignore[arg-type]
|
||||
r"\notes\random-note.md",
|
||||
runtime,
|
||||
)
|
||||
|
||||
assert resolved == "/pc_backups/notes/random-note.md"
|
||||
|
||||
|
||||
def test_normalize_local_mount_path_normalizes_mixed_separators(tmp_path: Path) -> None:
|
||||
root = tmp_path / "PC Backups"
|
||||
root.mkdir()
|
||||
backend = MultiRootLocalFolderBackend((("pc_backups", str(root)),))
|
||||
runtime = _RuntimeNoSuggestedPath()
|
||||
middleware = SurfSenseFilesystemMiddleware.__new__(SurfSenseFilesystemMiddleware)
|
||||
middleware._get_backend = lambda _runtime: backend # type: ignore[method-assign]
|
||||
|
||||
resolved = middleware._normalize_local_mount_path( # type: ignore[arg-type]
|
||||
r"\\notes//nested\\random-note.md",
|
||||
runtime,
|
||||
)
|
||||
|
||||
assert resolved == "/pc_backups/notes/nested/random-note.md"
|
||||
|
||||
|
||||
def test_normalize_local_mount_path_keeps_explicit_mount_with_backslashes(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = tmp_path / "PC Backups"
|
||||
root.mkdir()
|
||||
backend = MultiRootLocalFolderBackend((("pc_backups", str(root)),))
|
||||
runtime = _RuntimeNoSuggestedPath()
|
||||
middleware = SurfSenseFilesystemMiddleware.__new__(SurfSenseFilesystemMiddleware)
|
||||
middleware._get_backend = lambda _runtime: backend # type: ignore[method-assign]
|
||||
|
||||
resolved = middleware._normalize_local_mount_path( # type: ignore[arg-type]
|
||||
r"\pc_backups\notes\random-note.md",
|
||||
runtime,
|
||||
)
|
||||
|
||||
assert resolved == "/pc_backups/notes/random-note.md"
|
||||
|
||||
|
||||
def test_normalize_local_mount_path_prefixes_posix_absolute_path_for_linux_and_macos(
|
||||
tmp_path: Path,
|
||||
) -> None:
|
||||
root = tmp_path / "PC Backups"
|
||||
root.mkdir()
|
||||
backend = MultiRootLocalFolderBackend((("pc_backups", str(root)),))
|
||||
runtime = _RuntimeNoSuggestedPath()
|
||||
middleware = SurfSenseFilesystemMiddleware.__new__(SurfSenseFilesystemMiddleware)
|
||||
middleware._get_backend = lambda _runtime: backend # type: ignore[method-assign]
|
||||
|
||||
resolved = middleware._normalize_local_mount_path("/var/log/app.log", runtime) # type: ignore[arg-type]
|
||||
|
||||
assert resolved == "/pc_backups/var/log/app.log"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import {
|
||||
BookOpen,
|
||||
Brain,
|
||||
FileUser,
|
||||
FileText,
|
||||
Film,
|
||||
Globe,
|
||||
|
|
@ -15,6 +16,7 @@ const TOOL_ICONS: Record<string, LucideIcon> = {
|
|||
generate_podcast: Podcast,
|
||||
generate_video_presentation: Film,
|
||||
generate_report: FileText,
|
||||
generate_resume: FileUser,
|
||||
generate_image: ImageIcon,
|
||||
scrape_webpage: ScanLine,
|
||||
web_search: Globe,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue