|
23 | 23 | from __future__ import annotations |
24 | 24 |
|
25 | 25 | import sys |
| 26 | +from pathlib import Path |
26 | 27 |
|
27 | 28 | import pytest |
28 | 29 | from langchain_anthropic import ChatAnthropic |
@@ -830,6 +831,108 @@ def test_is_available_reports_not_ready(self) -> None: |
830 | 831 | assert reason |
831 | 832 |
|
832 | 833 |
|
| 834 | +class TestAgentCLIProviderMetadata: |
| 835 | + """Shared model-registry behavior for supported agent CLI providers.""" |
| 836 | + |
| 837 | + @pytest.mark.parametrize( |
| 838 | + "provider_type", [ClaudeCLIProvider, CodexCLIProvider, GeminiCLIProvider] |
| 839 | + ) |
| 840 | + @pytest.mark.parametrize( |
| 841 | + "contents", |
| 842 | + [ |
| 843 | + "models: [test-model]\n", |
| 844 | + "models:\n test-model: 42\n", |
| 845 | + "models:\n test-model:\n context_length: lots\n max_output_tokens: lots\n", |
| 846 | + "models:\n test-model:\n context_length: -1\n max_output_tokens: -1\n", |
| 847 | + "models:\n test-model:\n context_length: 0\n max_output_tokens: 0\n", |
| 848 | + "models:\n test-model:\n context_length: .inf\n max_output_tokens: .inf\n", |
| 849 | + ], |
| 850 | + ids=["models-list", "scalar-entry", "bad-string", "negative", "zero", "infinite"], |
| 851 | + ) |
| 852 | + def test_malformed_registry_returns_none( |
| 853 | + self, |
| 854 | + provider_type: type[ClaudeCLIProvider | CodexCLIProvider | GeminiCLIProvider], |
| 855 | + contents: str, |
| 856 | + monkeypatch: pytest.MonkeyPatch, |
| 857 | + tmp_path: Path, |
| 858 | + ) -> None: |
| 859 | + registry_path = tmp_path / "model_registry.yaml" |
| 860 | + registry_path.write_text(contents, encoding="utf-8") |
| 861 | + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) |
| 862 | + |
| 863 | + provider = provider_type() |
| 864 | + assert provider.get_context_length("test-model") is None |
| 865 | + assert provider.get_max_output_tokens("test-model") is None |
| 866 | + |
| 867 | + @pytest.mark.parametrize("invalid_field", ["context_length", "max_output_tokens"]) |
| 868 | + def test_invalid_budget_preserves_other_field( |
| 869 | + self, invalid_field: str, monkeypatch: pytest.MonkeyPatch, tmp_path: Path |
| 870 | + ) -> None: |
| 871 | + valid_field = "max_output_tokens" if invalid_field == "context_length" else "context_length" |
| 872 | + registry_path = tmp_path / "model_registry.yaml" |
| 873 | + registry_path.write_text( |
| 874 | + f'models:\n test-model:\n {invalid_field}: lots\n {valid_field}: "32000"\n', |
| 875 | + encoding="utf-8", |
| 876 | + ) |
| 877 | + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) |
| 878 | + |
| 879 | + provider = ClaudeCLIProvider() |
| 880 | + assert getattr(provider, f"get_{invalid_field}")("test-model") is None |
| 881 | + assert getattr(provider, f"get_{valid_field}")("test-model") == 32_000 |
| 882 | + |
| 883 | + @pytest.mark.parametrize( |
| 884 | + "provider_type", |
| 885 | + [ClaudeCLIProvider, CodexCLIProvider, GeminiCLIProvider], |
| 886 | + ) |
| 887 | + def test_honors_model_registry_override( |
| 888 | + self, |
| 889 | + provider_type: type[ClaudeCLIProvider | CodexCLIProvider | GeminiCLIProvider], |
| 890 | + monkeypatch: pytest.MonkeyPatch, |
| 891 | + tmp_path: Path, |
| 892 | + ) -> None: |
| 893 | + registry_path = tmp_path / "model_registry.yaml" |
| 894 | + registry_path.write_text( |
| 895 | + "models:\n test-model:\n context_length: 200000\n max_output_tokens: 32000\n", |
| 896 | + encoding="utf-8", |
| 897 | + ) |
| 898 | + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) |
| 899 | + |
| 900 | + provider = provider_type() |
| 901 | + assert provider.get_context_length("test-model") == 200_000 |
| 902 | + assert provider.get_max_output_tokens("test-model") == 32_000 |
| 903 | + |
| 904 | + @pytest.mark.parametrize("registry_value", [None, " "]) |
| 905 | + def test_returns_none_without_registry( |
| 906 | + self, |
| 907 | + registry_value: str | None, |
| 908 | + monkeypatch: pytest.MonkeyPatch, |
| 909 | + ) -> None: |
| 910 | + if registry_value is None: |
| 911 | + monkeypatch.delenv("SKILLSPECTOR_MODEL_REGISTRY", raising=False) |
| 912 | + else: |
| 913 | + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", registry_value) |
| 914 | + |
| 915 | + provider = ClaudeCLIProvider() |
| 916 | + assert provider.get_context_length("test-model") is None |
| 917 | + assert provider.get_max_output_tokens("test-model") is None |
| 918 | + |
| 919 | + def test_unknown_model_returns_none( |
| 920 | + self, |
| 921 | + monkeypatch: pytest.MonkeyPatch, |
| 922 | + tmp_path: Path, |
| 923 | + ) -> None: |
| 924 | + registry_path = tmp_path / "model_registry.yaml" |
| 925 | + registry_path.write_text( |
| 926 | + "models:\n known-model:\n context_length: 200000\n max_output_tokens: 32000\n", |
| 927 | + encoding="utf-8", |
| 928 | + ) |
| 929 | + monkeypatch.setenv("SKILLSPECTOR_MODEL_REGISTRY", str(registry_path)) |
| 930 | + |
| 931 | + provider = ClaudeCLIProvider() |
| 932 | + assert provider.get_context_length("unknown-model") is None |
| 933 | + assert provider.get_max_output_tokens("unknown-model") is None |
| 934 | + |
| 935 | + |
833 | 936 | class TestClaudeCLIProvider: |
834 | 937 | """Claude CLI provider — metadata, availability, and capability detection.""" |
835 | 938 |
|
|
0 commit comments