diff --git a/hermes_cli/doctor.py b/hermes_cli/doctor.py index 064b1d68d..58b008d87 100644 --- a/hermes_cli/doctor.py +++ b/hermes_cli/doctor.py @@ -29,6 +29,7 @@ if _env_path.exists(): load_dotenv(PROJECT_ROOT / ".env", override=False, encoding="utf-8") from hermes_cli.colors import Colors, color +from hermes_cli.models import _HERMES_USER_AGENT from hermes_constants import OPENROUTER_MODELS_URL from utils import base_url_host_matches @@ -957,7 +958,10 @@ def run_doctor(args): if base_url_host_matches(_base, "api.kimi.com") and _base.rstrip("/").endswith("/coding"): _base = _base.rstrip("/") + "/v1" _url = (_base.rstrip("/") + "/models") if _base else _default_url - _headers = {"Authorization": f"Bearer {_key}"} + _headers = { + "Authorization": f"Bearer {_key}", + "User-Agent": _HERMES_USER_AGENT, + } if base_url_host_matches(_base, "api.kimi.com"): _headers["User-Agent"] = "claude-code/0.1.0" _resp = httpx.get( diff --git a/hermes_cli/models.py b/hermes_cli/models.py index b7c4bccf1..856ba9b8b 100644 --- a/hermes_cli/models.py +++ b/hermes_cli/models.py @@ -1110,7 +1110,10 @@ def fetch_models_with_pricing( return _pricing_cache[cache_key] url = cache_key.rstrip("/") + "/v1/models" - headers: dict[str, str] = {"Accept": "application/json"} + headers: dict[str, str] = { + "Accept": "application/json", + "User-Agent": _HERMES_USER_AGENT, + } if api_key: headers["Authorization"] = f"Bearer {api_key}" @@ -2241,7 +2244,10 @@ def _fetch_ai_gateway_models(timeout: float = 5.0) -> Optional[list[str]]: base_url = AI_GATEWAY_BASE_URL url = base_url.rstrip("/") + "/models" - headers: dict[str, str] = {"Authorization": f"Bearer {api_key}"} + headers: dict[str, str] = { + "Authorization": f"Bearer {api_key}", + "User-Agent": _HERMES_USER_AGENT, + } req = urllib.request.Request(url, headers=headers) try: with urllib.request.urlopen(req, timeout=timeout) as resp: diff --git a/run_agent.py b/run_agent.py index 3640dd692..97b934e1e 100644 --- a/run_agent.py +++ b/run_agent.py @@ -664,6 +664,15 @@ def _sanitize_structure_non_ascii(payload: Any) -> bool: _QWEN_CODE_VERSION = "0.14.1" +def _routermint_headers() -> dict: + """Return the User-Agent RouterMint needs to avoid Cloudflare 1010 blocks.""" + from hermes_cli import __version__ as _HERMES_VERSION + + return { + "User-Agent": f"HermesAgent/{_HERMES_VERSION}", + } + + def _qwen_portal_headers() -> dict: """Return default HTTP headers required by Qwen Portal API.""" import platform as _plat @@ -1180,6 +1189,8 @@ class AIAgent: "X-OpenRouter-Title": "Hermes Agent", "X-OpenRouter-Categories": "productivity,cli-agent", } + elif base_url_host_matches(effective_base, "api.routermint.com"): + client_kwargs["default_headers"] = _routermint_headers() elif base_url_host_matches(effective_base, "api.githubcopilot.com"): from hermes_cli.models import copilot_default_headers @@ -5097,6 +5108,8 @@ class AIAgent: self._client_kwargs["default_headers"] = dict(_OR_HEADERS) elif base_url_host_matches(base_url, "ai-gateway.vercel.sh"): self._client_kwargs["default_headers"] = dict(_AI_GATEWAY_HEADERS) + elif base_url_host_matches(base_url, "api.routermint.com"): + self._client_kwargs["default_headers"] = _routermint_headers() elif base_url_host_matches(base_url, "api.githubcopilot.com"): from hermes_cli.models import copilot_default_headers diff --git a/tests/run_agent/test_provider_attribution_headers.py b/tests/run_agent/test_provider_attribution_headers.py index a2c543ee7..cf9d8bb8f 100644 --- a/tests/run_agent/test_provider_attribution_headers.py +++ b/tests/run_agent/test_provider_attribution_headers.py @@ -47,6 +47,24 @@ def test_ai_gateway_base_url_applies_attribution_headers(mock_openai): assert headers["User-Agent"].startswith("HermesAgent/") +@patch("run_agent.OpenAI") +def test_routermint_base_url_applies_user_agent_header(mock_openai): + mock_openai.return_value = MagicMock() + agent = AIAgent( + api_key="test-key", + base_url="https://api.routermint.com/v1", + model="test/model", + quiet_mode=True, + skip_context_files=True, + skip_memory=True, + ) + + agent._apply_client_headers_for_base_url("https://api.routermint.com/v1") + + headers = agent._client_kwargs["default_headers"] + assert headers["User-Agent"].startswith("HermesAgent/") + + @patch("run_agent.OpenAI") def test_unknown_base_url_clears_default_headers(mock_openai): mock_openai.return_value = MagicMock()