From cc6709c9d5c79a71abd33a20b3a2ded5e7f8e39a Mon Sep 17 00:00:00 2001 From: "Carly 2.0" Date: Sat, 4 Apr 2026 15:03:02 -0500 Subject: [PATCH] fix: pass api_key to AIAgent for non-Anthropic /anthropic providers When the user's config uses a non-Anthropic provider with an Anthropic-compatible endpoint (e.g. MiniMax at https://api.minimax.io/anthropic), chat in the WebUI fails silently with APIConnectionError on every request, while the hermes CLI and messaging gateway work fine with the same config. Root cause: both api/routes.py and api/streaming.py constructed AIAgent using only (model, provider, base_url) from resolve_model_provider() and never passed api_key. When the base URL ends in /anthropic, AIAgent uses the anthropic_messages adapter, but only falls back to ANTHROPIC_TOKEN when provider == "anthropic" (a safety check to avoid leaking Anthropic credentials to third parties). For MiniMax and similar providers the effective key becomes "", and the auth failure surfaces as a generic "Connection error" after three retries. The CLI and gateway resolve the key via hermes_cli.runtime_provider.resolve_runtime_provider(), which reads MINIMAX_API_KEY (and similar) from ~/.hermes/.env. This patch does the same before creating the AIAgent in both chat paths. Fixes #77 --- api/routes.py | 15 ++++++++++++++- api/streaming.py | 14 ++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/api/routes.py b/api/routes.py index f032916..60a45bc 100644 --- a/api/routes.py +++ b/api/routes.py @@ -888,8 +888,21 @@ def _handle_chat_sync(handler, body): with CHAT_LOCK: from api.config import resolve_model_provider _model, _provider, _base_url = resolve_model_provider(s.model) + # Resolve API key via Hermes runtime provider (matches gateway behaviour) + _api_key = None + try: + from hermes_cli.runtime_provider import resolve_runtime_provider + _rt = resolve_runtime_provider() + _api_key = _rt.get("api_key") + # Also use runtime provider/base_url if the webui config didn't resolve them + if not _provider: + _provider = _rt.get("provider") + if not _base_url: + _base_url = _rt.get("base_url") + except Exception as _e: + print(f"[webui] WARNING: resolve_runtime_provider failed: {_e}", flush=True) agent = AIAgent(model=_model, provider=_provider, base_url=_base_url, - platform='cli', quiet_mode=True, + api_key=_api_key, platform='cli', quiet_mode=True, enabled_toolsets=CLI_TOOLSETS, session_id=s.session_id) workspace_ctx = f"[Workspace: {s.workspace}]\n" workspace_system_msg = ( diff --git a/api/streaming.py b/api/streaming.py index c22aea4..7ff21e9 100644 --- a/api/streaming.py +++ b/api/streaming.py @@ -113,6 +113,19 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta raise ImportError("AIAgent not available -- check that hermes-agent is on sys.path") resolved_model, resolved_provider, resolved_base_url = resolve_model_provider(model) + # Resolve API key via Hermes runtime provider (matches gateway behaviour) + resolved_api_key = None + try: + from hermes_cli.runtime_provider import resolve_runtime_provider + _rt = resolve_runtime_provider() + resolved_api_key = _rt.get("api_key") + if not resolved_provider: + resolved_provider = _rt.get("provider") + if not resolved_base_url: + resolved_base_url = _rt.get("base_url") + except Exception as _e: + print(f"[webui] WARNING: resolve_runtime_provider failed: {_e}", flush=True) + # Read per-profile config at call time (not module-level snapshot) from api.config import get_config as _get_config _cfg = _get_config() @@ -140,6 +153,7 @@ def _run_agent_streaming(session_id, msg_text, model, workspace, stream_id, atta model=resolved_model, provider=resolved_provider, base_url=resolved_base_url, + api_key=resolved_api_key, platform='cli', quiet_mode=True, enabled_toolsets=_toolsets,