mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-04-29 15:31:38 +08:00
Compare commits
1 Commits
codex-port
...
hermes/her
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fd7ec0059 |
@@ -639,31 +639,47 @@ def resolve_runtime_provider(
|
||||
)
|
||||
|
||||
if provider == "nous":
|
||||
creds = resolve_nous_runtime_credentials(
|
||||
min_key_ttl_seconds=max(60, int(os.getenv("HERMES_NOUS_MIN_KEY_TTL_SECONDS", "1800"))),
|
||||
timeout_seconds=float(os.getenv("HERMES_NOUS_TIMEOUT_SECONDS", "15")),
|
||||
)
|
||||
return {
|
||||
"provider": "nous",
|
||||
"api_mode": "chat_completions",
|
||||
"base_url": creds.get("base_url", "").rstrip("/"),
|
||||
"api_key": creds.get("api_key", ""),
|
||||
"source": creds.get("source", "portal"),
|
||||
"expires_at": creds.get("expires_at"),
|
||||
"requested_provider": requested_provider,
|
||||
}
|
||||
try:
|
||||
creds = resolve_nous_runtime_credentials(
|
||||
min_key_ttl_seconds=max(60, int(os.getenv("HERMES_NOUS_MIN_KEY_TTL_SECONDS", "1800"))),
|
||||
timeout_seconds=float(os.getenv("HERMES_NOUS_TIMEOUT_SECONDS", "15")),
|
||||
)
|
||||
return {
|
||||
"provider": "nous",
|
||||
"api_mode": "chat_completions",
|
||||
"base_url": creds.get("base_url", "").rstrip("/"),
|
||||
"api_key": creds.get("api_key", ""),
|
||||
"source": creds.get("source", "portal"),
|
||||
"expires_at": creds.get("expires_at"),
|
||||
"requested_provider": requested_provider,
|
||||
}
|
||||
except AuthError:
|
||||
if requested_provider != "auto":
|
||||
raise
|
||||
# Auto-detected Nous but credentials are stale/revoked —
|
||||
# fall through to env-var providers (e.g. OpenRouter).
|
||||
logger.info("Auto-detected Nous provider but credentials failed; "
|
||||
"falling through to next provider.")
|
||||
|
||||
if provider == "openai-codex":
|
||||
creds = resolve_codex_runtime_credentials()
|
||||
return {
|
||||
"provider": "openai-codex",
|
||||
"api_mode": "codex_responses",
|
||||
"base_url": creds.get("base_url", "").rstrip("/"),
|
||||
"api_key": creds.get("api_key", ""),
|
||||
"source": creds.get("source", "hermes-auth-store"),
|
||||
"last_refresh": creds.get("last_refresh"),
|
||||
"requested_provider": requested_provider,
|
||||
}
|
||||
try:
|
||||
creds = resolve_codex_runtime_credentials()
|
||||
return {
|
||||
"provider": "openai-codex",
|
||||
"api_mode": "codex_responses",
|
||||
"base_url": creds.get("base_url", "").rstrip("/"),
|
||||
"api_key": creds.get("api_key", ""),
|
||||
"source": creds.get("source", "hermes-auth-store"),
|
||||
"last_refresh": creds.get("last_refresh"),
|
||||
"requested_provider": requested_provider,
|
||||
}
|
||||
except AuthError:
|
||||
if requested_provider != "auto":
|
||||
raise
|
||||
# Auto-detected Codex but credentials are stale/revoked —
|
||||
# fall through to env-var providers (e.g. OpenRouter).
|
||||
logger.info("Auto-detected Codex provider but credentials failed; "
|
||||
"falling through to next provider.")
|
||||
|
||||
if provider == "copilot-acp":
|
||||
creds = resolve_external_process_provider_credentials(provider)
|
||||
|
||||
@@ -996,6 +996,89 @@ def test_custom_provider_no_key_gets_placeholder(monkeypatch):
|
||||
assert resolved["base_url"] == "http://localhost:8080/v1"
|
||||
|
||||
|
||||
def test_auto_detected_nous_auth_failure_falls_through_to_openrouter(monkeypatch):
|
||||
"""When auto-detect picks Nous but credentials are revoked, fall through to OpenRouter."""
|
||||
from hermes_cli.auth import AuthError
|
||||
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "test-or-key")
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(rp, "load_config", lambda: {})
|
||||
|
||||
# resolve_provider returns "nous" (stale active_provider in auth.json)
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "nous")
|
||||
# load_pool returns empty pool so we hit the direct credential resolution
|
||||
monkeypatch.setattr(rp, "load_pool", lambda p: type("P", (), {
|
||||
"has_credentials": lambda self: False,
|
||||
})())
|
||||
# Nous credential resolution fails with revoked token
|
||||
monkeypatch.setattr(
|
||||
rp, "resolve_nous_runtime_credentials",
|
||||
lambda **kw: (_ for _ in ()).throw(
|
||||
AuthError("Refresh session has been revoked",
|
||||
provider="nous", code="invalid_grant", relogin_required=True)
|
||||
),
|
||||
)
|
||||
|
||||
# With requested="auto", should fall through to OpenRouter
|
||||
resolved = rp.resolve_runtime_provider(requested="auto")
|
||||
assert resolved["provider"] == "openrouter"
|
||||
assert resolved["api_key"] == "test-or-key"
|
||||
|
||||
|
||||
def test_auto_detected_codex_auth_failure_falls_through_to_openrouter(monkeypatch):
|
||||
"""When auto-detect picks Codex but credentials are revoked, fall through to OpenRouter."""
|
||||
from hermes_cli.auth import AuthError
|
||||
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "test-or-key")
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
monkeypatch.delenv("OPENAI_BASE_URL", raising=False)
|
||||
monkeypatch.delenv("OPENROUTER_BASE_URL", raising=False)
|
||||
monkeypatch.setattr(rp, "load_config", lambda: {})
|
||||
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "openai-codex")
|
||||
monkeypatch.setattr(rp, "load_pool", lambda p: type("P", (), {
|
||||
"has_credentials": lambda self: False,
|
||||
})())
|
||||
monkeypatch.setattr(
|
||||
rp, "resolve_codex_runtime_credentials",
|
||||
lambda **kw: (_ for _ in ()).throw(
|
||||
AuthError("Codex token refresh failed: session revoked",
|
||||
provider="openai-codex", code="invalid_grant", relogin_required=True)
|
||||
),
|
||||
)
|
||||
|
||||
resolved = rp.resolve_runtime_provider(requested="auto")
|
||||
assert resolved["provider"] == "openrouter"
|
||||
assert resolved["api_key"] == "test-or-key"
|
||||
|
||||
|
||||
def test_explicit_nous_auth_failure_still_raises(monkeypatch):
|
||||
"""When user explicitly requests Nous and auth fails, the error should propagate."""
|
||||
from hermes_cli.auth import AuthError
|
||||
import pytest
|
||||
|
||||
monkeypatch.setenv("OPENROUTER_API_KEY", "test-or-key")
|
||||
monkeypatch.setattr(rp, "load_config", lambda: {})
|
||||
|
||||
monkeypatch.setattr(rp, "resolve_provider", lambda *a, **k: "nous")
|
||||
monkeypatch.setattr(rp, "load_pool", lambda p: type("P", (), {
|
||||
"has_credentials": lambda self: False,
|
||||
})())
|
||||
monkeypatch.setattr(
|
||||
rp, "resolve_nous_runtime_credentials",
|
||||
lambda **kw: (_ for _ in ()).throw(
|
||||
AuthError("Refresh session has been revoked",
|
||||
provider="nous", code="invalid_grant", relogin_required=True)
|
||||
),
|
||||
)
|
||||
|
||||
# With explicit "nous", should raise — don't silently switch providers
|
||||
with pytest.raises(AuthError, match="Refresh session has been revoked"):
|
||||
rp.resolve_runtime_provider(requested="nous")
|
||||
|
||||
|
||||
def test_openrouter_provider_not_affected_by_custom_fix(monkeypatch):
|
||||
"""Fixing custom must not change openrouter behavior."""
|
||||
monkeypatch.delenv("OPENAI_API_KEY", raising=False)
|
||||
|
||||
Reference in New Issue
Block a user