diff --git a/src/ucode/agents/claude.py b/src/ucode/agents/claude.py index 5fad522..9f4df25 100644 --- a/src/ucode/agents/claude.py +++ b/src/ucode/agents/claude.py @@ -40,6 +40,7 @@ from ucode.tracing import tracing_env from ucode.ui import print_err, print_note, print_success, print_warning +GATEWAY_MODEL_DISCOVERY_ENV_VAR = "ENABLE_CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY" CLAUDE_CONFIG_DIR = Path.home() / ".claude" CLAUDE_SETTINGS_PATH = CLAUDE_CONFIG_DIR / "ucode-settings.json" CLAUDE_BACKUP_PATH = APP_DIR / "claude-ucode-settings.backup.json" @@ -302,6 +303,12 @@ def render_overlay( "ENABLE_TOOL_SEARCH": "1", "CLAUDE_CODE_USE_GATEWAY": "1", } + # Native /model discovery: picker lists every gateway Messages-API endpoint, + # not just the family aliases. Skipped under a provider (its routing header + # would send a discovered gateway id to a provider that can't resolve it). + discovery_enabled = os.environ.get(GATEWAY_MODEL_DISCOVERY_ENV_VAR) == "1" + if discovery_enabled and not provider: + env["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] = "1" # Intentionally NOT setting ANTHROPIC_MODEL by default. Setting it produces a # duplicate catalog row in Claude Code's /model picker (e.g. "Opus 4.8 (1M # context) ✓") on top of the family-alias row from ANTHROPIC_DEFAULT_OPUS_MODEL. diff --git a/tests/conftest.py b/tests/conftest.py index 3e460ed..03d09ad 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -34,6 +34,7 @@ def _isolate_ucode_state(tmp_path, monkeypatch): # Isolate the managed-config opt-in from the developer's own shell: leaving it set changes what # `ucode`/`ucode configure` do mid-test. Tests that exercise the managed path set it explicitly. monkeypatch.delenv("ENABLE_MANAGED_AGENT_CONFIG", raising=False) + monkeypatch.delenv("ENABLE_CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY", raising=False) # The model-services listing is memoized for the life of the process, so without this a cached # result would leak into the next test and make a stubbed listing look like it was never called. databricks_mod.clear_model_services_cache() diff --git a/tests/test_agent_claude.py b/tests/test_agent_claude.py index b45b630..37b7401 100644 --- a/tests/test_agent_claude.py +++ b/tests/test_agent_claude.py @@ -127,6 +127,26 @@ def test_enables_use_gateway(self): overlay, _ = claude.render_overlay(WS, "s4") assert overlay["env"]["CLAUDE_CODE_USE_GATEWAY"] == "1" + @pytest.mark.parametrize("env_value", [None, "", "0", "true", "yes"]) + def test_gateway_model_discovery_disabled_unless_opted_in(self, monkeypatch, env_value): + if env_value is not None: + monkeypatch.setenv("ENABLE_CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY", env_value) + overlay, _ = claude.render_overlay(WS, "s4") + assert "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY" not in overlay["env"] + + def test_enables_gateway_model_discovery(self, monkeypatch): + monkeypatch.setenv("ENABLE_CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY", "1") + overlay, _ = claude.render_overlay(WS, "s4") + assert overlay["env"]["CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY"] == "1" + + def test_gateway_model_discovery_skipped_under_provider(self, monkeypatch): + # A Model Provider Service routes every request to the external provider, + # so a discovered gateway endpoint id would reach a provider that can't + # resolve it — discovery must be off in that mode. + monkeypatch.setenv("ENABLE_CLAUDE_CODE_GATEWAY_MODEL_DISCOVERY", "1") + overlay, _ = claude.render_overlay(WS, "s4", provider="main.x.claude-svc") + assert "CLAUDE_CODE_ENABLE_GATEWAY_MODEL_DISCOVERY" not in overlay["env"] + def test_sets_api_key_helper(self): overlay, _ = claude.render_overlay(WS, "s4") assert "apiKeyHelper" in overlay