mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-29 18:46:59 +00:00
fix(hermes_cli): preserve unset-active dashboard xAI OAuth and cover token save modes
Use mark_provider_active_if_unset after dashboard token save, unsuppress device_code after TTS setup login, and lock default-active plus refresh active_provider contracts in tests.
This commit is contained in:
parent
fce06e909d
commit
f42be94049
3 changed files with 80 additions and 2 deletions
|
|
@ -908,6 +908,7 @@ def _run_xai_oauth_login_from_setup() -> bool:
|
|||
_is_remote_session,
|
||||
_save_xai_oauth_tokens,
|
||||
_xai_oauth_device_code_login,
|
||||
unsuppress_credential_source,
|
||||
)
|
||||
except Exception as exc:
|
||||
print_warning(f"xAI Grok OAuth helpers unavailable: {exc}")
|
||||
|
|
@ -926,6 +927,9 @@ def _run_xai_oauth_login_from_setup() -> bool:
|
|||
auth_mode="oauth_device_code",
|
||||
set_active=False,
|
||||
)
|
||||
# Mirror model/dashboard re-login: clear device_code suppression so
|
||||
# the pool can seed from the singleton after a prior `auth remove`.
|
||||
unsuppress_credential_source("xai-oauth", "device_code")
|
||||
return True
|
||||
except Exception as exc:
|
||||
print_warning(f"xAI Grok OAuth login failed: {exc}")
|
||||
|
|
|
|||
|
|
@ -11117,6 +11117,7 @@ def _xai_device_poller(session_id: str) -> None:
|
|||
_save_xai_oauth_tokens,
|
||||
_xai_oauth_discovery,
|
||||
_xai_oauth_poll_device_token,
|
||||
mark_provider_active_if_unset,
|
||||
unsuppress_credential_source,
|
||||
)
|
||||
|
||||
|
|
@ -11153,10 +11154,13 @@ def _xai_device_poller(session_id: str) -> None:
|
|||
discovery=discovery,
|
||||
last_refresh=datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
||||
auth_mode="oauth_device_code",
|
||||
# Dashboard OAuth only bootstraps credentials for side tools;
|
||||
# do not hijack the active chat inference provider.
|
||||
# Persist credentials without hijacking an existing active
|
||||
# chat provider.
|
||||
set_active=False,
|
||||
)
|
||||
# Mirror `hermes auth add xai-oauth`: first credential may become
|
||||
# active when none is set yet; never overwrite an existing choice.
|
||||
mark_provider_active_if_unset("xai-oauth")
|
||||
# The singleton write above is the single source of truth: the
|
||||
# credential-pool load seeds it as the canonical ``device_code``
|
||||
# entry. Do NOT also insert a parallel ``manual:dashboard_*`` pool
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ from hermes_cli.auth import (
|
|||
XAI_OAUTH_CLIENT_ID,
|
||||
XAI_OAUTH_SCOPE,
|
||||
_read_xai_oauth_tokens,
|
||||
_refresh_xai_oauth_tokens,
|
||||
_save_xai_oauth_tokens,
|
||||
_xai_access_token_is_expiring,
|
||||
_xai_oauth_poll_device_token,
|
||||
|
|
@ -315,6 +316,75 @@ def test_save_xai_oauth_tokens_set_active_false_preserves_active_provider(
|
|||
assert raw["providers"]["xai-oauth"]["tokens"]["access_token"] == "side-tool-at"
|
||||
|
||||
|
||||
def test_save_xai_oauth_tokens_default_sets_active_provider(tmp_path, monkeypatch):
|
||||
"""Intentional login default must promote xai-oauth to active_provider."""
|
||||
hermes_home = tmp_path / "hermes"
|
||||
hermes_home.mkdir(parents=True, exist_ok=True)
|
||||
auth_path = hermes_home / "auth.json"
|
||||
auth_path.write_text(
|
||||
json.dumps(
|
||||
{
|
||||
"version": 1,
|
||||
"active_provider": "openrouter",
|
||||
"providers": {},
|
||||
}
|
||||
)
|
||||
)
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
_save_xai_oauth_tokens(
|
||||
{
|
||||
"access_token": "login-at",
|
||||
"refresh_token": "login-rt",
|
||||
"id_token": "",
|
||||
"token_type": "Bearer",
|
||||
},
|
||||
discovery={"token_endpoint": "https://auth.x.ai/oauth2/token"},
|
||||
)
|
||||
|
||||
raw = json.loads(auth_path.read_text())
|
||||
assert raw["active_provider"] == "xai-oauth"
|
||||
assert raw["providers"]["xai-oauth"]["tokens"]["access_token"] == "login-at"
|
||||
|
||||
|
||||
def test_refresh_xai_oauth_tokens_preserves_active_provider(tmp_path, monkeypatch):
|
||||
"""Token refresh must not flip active_provider away from the chat provider."""
|
||||
hermes_home = tmp_path / "hermes"
|
||||
near = _jwt_with_exp(int(time.time()) + 30)
|
||||
_setup_hermes_auth(hermes_home, access_token=near, refresh_token="rt-old")
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
|
||||
auth_path = hermes_home / "auth.json"
|
||||
raw = json.loads(auth_path.read_text())
|
||||
raw["active_provider"] = "openrouter"
|
||||
auth_path.write_text(json.dumps(raw))
|
||||
|
||||
new_access = _jwt_with_exp(int(time.time()) + 7200)
|
||||
|
||||
def _fake_pure(access_token, refresh_token, **kwargs):
|
||||
return {
|
||||
"access_token": new_access,
|
||||
"refresh_token": "rt-new",
|
||||
"id_token": "",
|
||||
"expires_in": 3600,
|
||||
"token_type": "Bearer",
|
||||
"last_refresh": "2026-07-25T12:00:00Z",
|
||||
}
|
||||
|
||||
monkeypatch.setattr("hermes_cli.auth.refresh_xai_oauth_pure", _fake_pure)
|
||||
|
||||
tokens = _read_xai_oauth_tokens()["tokens"]
|
||||
_refresh_xai_oauth_tokens(
|
||||
tokens,
|
||||
token_endpoint="https://auth.x.ai/oauth2/token",
|
||||
timeout_seconds=5.0,
|
||||
)
|
||||
|
||||
after = json.loads(auth_path.read_text())
|
||||
assert after["active_provider"] == "openrouter"
|
||||
assert after["providers"]["xai-oauth"]["tokens"]["access_token"] == new_access
|
||||
|
||||
|
||||
def test_read_xai_oauth_tokens_missing(tmp_path, monkeypatch):
|
||||
hermes_home = tmp_path / "hermes"
|
||||
hermes_home.mkdir(parents=True, exist_ok=True)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue