mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-30 19:09:28 +00:00
feat(sync): default the sync plane to production
Skill Sync had no default base URL, so a user with no `sync.base_url` in
config.yaml and no HERMES_SYNC_BASE_URL got:
sync inert: no sync base URL configured (config.yaml sync.base_url
or HERMES_SYNC_BASE_URL).
Every sync command was unusable out of the box. The URL was left unset
because the plane did not exist yet when the client was written; it does now.
- Adds DEFAULT_SYNC_BASE_URL = "https://gateway-gateway.nousresearch.com" and
returns it as the last step of resolve_sync_base_url().
- Resolution order is unchanged otherwise: HERMES_SYNC_BASE_URL ->
config.yaml sync.base_url -> production default. The env var and config key
now exist to point a dev/staging build at another plane rather than to make
the feature work at all.
- Follows the existing precedent for production endpoints in this codebase
(DEFAULT_NOUS_PORTAL_URL in hermes_cli/auth.py, HERMES_DIAGNOSTICS_BASE_URL
in diagnostics_upload.py): a module constant with env/config override.
The "no sync base URL configured" guards are kept — they are now unreachable
in practice but remain correct if the default is ever blanked.
Tests: 3 new — the default is returned when nothing is configured, config
still overrides it, and the constant is a bare https origin (no trailing
slash, no path) since the client appends /v1/sync/. 2349 passed / 0 failed
across 56 suites via scripts/run_tests.sh.
Verified against a temp HERMES_HOME with no config: resolves to the
production plane; HERMES_SYNC_BASE_URL and sync.base_url both still win, and
trailing slashes are stripped.
This commit is contained in:
parent
f9c4d835f9
commit
e327eaa2a0
2 changed files with 44 additions and 10 deletions
|
|
@ -694,6 +694,33 @@ class TestEnvConfig:
|
|||
monkeypatch.setenv("HERMES_SYNC_BASE_URL", "https://plane.example/")
|
||||
assert ssc.resolve_sync_base_url() == "https://plane.example"
|
||||
|
||||
def test_base_url_defaults_to_production(self, monkeypatch):
|
||||
# With nothing configured a user must still reach the real plane —
|
||||
# otherwise every sync command fails with "no base URL configured".
|
||||
monkeypatch.delenv("HERMES_SYNC_BASE_URL", raising=False)
|
||||
monkeypatch.setattr("hermes_cli.config.load_config", lambda: {}, raising=False)
|
||||
assert ssc.resolve_sync_base_url() == ssc.DEFAULT_SYNC_BASE_URL
|
||||
|
||||
def test_default_is_a_bare_https_origin(self):
|
||||
# The client appends /v1/sync/, so the default must be a scheme+host
|
||||
# origin with no trailing slash and no path.
|
||||
from urllib.parse import urlparse
|
||||
|
||||
parsed = urlparse(ssc.DEFAULT_SYNC_BASE_URL)
|
||||
assert parsed.scheme == "https"
|
||||
assert parsed.netloc
|
||||
assert parsed.path == ""
|
||||
assert not ssc.DEFAULT_SYNC_BASE_URL.endswith("/")
|
||||
|
||||
def test_config_overrides_default(self, monkeypatch):
|
||||
monkeypatch.delenv("HERMES_SYNC_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(
|
||||
"hermes_cli.config.load_config",
|
||||
lambda: {"sync": {"base_url": "https://cfg.example/"}},
|
||||
raising=False,
|
||||
)
|
||||
assert ssc.resolve_sync_base_url() == "https://cfg.example"
|
||||
|
||||
def test_feature_enabled_env(self, monkeypatch):
|
||||
# Default off.
|
||||
monkeypatch.delenv("HERMES_SYNC_ENABLED", raising=False)
|
||||
|
|
|
|||
|
|
@ -295,18 +295,25 @@ def dev_gate_open() -> bool:
|
|||
# ---------------------------------------------------------------------------
|
||||
# Sync-plane endpoint resolution
|
||||
#
|
||||
# The sync routes are mounted under /v1/sync/. The base URL is
|
||||
# configurable (config.yaml sync.base_url or HERMES_SYNC_BASE_URL bridge env);
|
||||
# it is NOT the inference base_url. When unset, sync is inert -- there is no
|
||||
# server to talk to yet (the server is being built in parallel).
|
||||
# The sync routes are mounted under /v1/sync/. The base URL defaults to the
|
||||
# production plane, so a normal user configures nothing; config.yaml
|
||||
# sync.base_url (or the HERMES_SYNC_BASE_URL bridge env) overrides it to point
|
||||
# a dev/staging build at another plane. It is NOT the inference base_url.
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def resolve_sync_base_url() -> Optional[str]:
|
||||
"""Resolve the sync-plane base URL, or None when unconfigured.
|
||||
#: Production Skill Sync plane. Overridable per the resolution order below.
|
||||
DEFAULT_SYNC_BASE_URL = "https://gateway-gateway.nousresearch.com"
|
||||
|
||||
Order: HERMES_SYNC_BASE_URL env bridge -> config.yaml ``sync.base_url``.
|
||||
Returns a base without a trailing slash (e.g. ``https://host``); the
|
||||
``/v1/sync/`` prefix is appended by the client.
|
||||
def resolve_sync_base_url() -> Optional[str]:
|
||||
"""Resolve the sync-plane base URL.
|
||||
|
||||
Order: HERMES_SYNC_BASE_URL env bridge -> config.yaml ``sync.base_url`` ->
|
||||
the production plane. Returns a base without a trailing slash (e.g.
|
||||
``https://host``); the ``/v1/sync/`` prefix is appended by the client.
|
||||
|
||||
The production default means a normal user never configures a URL — the
|
||||
env var and config key exist to point a dev/staging build at another
|
||||
plane. Returns None only if the default is somehow blanked out.
|
||||
"""
|
||||
env = os.getenv("HERMES_SYNC_BASE_URL")
|
||||
if env and env.strip():
|
||||
|
|
@ -324,7 +331,7 @@ def resolve_sync_base_url() -> Optional[str]:
|
|||
return base.strip().rstrip("/")
|
||||
except Exception as e:
|
||||
logger.debug("skills_sync_client: config sync.base_url read failed: %s", e)
|
||||
return None
|
||||
return DEFAULT_SYNC_BASE_URL or None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue