From 16b28b9a658d740277f3a35ab2894c8fd468c860 Mon Sep 17 00:00:00 2001 From: claude-ceo-assistant Date: Fri, 8 May 2026 14:23:51 -0700 Subject: [PATCH] fix(adapter): map persona-friendly slugs (claude-code, anthropic) to registry names MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Phase 4 verification surfaced a follow-up edge case the initial fix missed: the persona env files use friendlier slugs than the registry's canonical names: * MODEL_PROVIDER=claude-code -> anthropic-oauth (Claude Code subscription) * MODEL_PROVIDER=anthropic -> anthropic-api (direct Anthropic API key) Without an alias map, a lead workspace's MODEL_PROVIDER=claude-code env fell through the slug-detection path; when the YAML didn't pin a provider, the model-prefix matcher saw MODEL=MiniMax-M2.7 and routed the lead to MiniMax — even though CLAUDE_CODE_OAUTH_TOKEN was clearly the intended auth path. Add _PROVIDER_SLUG_ALIASES with the two operator-facing slugs that don't match registry names verbatim. The alias map is consulted before the slug-vs-legacy detection, so claude-code now resolves to anthropic-oauth and the lead boots through OAuth as intended. Tests ----- + test_persona_env_lead_with_minimax_model_routes_via_oauth — lock in the alias-map behavior so a future contributor can't silently re-introduce the lead-mis-routed-to-MiniMax bug. + test_anthropic_alias_resolves_to_anthropic_api — covers the second alias path. Updated test_persona_env_lead_claude_code_resolves_correctly to assert the new (correct) behavior: provider == 'anthropic-oauth', not None. Full adapter suite: 78/78 pass. --- adapter.py | 40 ++++++++++++++++++-- tests/test_env_model_provider_dispatch.py | 45 +++++++++++++++++++---- 2 files changed, 73 insertions(+), 12 deletions(-) diff --git a/adapter.py b/adapter.py index 9465df5..a1e5003 100644 --- a/adapter.py +++ b/adapter.py @@ -278,6 +278,26 @@ def _load_providers(config_path: str) -> tuple: return tuple(parsed) +# Aliases for `MODEL_PROVIDER` env values that should map to a registry +# provider name. The persona env files use shorter / friendlier slugs +# than the registry's canonical names — without this alias map a value +# like ``MODEL_PROVIDER=claude-code`` would fall through to YAML-based +# resolution and (when the YAML doesn't pin a provider) hit the +# model-prefix matcher with the operator-picked MODEL, mis-routing a +# lead workspace through MiniMax even though its CLAUDE_CODE_OAUTH_TOKEN +# was clearly meant to be used. +# +# Maintain this list in sync with the persona env file convention: +# - ``claude-code`` → ``anthropic-oauth`` (Claude Code subscription path) +# - ``anthropic`` → ``anthropic-api`` (direct Anthropic API key) +# Provider names already in the registry alias to themselves implicitly +# (the ``in registry`` check catches them before this map is consulted). +_PROVIDER_SLUG_ALIASES = { + "claude-code": "anthropic-oauth", + "anthropic": "anthropic-api", +} + + def _resolve_model_and_provider_from_env( yaml_model: str, yaml_provider: str, @@ -331,8 +351,20 @@ def _resolve_model_and_provider_from_env( # (provider name) vs. the legacy convention (model id). Persona- # convention wins when the value matches a registered provider; we # fall back to legacy interpretation only when it doesn't. + # + # First, apply the alias map so persona-friendly slugs like + # ``claude-code`` resolve to the canonical registry name + # ``anthropic-oauth``. Without this, a lead workspace's + # ``MODEL_PROVIDER=claude-code`` env would fall through to the model- + # prefix matcher, see ``MODEL=MiniMax-M2.7`` and mis-route to MiniMax + # even though the operator's intent (and the OAuth token they set) + # was the OAuth subscription path. + env_provider_resolved = _PROVIDER_SLUG_ALIASES.get( + env_provider.lower(), env_provider, + ) if env_provider else "" env_provider_is_slug = ( - bool(env_provider) and env_provider.lower() in provider_names_lower + bool(env_provider_resolved) + and env_provider_resolved.lower() in provider_names_lower ) # Picked model resolution @@ -345,10 +377,10 @@ def _resolve_model_and_provider_from_env( else: picked_model = yaml_model or "" - # Explicit provider resolution — env wins when it's a registered slug, - # otherwise fall back to YAML. + # Explicit provider resolution — env wins when it's a registered slug + # (after alias mapping), otherwise fall back to YAML. if env_provider_is_slug: - explicit_provider = env_provider + explicit_provider = env_provider_resolved else: explicit_provider = yaml_provider or None diff --git a/tests/test_env_model_provider_dispatch.py b/tests/test_env_model_provider_dispatch.py index 3cd1bb1..1ef31a1 100644 --- a/tests/test_env_model_provider_dispatch.py +++ b/tests/test_env_model_provider_dispatch.py @@ -73,10 +73,11 @@ def test_persona_env_minimax_resolves_correctly(monkeypatch): def test_persona_env_lead_claude_code_resolves_correctly(monkeypatch): """Lead persona env (MODEL=opus, MODEL_PROVIDER=claude-code) — - ``claude-code`` isn't a registered provider name (registry uses - ``anthropic-oauth``), so it falls back to legacy interpretation - and yields no explicit provider, letting the model-based - fall-through to providers[0]=anthropic-oauth do the right thing.""" + ``claude-code`` is the persona-friendly alias for the canonical + ``anthropic-oauth`` registry name. Must resolve via the alias map + so the lead boots through the OAuth subscription path even when + MODEL is a non-Anthropic model id (e.g. an operator who picked + MiniMax in canvas but whose persona env still pins claude-code).""" _clear_env(monkeypatch) monkeypatch.setenv("MODEL", "opus") monkeypatch.setenv("MODEL_PROVIDER", "claude-code") @@ -84,10 +85,38 @@ def test_persona_env_lead_claude_code_resolves_correctly(monkeypatch): yaml_model="", yaml_provider="", providers=_REGISTRY, ) assert model == "opus" - # claude-code is not a registered slug, so this falls back — - # provider is None and the caller will model-resolve to - # anthropic-oauth via the alias match on "opus". - assert provider is None + # claude-code → anthropic-oauth via the alias map + assert provider == "anthropic-oauth" + + +def test_persona_env_lead_with_minimax_model_routes_via_oauth(monkeypatch): + """Lead workspace whose persona pins MODEL_PROVIDER=claude-code but + whose YAML/canvas selection happens to be a MiniMax model still + routes via OAuth — the persona's provider pin wins over the + model-prefix matcher. Without the alias map, the fall-through + mis-routed leads to MiniMax even when their CLAUDE_CODE_OAUTH_TOKEN + was set.""" + _clear_env(monkeypatch) + monkeypatch.setenv("MODEL", "MiniMax-M2.7") + monkeypatch.setenv("MODEL_PROVIDER", "claude-code") + model, provider = _resolve_model_and_provider_from_env( + yaml_model="", yaml_provider="", providers=_REGISTRY, + ) + assert model == "MiniMax-M2.7" + assert provider == "anthropic-oauth" + + +def test_anthropic_alias_resolves_to_anthropic_api(monkeypatch): + """``MODEL_PROVIDER=anthropic`` alias → ``anthropic-api`` (direct + Anthropic API key path).""" + _clear_env(monkeypatch) + monkeypatch.setenv("MODEL", "claude-opus-4-7") + monkeypatch.setenv("MODEL_PROVIDER", "anthropic") + model, provider = _resolve_model_and_provider_from_env( + yaml_model="", yaml_provider="", providers=_REGISTRY, + ) + assert model == "claude-opus-4-7" + assert provider == "anthropic-api" def test_persona_env_glm_resolves_correctly(monkeypatch):