mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
fix(auth): recover runtime Nous token from shared store
This commit is contained in:
parent
caf557be5b
commit
ca6513542d
2 changed files with 72 additions and 0 deletions
|
|
@ -5608,6 +5608,18 @@ def resolve_nous_runtime_credentials(
|
|||
)
|
||||
client_id = str(state.get("client_id") or DEFAULT_NOUS_CLIENT_ID)
|
||||
|
||||
def _refresh_effective_inference_base_url() -> None:
|
||||
nonlocal stored_inference_base_url, inference_base_url
|
||||
stored_inference_base_url = (
|
||||
_validate_nous_inference_url_from_network(
|
||||
_optional_base_url(state.get("inference_base_url"))
|
||||
)
|
||||
or DEFAULT_NOUS_INFERENCE_URL
|
||||
)
|
||||
inference_base_url = (
|
||||
_nous_inference_env_override() or stored_inference_base_url
|
||||
)
|
||||
|
||||
def _persist_state(reason: str) -> None:
|
||||
nonlocal persisted_state, state_persisted
|
||||
# Skip writes where only derived TTL countdowns changed; this keeps
|
||||
|
|
@ -5659,6 +5671,16 @@ def resolve_nous_runtime_credentials(
|
|||
access_token = state.get("access_token")
|
||||
refresh_token = state.get("refresh_token")
|
||||
|
||||
if not isinstance(access_token, str) or not access_token:
|
||||
with _nous_shared_store_lock(
|
||||
timeout_seconds=max(timeout_seconds + 5.0, AUTH_LOCK_TIMEOUT_SECONDS)
|
||||
):
|
||||
if _merge_shared_nous_oauth_state(state):
|
||||
access_token = state.get("access_token")
|
||||
refresh_token = state.get("refresh_token")
|
||||
_refresh_effective_inference_base_url()
|
||||
_persist_state("runtime_shared_merge_missing_access_token")
|
||||
|
||||
if not isinstance(access_token, str) or not access_token:
|
||||
raise AuthError("No access token found for Nous Portal login.",
|
||||
provider="nous", relogin_required=True)
|
||||
|
|
|
|||
|
|
@ -1954,6 +1954,56 @@ def test_runtime_refresh_uses_newer_shared_token_before_local_stale_token(
|
|||
assert profile_state["access_token"] == shared_token
|
||||
|
||||
|
||||
def test_runtime_credentials_merges_shared_token_before_empty_local_access_token(
|
||||
tmp_path, monkeypatch, shared_store_env,
|
||||
):
|
||||
"""A profile with access_token=None must recover from the shared store.
|
||||
|
||||
``resolve_nous_access_token()`` already merges shared OAuth state before
|
||||
giving up. The runtime path must do the same so sibling profiles that
|
||||
share a valid Nous login do not dead-end on an empty local auth.json.
|
||||
"""
|
||||
from hermes_cli import auth as auth_mod
|
||||
|
||||
profile_b = tmp_path / "profile_b"
|
||||
_setup_nous_auth(
|
||||
profile_b,
|
||||
access_token="local-placeholder",
|
||||
refresh_token="local-stale-refresh",
|
||||
expires_at="2000-01-01T00:00:00+00:00",
|
||||
expires_in=0,
|
||||
)
|
||||
auth_path = profile_b / "auth.json"
|
||||
auth_payload = json.loads(auth_path.read_text())
|
||||
auth_payload["providers"]["nous"]["access_token"] = None
|
||||
auth_path.write_text(json.dumps(auth_payload, indent=2))
|
||||
monkeypatch.setenv("HERMES_HOME", str(profile_b))
|
||||
|
||||
shared_state = _full_state_fixture()
|
||||
shared_token = _invoke_jwt(seconds=3600)
|
||||
shared_state["access_token"] = shared_token
|
||||
shared_state["refresh_token"] = "shared-fresh-refresh"
|
||||
shared_state["expires_at"] = _future_iso(3600)
|
||||
shared_state["scope"] = auth_mod.DEFAULT_NOUS_SCOPE
|
||||
shared_state["inference_base_url"] = auth_mod.DEFAULT_NOUS_INFERENCE_URL
|
||||
auth_mod._write_shared_nous_state(shared_state)
|
||||
|
||||
def _refresh_should_not_happen(**_kwargs):
|
||||
raise AssertionError("empty local access token should recover from shared store")
|
||||
|
||||
monkeypatch.setattr(auth_mod, "_refresh_access_token", _refresh_should_not_happen)
|
||||
|
||||
creds = auth_mod.resolve_nous_runtime_credentials()
|
||||
|
||||
assert creds["api_key"] == shared_token
|
||||
assert creds["base_url"] == auth_mod.DEFAULT_NOUS_INFERENCE_URL
|
||||
|
||||
profile_state = auth_mod.get_provider_auth_state("nous")
|
||||
assert profile_state is not None
|
||||
assert profile_state["access_token"] == shared_token
|
||||
assert profile_state["refresh_token"] == "shared-fresh-refresh"
|
||||
|
||||
|
||||
def test_managed_gateway_access_token_uses_newer_shared_token(
|
||||
tmp_path, monkeypatch, shared_store_env,
|
||||
):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue