refactor: unified OAuth/API-key credential resolution for fallback

Split fallback provider handling into two clean registries:

  _FALLBACK_API_KEY_PROVIDERS — env-var-based (openrouter, zai, kimi, minimax)
  _FALLBACK_OAUTH_PROVIDERS  — OAuth-based (openai-codex, nous)

New _resolve_fallback_credentials() method handles all three cases
(OAuth, API key, custom endpoint) and returns a uniform (key, url, mode)
tuple. _try_activate_fallback() is now just validation + client build.

Adds Nous Portal as a fallback provider — uses the same OAuth flow
as the primary provider (hermes login), returns chat_completions mode.

OAuth providers get credential refresh for free: the existing 401
retry handlers (_try_refresh_codex/nous_client_credentials) check
self.provider, which is set correctly after fallback activation.

4 new tests (nous activation, nous no-login, codex retained).
27 total fallback tests passing, 2548 full suite.
This commit is contained in:
teknium1
2026-03-08 21:44:48 -07:00
parent 5785bd3272
commit 35d57ed752
3 changed files with 102 additions and 45 deletions

View File

@@ -252,6 +252,40 @@ class TestTryActivateFallback:
assert agent._try_activate_fallback() is False
assert agent._fallback_activated is False
def test_activates_nous_fallback(self):
"""Nous Portal fallback should use OAuth credentials and chat_completions mode."""
agent = _make_agent(
fallback_model={"provider": "nous", "model": "nous-hermes-3"},
)
mock_creds = {
"api_key": "nous-agent-key-abc",
"base_url": "https://inference-api.nousresearch.com/v1",
}
with (
patch("hermes_cli.auth.resolve_nous_runtime_credentials", return_value=mock_creds),
patch("run_agent.OpenAI") as mock_openai,
):
result = agent._try_activate_fallback()
assert result is True
assert agent.model == "nous-hermes-3"
assert agent.provider == "nous"
assert agent.api_mode == "chat_completions"
call_kwargs = mock_openai.call_args[1]
assert call_kwargs["api_key"] == "nous-agent-key-abc"
assert "nousresearch.com" in call_kwargs["base_url"]
def test_nous_fallback_fails_gracefully_without_login(self):
"""Nous fallback should return False if not logged in."""
agent = _make_agent(
fallback_model={"provider": "nous", "model": "nous-hermes-3"},
)
with patch(
"hermes_cli.auth.resolve_nous_runtime_credentials",
side_effect=Exception("Not logged in to Nous Portal"),
):
assert agent._try_activate_fallback() is False
assert agent._fallback_activated is False
# =============================================================================
# Fallback config init