Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion plugins/memory/hindsight/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ def _parse_int_setting(value: Any, default: int) -> int:
return default


def _ensure_url_scheme(url: str | None, mode: str) -> str:
"""Auto-add a scheme when a configured Hindsight api_url omits one.

A schemeless value (e.g. ``"host:8888"``) would otherwise reach the
HTTP layer as an invalid URL and fail with a cryptic URL-only error on
every tool call (issue #78967). Local modes default to plain ``http``
(self-hosted daemon); everything else defaults to ``https``. Explicit
schemes (anything containing ``://``) are left untouched.
"""
if not url:
return ""
url = str(url).strip()
if url and "://" not in url:
scheme = "http://" if mode in {"local_embedded", "local_external"} else "https://"
url = scheme + url
return url


# Env var the embedded daemon manager reads (at import time, as a module-level
# constant) to size the grace window it waits for a slow /health before
# declaring a daemon stale and killing it. Default upstream is 30s; on
Expand Down Expand Up @@ -1526,7 +1544,10 @@ def initialize(self, session_id: str, **kwargs) -> None:
return
self._api_key = self._config.get("apiKey") or self._config.get("api_key") or get_secret("HINDSIGHT_API_KEY", "")
default_url = _DEFAULT_LOCAL_URL if self._mode in {"local_embedded", "local_external"} else _DEFAULT_API_URL
self._api_url = self._config.get("api_url") or os.environ.get("HINDSIGHT_API_URL", default_url)
self._api_url = _ensure_url_scheme(
self._config.get("api_url") or os.environ.get("HINDSIGHT_API_URL", default_url),
self._mode,
)
self._llm_base_url = self._config.get("llm_base_url", "")

banks = cfg_get(self._config, "banks", "hermes", default={})
Expand Down
51 changes: 51 additions & 0 deletions tests/plugins/memory/test_hindsight_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,57 @@ def _make(**overrides):
return _make


def _write_config(tmp_path, monkeypatch, config: dict) -> None:
"""Write a hindsight config.json and point get_hermes_home at tmp_path."""
config_path = tmp_path / "hindsight" / "config.json"
config_path.parent.mkdir(parents=True, exist_ok=True)
config_path.write_text(json.dumps(config))
monkeypatch.setattr("plugins.memory.hindsight.get_hermes_home", lambda: tmp_path)


def _init_provider(tmp_path, monkeypatch, config: dict) -> HindsightMemoryProvider:
_write_config(tmp_path, monkeypatch, config)
p = HindsightMemoryProvider()
p.initialize(session_id="test-session", hermes_home=str(tmp_path), platform="cli")
return p


def test_schemeless_api_url_gets_http_for_local_external(tmp_path, monkeypatch):
"""Schemeless api_url in local_external mode auto-adds http:// (issue #78967)."""
p = _init_provider(tmp_path, monkeypatch, {
"mode": "local_external",
"api_url": "pve1-docker.ts.rmg7.com:8888",
})
assert p._api_url == "http://pve1-docker.ts.rmg7.com:8888"


def test_schemeless_api_url_gets_https_for_cloud(tmp_path, monkeypatch):
"""Schemeless api_url in cloud mode auto-adds https:// (issue #78967)."""
p = _init_provider(tmp_path, monkeypatch, {
"mode": "cloud",
"api_url": "localhost:8888",
})
assert p._api_url == "https://localhost:8888"


def test_explicit_scheme_api_url_preserved(tmp_path, monkeypatch):
"""An api_url that already has a scheme is left untouched (issue #78967)."""
p = _init_provider(tmp_path, monkeypatch, {
"mode": "cloud",
"api_url": "http://localhost:9999",
})
assert p._api_url == "http://localhost:9999"


def test_schemeless_env_var_url_gets_scheme(tmp_path, monkeypatch):
"""Schemeless HINDSIGHT_API_URL env var is normalized at init (issue #78967)."""
monkeypatch.delenv("HINDSIGHT_API_URL", raising=False)
monkeypatch.delenv("HINDSIGHT_API_KEY", raising=False)
monkeypatch.setenv("HINDSIGHT_API_URL", "hindsight.internal:9999")
p = _init_provider(tmp_path, monkeypatch, {"mode": "local_external"})
assert p._api_url == "http://hindsight.internal:9999"


def test_normalize_retain_tags_accepts_csv_and_dedupes():
assert _normalize_retain_tags("agent:fakeassistantname, source_system:hermes-agent, agent:fakeassistantname") == [
"agent:fakeassistantname",
Expand Down