diff --git a/dockerfiles/Dockerfile.hermes-agent b/dockerfiles/Dockerfile.hermes-agent index d4d498861..1bf51ae27 100644 --- a/dockerfiles/Dockerfile.hermes-agent +++ b/dockerfiles/Dockerfile.hermes-agent @@ -619,6 +619,72 @@ if source.count(before) != 1: path.write_text(source.replace(before, after, 1)) PY +# Keep the dashboard's account cards aligned with the managed provider lanes. +# Switchyard receives Claude Code subscription OAuth through the environment; +# that credential must not be presented as a metered Anthropic API key. +RUN python - <<'PY' +from pathlib import Path + +path = Path("/opt/hermes/hermes_cli/web_server.py") +source = path.read_text() + +registry_before = ''' try: + from hermes_cli.auth import PROVIDER_REGISTRY + env_var_order = PROVIDER_REGISTRY["anthropic"].api_key_env_vars + except (ImportError, KeyError): + pass +''' +registry_after = ''' try: + from hermes_cli.auth import PROVIDER_REGISTRY + env_var_order = PROVIDER_REGISTRY["anthropic"].api_key_env_vars + except (ImportError, KeyError): + pass + # Claude Code subscription OAuth is reported by its dedicated card below. + env_var_order = tuple( + var for var in env_var_order if var != "CLAUDE_CODE_OAUTH_TOKEN" + ) +''' + +managed_before = ''' try: + from agent.anthropic_adapter import read_claude_code_credentials + creds = read_claude_code_credentials() +''' +managed_after = ''' managed_token = os.getenv("CLAUDE_CODE_OAUTH_TOKEN") + if managed_token: + return { + "logged_in": True, + "source": "managed_claude_code_subscription", + "source_label": "Switchyard-managed Claude Code subscription", + "token_preview": _truncate_token(managed_token), + "expires_at": None, + "has_refresh_token": False, + } + try: + from agent.anthropic_adapter import read_claude_code_credentials + creds = read_claude_code_credentials() +''' + +replacements = ( + (registry_before, registry_after, "Anthropic credential separation"), + (managed_before, managed_after, "managed Claude Code status"), + ('"name": "Anthropic API Key",', + '"name": "Anthropic API / direct OAuth (not used by Switchyard)",', + "Anthropic account label"), + ('"name": "Anthropic OAuth: Required Extra Usage Credits to Use Subscription",', + '"name": "Claude Code subscription (Switchyard)",', + "Claude Code account label"), +) +for before, after, label in replacements: + if source.count(before) != 1: + raise SystemExit( + f"Hermes {label} patch context changed: expected 1, " + f"found {source.count(before)}" + ) + source = source.replace(before, after, 1) + +path.write_text(source) +PY + COPY dockerfiles/hermes-session-migrate.py /opt/hermes/bin/hermes-session-migrate RUN cd /opt/hermes/web \ @@ -641,6 +707,10 @@ RUN cd /opt/hermes/web \ /opt/hermes/gateway/platforms/api_server.py \ && grep -Fq 'agent._hermes_explicit_model_pick' \ /opt/hermes/gateway/platforms/api_server.py \ + && grep -Fq 'managed_claude_code_subscription' \ + /opt/hermes/hermes_cli/web_server.py \ + && grep -Fq 'Claude Code subscription (Switchyard)' \ + /opt/hermes/hermes_cli/web_server.py \ && grep -Fq '"pre_turn_route"' /opt/hermes/hermes_cli/plugins.py \ && grep -Fq '"pre_internal_route"' /opt/hermes/hermes_cli/plugins.py \ && grep -Fq '"pre_subagent_route"' /opt/hermes/hermes_cli/plugins.py \ diff --git a/testing/tests/test_hermes_chat_quality.py b/testing/tests/test_hermes_chat_quality.py index 055417e4e..14c0f0b74 100644 --- a/testing/tests/test_hermes_chat_quality.py +++ b/testing/tests/test_hermes_chat_quality.py @@ -149,6 +149,10 @@ def test_gateway_image_honors_ui_model_and_caps_reasoning(): assert "pre_internal_route hook failed" in dockerfile assert '"pre_subagent_route"' in dockerfile assert "pre_subagent_route hook failed" in dockerfile + assert 'var != "CLAUDE_CODE_OAUTH_TOKEN"' in dockerfile + assert '"source": "managed_claude_code_subscription"' in dockerfile + assert '"name": "Claude Code subscription (Switchyard)"' in dockerfile + assert "Anthropic API / direct OAuth (not used by Switchyard)" in dockerfile def test_chat_oauth_allows_stale_service_worker_retirement():