diff --git a/src/skillspector/providers/_agent_cli_base.py b/src/skillspector/providers/_agent_cli_base.py index 3548a64e..8e0dfd95 100644 --- a/src/skillspector/providers/_agent_cli_base.py +++ b/src/skillspector/providers/_agent_cli_base.py @@ -39,7 +39,7 @@ class AgentCLIProviderBase: #: ``_provider.DEFAULT_MODEL`` lookup has an attribute; never pins a version. DEFAULT_MODEL: str = "" #: Optional path to a bundled ``model_registry.yaml`` for token budgets. CLI - #: providers leave this empty and fall back to package-wide default budgets. + #: providers leave this empty, but users can supply a global registry override. REGISTRY_PATH: str = "" # -- Credentials --------------------------------------------------------- @@ -81,13 +81,9 @@ def complete( # -- Metadata ------------------------------------------------------------ def get_context_length(self, model: str) -> int | None: - if not self.REGISTRY_PATH: - return None # no registry -> caller uses the package-wide default budget return registry.lookup_context_length(self.REGISTRY_PATH, model) def get_max_output_tokens(self, model: str) -> int | None: - if not self.REGISTRY_PATH: - return None return registry.lookup_max_output_tokens(self.REGISTRY_PATH, model) def resolve_model(self, slot: str = "default") -> str: diff --git a/tests/unit/test_providers.py b/tests/unit/test_providers.py index 6aafcff7..aa5ff1e2 100644 --- a/tests/unit/test_providers.py +++ b/tests/unit/test_providers.py @@ -23,6 +23,7 @@ from __future__ import annotations import sys +from pathlib import Path import pytest from langchain_anthropic import ChatAnthropic @@ -830,6 +831,62 @@ def test_is_available_reports_not_ready(self) -> None: assert reason +class TestAgentCLIProviderMetadata: + """Shared model-registry behavior for supported agent CLI providers.""" + + @pytest.mark.parametrize( + "provider_type", + [ClaudeCLIProvider, CodexCLIProvider, GeminiCLIProvider], + ) + def test_honors_model_registry_override( + self, + provider_type: type[ClaudeCLIProvider | CodexCLIProvider | GeminiCLIProvider], + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + ) -> None: + registry_path = tmp_path / "model_registry.yaml" + registry_path.write_text( + "models:\n test-model:\n context_length: 200000\n max_output_tokens: 32000\n", + encoding="utf-8", + ) + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) + + provider = provider_type() + assert provider.get_context_length("test-model") == 200_000 + assert provider.get_max_output_tokens("test-model") == 32_000 + + @pytest.mark.parametrize("registry_value", [None, " "]) + def test_returns_none_without_registry( + self, + registry_value: str | None, + monkeypatch: pytest.MonkeyPatch, + ) -> None: + if registry_value is None: + monkeypatch.delenv("SKILLSPECTOR_MODEL_REGISTRY", raising=False) + else: + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", registry_value) + + provider = ClaudeCLIProvider() + assert provider.get_context_length("test-model") is None + assert provider.get_max_output_tokens("test-model") is None + + def test_unknown_model_returns_none( + self, + monkeypatch: pytest.MonkeyPatch, + tmp_path: Path, + ) -> None: + registry_path = tmp_path / "model_registry.yaml" + registry_path.write_text( + "models:\n known-model:\n context_length: 200000\n max_output_tokens: 32000\n", + encoding="utf-8", + ) + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) + + provider = ClaudeCLIProvider() + assert provider.get_context_length("unknown-model") is None + assert provider.get_max_output_tokens("unknown-model") is None + + class TestClaudeCLIProvider: """Claude CLI provider — metadata, availability, and capability detection."""