mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-25 17:18:11 +00:00
fix(auth): count MoA preset slots as explicit provider configuration
A user who configured a provider only inside a MoA preset (advisor or
aggregator slot) has explicitly opted into that provider — the consent
gate (is_provider_explicitly_configured) now scans moa.reference_models,
moa.aggregator, and all moa.presets.* slots, so Claude Code OAuth pool
seeding and the auxiliary auto-fallback chain treat MoA-only Anthropic
users consistently with model.provider users.
Salvaged from PR #57778 (trimmed): the auxiliary_client fallback half of
the original PR was independently landed on main in ddd3a2d247 and is
dropped here; a secret-scrubber artifact in the gate test fixture is
restored to the real placeholder token.
This commit is contained in:
parent
c7fd3eb377
commit
3638136e7e
2 changed files with 71 additions and 1 deletions
|
|
@ -1671,7 +1671,7 @@ def is_provider_explicitly_configured(provider_id: str) -> bool:
|
|||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. Check config.yaml model.provider
|
||||
# 2. Check config.yaml model.provider and other explicit provider slots.
|
||||
try:
|
||||
from hermes_cli.config import load_config
|
||||
cfg = load_config()
|
||||
|
|
@ -1680,6 +1680,37 @@ def is_provider_explicitly_configured(provider_id: str) -> bool:
|
|||
cfg_provider = (model_cfg.get("provider") or "").strip().lower()
|
||||
if cfg_provider == normalized:
|
||||
return True
|
||||
|
||||
# MoA presets are explicit model selections too. A user who configured
|
||||
# ``provider: anthropic`` as a MoA advisor/aggregator has opted Hermes
|
||||
# into using Anthropic credentials for that slot even when the main
|
||||
# session model is another provider. Without this, Claude Code OAuth
|
||||
# entries are pruned/ignored by credential_pool.load_pool("anthropic"),
|
||||
# so MoA Anthropic advisors fail with "no ANTHROPIC_API_KEY" while the
|
||||
# normal model picker says Anthropic is logged in.
|
||||
def _slot_matches_provider(slot):
|
||||
return (
|
||||
isinstance(slot, dict)
|
||||
and (slot.get("provider") or "").strip().lower() == normalized
|
||||
)
|
||||
|
||||
moa_cfg = cfg.get("moa")
|
||||
if isinstance(moa_cfg, dict):
|
||||
for slot in moa_cfg.get("reference_models") or []:
|
||||
if _slot_matches_provider(slot):
|
||||
return True
|
||||
if _slot_matches_provider(moa_cfg.get("aggregator")):
|
||||
return True
|
||||
presets = moa_cfg.get("presets")
|
||||
if isinstance(presets, dict):
|
||||
for preset in presets.values():
|
||||
if not isinstance(preset, dict):
|
||||
continue
|
||||
for slot in preset.get("reference_models") or []:
|
||||
if _slot_matches_provider(slot):
|
||||
return True
|
||||
if _slot_matches_provider(preset.get("aggregator")):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
|
|||
|
|
@ -129,6 +129,29 @@ def test_explicit_pool_source_counts_as_explicit(tmp_path, monkeypatch):
|
|||
assert is_provider_explicitly_configured("anthropic") is True
|
||||
|
||||
|
||||
def test_returns_true_when_moa_reference_slot_uses_provider(tmp_path, monkeypatch):
|
||||
"""MoA advisor slots are explicit provider selections for auth gating."""
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
|
||||
_write_config(tmp_path, {
|
||||
"model": {"provider": "openai-codex", "default": "gpt-5.5"},
|
||||
"moa": {
|
||||
"presets": {
|
||||
"default": {
|
||||
"reference_models": [
|
||||
{"provider": "anthropic", "model": "claude-opus-4-8"},
|
||||
{"provider": "opencode-go", "model": "glm-5.2"},
|
||||
],
|
||||
"aggregator": {"provider": "openai-codex", "model": "gpt-5.5"},
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
_write_auth_store(tmp_path, {"version": 1, "providers": {}, "active_provider": "openai-codex"})
|
||||
|
||||
from hermes_cli.auth import is_provider_explicitly_configured
|
||||
assert is_provider_explicitly_configured("anthropic") is True
|
||||
|
||||
|
||||
def test_stale_env_pool_entry_does_not_count_when_var_unset(tmp_path, monkeypatch):
|
||||
"""An env-seeded pool entry left in auth.json after the env var was removed
|
||||
must not mark the provider configured (#55790): the picker showed removed
|
||||
|
|
@ -190,3 +213,19 @@ def test_provider_not_in_registry_but_in_models_dev(tmp_path, monkeypatch):
|
|||
|
||||
from hermes_cli.auth import is_provider_explicitly_configured
|
||||
assert is_provider_explicitly_configured("openrouter") is True
|
||||
|
||||
|
||||
def test_returns_true_when_moa_aggregator_uses_provider(tmp_path, monkeypatch):
|
||||
"""MoA aggregator slots are explicit provider selections for auth gating."""
|
||||
monkeypatch.setenv("HERMES_HOME", str(tmp_path / "hermes"))
|
||||
_write_config(tmp_path, {
|
||||
"model": {"provider": "openai-codex", "default": "gpt-5.5"},
|
||||
"moa": {
|
||||
"reference_models": [{"provider": "opencode-go", "model": "glm-5.2"}],
|
||||
"aggregator": {"provider": "anthropic", "model": "claude-opus-4-8"},
|
||||
},
|
||||
})
|
||||
_write_auth_store(tmp_path, {"version": 1, "providers": {}, "active_provider": "openai-codex"})
|
||||
|
||||
from hermes_cli.auth import is_provider_explicitly_configured
|
||||
assert is_provider_explicitly_configured("anthropic") is True
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue