mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-25 00:51:20 +00:00
feat(auth-codex): add config-provider fallback detection for logout in hermes-agent/hermes_cli/auth.py
This commit is contained in:
parent
b2e124d082
commit
ac25e6c99a
2 changed files with 132 additions and 4 deletions
|
|
@ -955,10 +955,12 @@ def clear_provider_auth(provider_id: Optional[str] = None) -> bool:
|
|||
del pool[target]
|
||||
cleared = True
|
||||
|
||||
if not cleared:
|
||||
return False
|
||||
if auth_store.get("active_provider") == target:
|
||||
auth_store["active_provider"] = None
|
||||
cleared = True
|
||||
|
||||
if not cleared:
|
||||
return False
|
||||
_save_auth_store(auth_store)
|
||||
return True
|
||||
|
||||
|
|
@ -2826,6 +2828,46 @@ def _update_config_for_provider(
|
|||
return config_path
|
||||
|
||||
|
||||
def _get_config_provider() -> Optional[str]:
|
||||
"""Return model.provider from config.yaml, normalized, if present."""
|
||||
try:
|
||||
config = read_raw_config()
|
||||
except Exception:
|
||||
return None
|
||||
if not config:
|
||||
return None
|
||||
model = config.get("model")
|
||||
if not isinstance(model, dict):
|
||||
return None
|
||||
provider = model.get("provider")
|
||||
if not isinstance(provider, str):
|
||||
return None
|
||||
provider = provider.strip().lower()
|
||||
return provider or None
|
||||
|
||||
|
||||
def _config_provider_matches(provider_id: Optional[str]) -> bool:
|
||||
"""Return True when config.yaml currently selects *provider_id*."""
|
||||
if not provider_id:
|
||||
return False
|
||||
return _get_config_provider() == provider_id.strip().lower()
|
||||
|
||||
|
||||
def _logout_default_provider_from_config() -> Optional[str]:
|
||||
"""Fallback logout target when auth.json has no active provider.
|
||||
|
||||
`hermes logout` historically keyed off auth.json.active_provider only.
|
||||
That left users stuck when auth state had already been cleared but
|
||||
config.yaml still selected an OAuth provider such as openai-codex for the
|
||||
agent model: there was no active auth provider to target, so logout printed
|
||||
"No provider is currently logged in" and never reset model.provider.
|
||||
"""
|
||||
provider = _get_config_provider()
|
||||
if provider in {"nous", "openai-codex"}:
|
||||
return provider
|
||||
return None
|
||||
|
||||
|
||||
def _reset_config_provider() -> Path:
|
||||
"""Reset config.yaml provider back to auto after logout."""
|
||||
config_path = get_config_path()
|
||||
|
|
@ -3524,15 +3566,16 @@ def logout_command(args) -> None:
|
|||
raise SystemExit(1)
|
||||
|
||||
active = get_active_provider()
|
||||
target = provider_id or active
|
||||
target = provider_id or active or _logout_default_provider_from_config()
|
||||
|
||||
if not target:
|
||||
print("No provider is currently logged in.")
|
||||
return
|
||||
|
||||
provider_name = PROVIDER_REGISTRY[target].name if target in PROVIDER_REGISTRY else target
|
||||
config_matches = _config_provider_matches(target)
|
||||
|
||||
if clear_provider_auth(target):
|
||||
if clear_provider_auth(target) or config_matches:
|
||||
_reset_config_provider()
|
||||
print(f"Logged out of {provider_name}.")
|
||||
if os.getenv("OPENROUTER_API_KEY"):
|
||||
|
|
|
|||
|
|
@ -504,6 +504,91 @@ def test_clear_provider_auth_removes_provider_pool_entries(tmp_path, monkeypatch
|
|||
assert "openrouter" in payload.get("credential_pool", {})
|
||||
|
||||
|
||||
def test_logout_resets_codex_config_when_auth_state_already_cleared(tmp_path, monkeypatch, capsys):
|
||||
"""`hermes logout --provider openai-codex` must still clear model.provider.
|
||||
|
||||
Users can end up with auth.json already cleared but config.yaml still set to
|
||||
openai-codex. Previously logout reported no auth state and left the agent
|
||||
pinned to the Codex provider.
|
||||
"""
|
||||
hermes_home = tmp_path / "hermes"
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
_write_auth_store(tmp_path, {"version": 1, "providers": {}, "credential_pool": {}})
|
||||
(hermes_home / "config.yaml").write_text(
|
||||
"model:\n"
|
||||
" default: gpt-5.3-codex\n"
|
||||
" provider: openai-codex\n"
|
||||
" base_url: https://chatgpt.com/backend-api/codex\n"
|
||||
)
|
||||
|
||||
from types import SimpleNamespace
|
||||
from hermes_cli.auth import logout_command
|
||||
|
||||
logout_command(SimpleNamespace(provider="openai-codex"))
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Logged out of OpenAI Codex." in out
|
||||
config_text = (hermes_home / "config.yaml").read_text()
|
||||
assert "provider: auto" in config_text
|
||||
assert "base_url: https://openrouter.ai/api/v1" in config_text
|
||||
|
||||
|
||||
def test_logout_defaults_to_configured_codex_when_no_active_provider(tmp_path, monkeypatch, capsys):
|
||||
"""Bare `hermes logout` should target configured Codex if auth has no active provider."""
|
||||
hermes_home = tmp_path / "hermes"
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
_write_auth_store(tmp_path, {"version": 1, "providers": {}, "credential_pool": {}})
|
||||
(hermes_home / "config.yaml").write_text(
|
||||
"model:\n"
|
||||
" default: gpt-5.3-codex\n"
|
||||
" provider: openai-codex\n"
|
||||
" base_url: https://chatgpt.com/backend-api/codex\n"
|
||||
)
|
||||
|
||||
from types import SimpleNamespace
|
||||
from hermes_cli.auth import logout_command
|
||||
|
||||
logout_command(SimpleNamespace(provider=None))
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Logged out of OpenAI Codex." in out
|
||||
config_text = (hermes_home / "config.yaml").read_text()
|
||||
assert "provider: auto" in config_text
|
||||
|
||||
|
||||
def test_logout_clears_stale_active_codex_without_provider_credentials(tmp_path, monkeypatch, capsys):
|
||||
"""Logout must clear active_provider even when provider credential payloads are gone."""
|
||||
hermes_home = tmp_path / "hermes"
|
||||
monkeypatch.setenv("HERMES_HOME", str(hermes_home))
|
||||
_write_auth_store(
|
||||
tmp_path,
|
||||
{
|
||||
"version": 1,
|
||||
"active_provider": "openai-codex",
|
||||
"providers": {},
|
||||
"credential_pool": {},
|
||||
},
|
||||
)
|
||||
(hermes_home / "config.yaml").write_text(
|
||||
"model:\n"
|
||||
" default: gpt-5.3-codex\n"
|
||||
" provider: openai-codex\n"
|
||||
" base_url: https://chatgpt.com/backend-api/codex\n"
|
||||
)
|
||||
|
||||
from types import SimpleNamespace
|
||||
from hermes_cli.auth import logout_command
|
||||
|
||||
logout_command(SimpleNamespace(provider=None))
|
||||
|
||||
out = capsys.readouterr().out
|
||||
assert "Logged out of OpenAI Codex." in out
|
||||
auth_payload = json.loads((hermes_home / "auth.json").read_text())
|
||||
assert auth_payload.get("active_provider") is None
|
||||
config_text = (hermes_home / "config.yaml").read_text()
|
||||
assert "provider: auto" in config_text
|
||||
|
||||
|
||||
def test_auth_list_does_not_call_mutating_select(monkeypatch, capsys):
|
||||
from hermes_cli.auth_commands import auth_list_command
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue