mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-05-03 09:17:09 +08:00
fix(openviking): pre-check fs/stat to route file URIs before hitting directory-only endpoints
Adds a deterministic pre-check on top of htsh's exception-based fallback: before calling /content/abstract or /content/overview on a non-pseudo URI, probe /api/v1/fs/stat. If the server says the URI is a file, route straight to /content/read instead of eating a failing 500 round-trip. This is the same idea pty819 and chennest independently landed in PRs #12757 and #12937 — merged here on top of htsh's broader fix so we keep pseudo-URI normalization and v0.3.3 browse-shape handling while avoiding the slow exception path on servers that return a raised 500 every time. The exception fallback from #5886 stays in place for environments where fs/stat is unavailable or returns an unfamiliar shape. Also credits pty819, chennest, and htsh in AUTHOR_MAP so future release notes attribute them correctly.
This commit is contained in:
@@ -545,6 +545,29 @@ class OpenVikingMemoryProvider(MemoryProvider):
|
||||
return uri[: -len(suffix)] or "viking://"
|
||||
return uri
|
||||
|
||||
def _is_directory_uri(self, uri: str) -> bool | None:
|
||||
"""Probe fs/stat to decide if a URI is a directory.
|
||||
|
||||
Returns True/False when the server answers cleanly, and None when the
|
||||
probe itself fails (network error, unexpected shape). Callers should
|
||||
treat None as "unknown" and fall back to the exception-based path.
|
||||
"""
|
||||
try:
|
||||
resp = self._client.get("/api/v1/fs/stat", params={"uri": uri})
|
||||
except Exception:
|
||||
return None
|
||||
result = self._unwrap_result(resp)
|
||||
if isinstance(result, dict):
|
||||
if "isDir" in result:
|
||||
return bool(result.get("isDir"))
|
||||
if "is_dir" in result:
|
||||
return bool(result.get("is_dir"))
|
||||
if result.get("type") == "dir":
|
||||
return True
|
||||
if result.get("type") == "file":
|
||||
return False
|
||||
return None
|
||||
|
||||
def _tool_search(self, args: dict) -> str:
|
||||
query = args.get("query", "")
|
||||
if not query:
|
||||
@@ -600,19 +623,32 @@ class OpenVikingMemoryProvider(MemoryProvider):
|
||||
resolved_uri = self._normalize_summary_uri(uri) if summary_level else uri
|
||||
used_fallback = False
|
||||
|
||||
# abstract/overview endpoints are directory-only on OpenViking
|
||||
# (v0.3.x returns 500/412 for file URIs). When the caller asks for a
|
||||
# summary level on a non-pseudo URI, probe fs/stat first and route
|
||||
# file URIs straight to /content/read instead of eating a failing
|
||||
# round-trip. The pseudo-URI path already points at a directory, so
|
||||
# skip the probe there.
|
||||
if summary_level and resolved_uri == uri:
|
||||
is_dir = self._is_directory_uri(uri)
|
||||
if is_dir is False:
|
||||
resolved_uri = uri
|
||||
used_fallback = True
|
||||
|
||||
# Map our level names to OpenViking GET endpoints.
|
||||
endpoint = "/api/v1/content/read"
|
||||
if level == "abstract":
|
||||
endpoint = "/api/v1/content/abstract"
|
||||
elif level == "overview":
|
||||
endpoint = "/api/v1/content/overview"
|
||||
if not used_fallback:
|
||||
if level == "abstract":
|
||||
endpoint = "/api/v1/content/abstract"
|
||||
elif level == "overview":
|
||||
endpoint = "/api/v1/content/overview"
|
||||
|
||||
try:
|
||||
resp = self._client.get(endpoint, params={"uri": resolved_uri})
|
||||
except Exception:
|
||||
# OpenViking may return HTTP 500 for abstract/overview reads on normal
|
||||
# file URIs (mem_*.md). For those, gracefully fallback to full read.
|
||||
if not summary_level or resolved_uri != uri:
|
||||
if not summary_level or resolved_uri != uri or used_fallback:
|
||||
raise
|
||||
resp = self._client.get("/api/v1/content/read", params={"uri": uri})
|
||||
used_fallback = True
|
||||
|
||||
Reference in New Issue
Block a user