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:
baenregod 2026-07-23 09:09:23 -07:00 committed by Teknium
parent c7fd3eb377
commit 3638136e7e
2 changed files with 71 additions and 1 deletions

View file

@ -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

View file

@ -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