feat: Update Claude model references from v3 to v4 (fixes issue #118) (#119)

* feat: Update Claude model references from v3 to v4

- Update model configurations from claude-3-opus to claude-4-opus
- Update model configurations from claude-3-sonnet to claude-4-sonnet
- Maintain backward compatibility through existing aliases (opus, sonnet, claude)
- Update provider registry preferred models list
- Update all test cases and assertions to reflect new model names
- Update documentation and examples consistently across all files
- Add Claude 4 model support while preserving existing functionality

Files modified: 15 (config, docs, providers, tests, tools)
Pattern: Systematic claude-3-* → claude-4-* model reference migration

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* PR feedback: changed anthropic/claude-4-opus -> anthropic/claude-opus-4 and anthropic/claude-4-haiku -> anthropic/claude-3.5-haiku

* changed anthropic/claude-4-sonnet -> anthropic/claude-sonnet-4

* PR feedback removed specific model from test mock

* PR feedback removed base.py

---------

Co-authored-by: Omry Nachman <omry@wix.com>
Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
omryn-vera
2025-06-23 11:57:13 +02:00
committed by GitHub
parent 8262d47c1e
commit 4ae0344b14
13 changed files with 63 additions and 63 deletions

View File

@@ -7,7 +7,7 @@
"Self-hosted APIs - Any OpenAI-compatible endpoint" "Self-hosted APIs - Any OpenAI-compatible endpoint"
], ],
"documentation": "https://github.com/BeehiveInnovations/zen-mcp-server/blob/main/docs/custom_models.md", "documentation": "https://github.com/BeehiveInnovations/zen-mcp-server/blob/main/docs/custom_models.md",
"usage": "Models can be accessed via aliases (e.g., 'opus', 'local-llama') or full names (e.g., 'anthropic/claude-3-opus', 'llama3.2')", "usage": "Models can be accessed via aliases (e.g., 'opus', 'local-llama') or full names (e.g., 'anthropic/claude-opus-4', 'llama3.2')",
"instructions": [ "instructions": [
"Add new models by copying an existing entry and modifying it", "Add new models by copying an existing entry and modifying it",
"Aliases are case-insensitive and should be unique across all models", "Aliases are case-insensitive and should be unique across all models",
@@ -15,11 +15,11 @@
"Set supports_* flags based on the model's actual capabilities", "Set supports_* flags based on the model's actual capabilities",
"Set is_custom=true for models that should ONLY work with custom endpoints (Ollama, vLLM, etc.)", "Set is_custom=true for models that should ONLY work with custom endpoints (Ollama, vLLM, etc.)",
"Models not listed here will use generic defaults (32K context window, basic features)", "Models not listed here will use generic defaults (32K context window, basic features)",
"For OpenRouter models: Use official OpenRouter model names (e.g., 'anthropic/claude-3-opus')", "For OpenRouter models: Use official OpenRouter model names (e.g., 'anthropic/claude-opus-4')",
"For local/custom models: Use model names as they appear in your API (e.g., 'llama3.2', 'gpt-3.5-turbo')" "For local/custom models: Use model names as they appear in your API (e.g., 'llama3.2', 'gpt-3.5-turbo')"
], ],
"field_descriptions": { "field_descriptions": {
"model_name": "The model identifier - OpenRouter format (e.g., 'anthropic/claude-3-opus') or custom model name (e.g., 'llama3.2')", "model_name": "The model identifier - OpenRouter format (e.g., 'anthropic/claude-opus-4') or custom model name (e.g., 'llama3.2')",
"aliases": "Array of short names users can type instead of the full model name", "aliases": "Array of short names users can type instead of the full model name",
"context_window": "Total number of tokens the model can process (input + output combined)", "context_window": "Total number of tokens the model can process (input + output combined)",
"supports_extended_thinking": "Whether the model supports extended reasoning tokens (currently none do via OpenRouter or custom APIs)", "supports_extended_thinking": "Whether the model supports extended reasoning tokens (currently none do via OpenRouter or custom APIs)",
@@ -49,29 +49,29 @@
}, },
"models": [ "models": [
{ {
"model_name": "anthropic/claude-3-opus", "model_name": "anthropic/claude-opus-4",
"aliases": ["opus", "claude-opus", "claude3-opus", "claude-3-opus"], "aliases": ["opus", "claude-opus", "claude4-opus", "claude-4-opus"],
"context_window": 200000, "context_window": 200000,
"supports_extended_thinking": false, "supports_extended_thinking": false,
"supports_json_mode": false, "supports_json_mode": false,
"supports_function_calling": false, "supports_function_calling": false,
"supports_images": true, "supports_images": true,
"max_image_size_mb": 5.0, "max_image_size_mb": 5.0,
"description": "Claude 3 Opus - Most capable Claude model with vision" "description": "Claude 4 Opus - Most capable Claude model with vision"
}, },
{ {
"model_name": "anthropic/claude-3-sonnet", "model_name": "anthropic/claude-sonnet-4",
"aliases": ["sonnet", "claude-sonnet", "claude3-sonnet", "claude-3-sonnet", "claude"], "aliases": ["sonnet", "claude-sonnet", "claude4-sonnet", "claude-4-sonnet", "claude"],
"context_window": 200000, "context_window": 200000,
"supports_extended_thinking": false, "supports_extended_thinking": false,
"supports_json_mode": false, "supports_json_mode": false,
"supports_function_calling": false, "supports_function_calling": false,
"supports_images": true, "supports_images": true,
"max_image_size_mb": 5.0, "max_image_size_mb": 5.0,
"description": "Claude 3 Sonnet - Balanced performance with vision" "description": "Claude 4 Sonnet - Balanced performance with vision"
}, },
{ {
"model_name": "anthropic/claude-3-haiku", "model_name": "anthropic/claude-3.5-haiku",
"aliases": ["haiku", "claude-haiku", "claude3-haiku", "claude-3-haiku"], "aliases": ["haiku", "claude-haiku", "claude3-haiku", "claude-3-haiku"],
"context_window": 200000, "context_window": 200000,
"supports_extended_thinking": false, "supports_extended_thinking": false,

View File

@@ -690,7 +690,7 @@ When a user requests a model (e.g., "pro", "o3", "example-large-v1"), the system
2. OpenAI skips (Gemini already handled it) 2. OpenAI skips (Gemini already handled it)
3. OpenRouter never sees it 3. OpenRouter never sees it
### Example: Model "claude-3-opus" ### Example: Model "claude-4-opus"
1. **Gemini provider** checks: NO, not my model → skip 1. **Gemini provider** checks: NO, not my model → skip
2. **OpenAI provider** checks: NO, not my model → skip 2. **OpenAI provider** checks: NO, not my model → skip

View File

@@ -41,9 +41,9 @@ The server uses `conf/custom_models.json` to map convenient aliases to both Open
| Alias | Maps to OpenRouter Model | | Alias | Maps to OpenRouter Model |
|-------|-------------------------| |-------|-------------------------|
| `opus` | `anthropic/claude-3-opus` | | `opus` | `anthropic/claude-opus-4` |
| `sonnet`, `claude` | `anthropic/claude-3-sonnet` | | `sonnet`, `claude` | `anthropic/claude-sonnet-4` |
| `haiku` | `anthropic/claude-3-haiku` | | `haiku` | `anthropic/claude-3.5-haiku` |
| `gpt4o`, `4o` | `openai/gpt-4o` | | `gpt4o`, `4o` | `openai/gpt-4o` |
| `gpt4o-mini`, `4o-mini` | `openai/gpt-4o-mini` | | `gpt4o-mini`, `4o-mini` | `openai/gpt-4o-mini` |
| `pro`, `gemini` | `google/gemini-2.5-pro` | | `pro`, `gemini` | `google/gemini-2.5-pro` |
@@ -151,8 +151,8 @@ CUSTOM_MODEL_NAME=your-loaded-model
**Using model aliases (from conf/custom_models.json):** **Using model aliases (from conf/custom_models.json):**
``` ```
# OpenRouter models: # OpenRouter models:
"Use opus for deep analysis" # → anthropic/claude-3-opus "Use opus for deep analysis" # → anthropic/claude-opus-4
"Use sonnet to review this code" # → anthropic/claude-3-sonnet "Use sonnet to review this code" # → anthropic/claude-sonnet-4
"Use pro via zen to analyze this" # → google/gemini-2.5-pro "Use pro via zen to analyze this" # → google/gemini-2.5-pro
"Use gpt4o via zen to analyze this" # → openai/gpt-4o "Use gpt4o via zen to analyze this" # → openai/gpt-4o
"Use mistral via zen to optimize" # → mistral/mistral-large "Use mistral via zen to optimize" # → mistral/mistral-large
@@ -165,7 +165,7 @@ CUSTOM_MODEL_NAME=your-loaded-model
**Using full model names:** **Using full model names:**
``` ```
# OpenRouter models: # OpenRouter models:
"Use anthropic/claude-3-opus via zen for deep analysis" "Use anthropic/claude-opus-4 via zen for deep analysis"
"Use openai/gpt-4o via zen to debug this" "Use openai/gpt-4o via zen to debug this"
"Use deepseek/deepseek-coder via zen to generate code" "Use deepseek/deepseek-coder via zen to generate code"
@@ -249,7 +249,7 @@ Edit `conf/custom_models.json` to add new models. The configuration supports bot
Popular models available through OpenRouter: Popular models available through OpenRouter:
- **GPT-4** - OpenAI's most capable model - **GPT-4** - OpenAI's most capable model
- **Claude 3** - Anthropic's models (Opus, Sonnet, Haiku) - **Claude 4** - Anthropic's models (Opus, Sonnet, Haiku)
- **Mistral** - Including Mistral Large - **Mistral** - Including Mistral Large
- **Llama 3** - Meta's open models - **Llama 3** - Meta's open models
- Many more at [openrouter.ai/models](https://openrouter.ai/models) - Many more at [openrouter.ai/models](https://openrouter.ai/models)

View File

@@ -402,8 +402,8 @@ class ModelProviderRegistry:
if openrouter_provider: if openrouter_provider:
# Prefer models known for deep reasoning # Prefer models known for deep reasoning
preferred_models = [ preferred_models = [
"anthropic/claude-3.5-sonnet", "anthropic/claude-sonnet-4",
"anthropic/claude-3-opus-20240229", "anthropic/claude-opus-4",
"google/gemini-2.5-pro", "google/gemini-2.5-pro",
"google/gemini-pro-1.5", "google/gemini-pro-1.5",
"meta-llama/llama-3.1-70b-instruct", "meta-llama/llama-3.1-70b-instruct",

View File

@@ -117,7 +117,7 @@ class OpenRouterModelsTest(BaseSimulatorTest):
self.logger.info(" ✅ Direct OpenRouter model call completed") self.logger.info(" ✅ Direct OpenRouter model call completed")
# Test 5: OpenRouter alias from config # Test 5: OpenRouter alias from config
self.logger.info(" 5: Testing OpenRouter alias from config ('opus' -> anthropic/claude-3-opus)") self.logger.info(" 5: Testing OpenRouter alias from config ('opus' -> anthropic/claude-opus-4)")
response5, _ = self.call_mcp_tool( response5, _ = self.call_mcp_tool(
"chat", "chat",

View File

@@ -527,7 +527,7 @@ class TestAutoModeComprehensive:
"google/gemini-2.5-pro", "google/gemini-2.5-pro",
"openai/o3", "openai/o3",
"openai/o4-mini", "openai/o4-mini",
"anthropic/claude-3-opus", "anthropic/claude-opus-4",
] ]
with patch.object(OpenRouterProvider, "_registry", mock_registry): with patch.object(OpenRouterProvider, "_registry", mock_registry):

View File

@@ -53,8 +53,8 @@ class TestListModelsRestrictions(unittest.TestCase):
# Set up mock to return only allowed models when restrictions are respected # Set up mock to return only allowed models when restrictions are respected
# Include both aliased models and full model names without aliases # Include both aliased models and full model names without aliases
self.mock_openrouter.list_models.return_value = [ self.mock_openrouter.list_models.return_value = [
"anthropic/claude-3-opus-20240229", # Has alias "opus" "anthropic/claude-opus-4", # Has alias "opus"
"anthropic/claude-3-sonnet-20240229", # Has alias "sonnet" "anthropic/claude-sonnet-4", # Has alias "sonnet"
"deepseek/deepseek-r1-0528:free", # No alias, full name "deepseek/deepseek-r1-0528:free", # No alias, full name
"qwen/qwen3-235b-a22b-04-28:free", # No alias, full name "qwen/qwen3-235b-a22b-04-28:free", # No alias, full name
] ]
@@ -67,12 +67,12 @@ class TestListModelsRestrictions(unittest.TestCase):
def resolve_side_effect(model_name): def resolve_side_effect(model_name):
if "opus" in model_name.lower(): if "opus" in model_name.lower():
config = MagicMock() config = MagicMock()
config.model_name = "anthropic/claude-3-opus-20240229" config.model_name = "anthropic/claude-opus-4-20240229"
config.context_window = 200000 config.context_window = 200000
return config return config
elif "sonnet" in model_name.lower(): elif "sonnet" in model_name.lower():
config = MagicMock() config = MagicMock()
config.model_name = "anthropic/claude-3-sonnet-20240229" config.model_name = "anthropic/claude-sonnet-4-20240229"
config.context_window = 200000 config.context_window = 200000
return config return config
return None # No config for models without aliases return None # No config for models without aliases
@@ -93,8 +93,8 @@ class TestListModelsRestrictions(unittest.TestCase):
mock_get_models.return_value = { mock_get_models.return_value = {
"gemini-2.5-flash": ProviderType.GOOGLE, "gemini-2.5-flash": ProviderType.GOOGLE,
"gemini-2.5-pro": ProviderType.GOOGLE, "gemini-2.5-pro": ProviderType.GOOGLE,
"anthropic/claude-3-opus-20240229": ProviderType.OPENROUTER, "anthropic/claude-opus-4-20240229": ProviderType.OPENROUTER,
"anthropic/claude-3-sonnet-20240229": ProviderType.OPENROUTER, "anthropic/claude-sonnet-4-20240229": ProviderType.OPENROUTER,
"deepseek/deepseek-r1-0528:free": ProviderType.OPENROUTER, "deepseek/deepseek-r1-0528:free": ProviderType.OPENROUTER,
"qwen/qwen3-235b-a22b-04-28:free": ProviderType.OPENROUTER, "qwen/qwen3-235b-a22b-04-28:free": ProviderType.OPENROUTER,
} }
@@ -172,7 +172,7 @@ class TestListModelsRestrictions(unittest.TestCase):
utils.model_restrictions._restriction_service = None utils.model_restrictions._restriction_service = None
# Set up mock to return many models when no restrictions # Set up mock to return many models when no restrictions
all_models = [f"provider{i//10}/model-{i}" for i in range(50)] # Simulate 50 models from different providers all_models = [f"provider{i // 10}/model-{i}" for i in range(50)] # Simulate 50 models from different providers
self.mock_openrouter.list_models.return_value = all_models self.mock_openrouter.list_models.return_value = all_models
# Mock registry instance # Mock registry instance

View File

@@ -24,7 +24,7 @@ class TestModelRestrictionService:
assert service.is_allowed(ProviderType.OPENAI, "o3-mini") assert service.is_allowed(ProviderType.OPENAI, "o3-mini")
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro") assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro")
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-flash") assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-flash")
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-opus") assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-opus-4")
assert service.is_allowed(ProviderType.OPENROUTER, "openai/o3") assert service.is_allowed(ProviderType.OPENROUTER, "openai/o3")
# Should have no restrictions # Should have no restrictions
@@ -44,7 +44,7 @@ class TestModelRestrictionService:
# Google and OpenRouter should have no restrictions # Google and OpenRouter should have no restrictions
assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro") assert service.is_allowed(ProviderType.GOOGLE, "gemini-2.5-pro")
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-opus") assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-opus-4")
def test_load_multiple_models_restriction(self): def test_load_multiple_models_restriction(self):
"""Test loading multiple allowed models.""" """Test loading multiple allowed models."""
@@ -159,7 +159,7 @@ class TestModelRestrictionService:
# Should only allow specified OpenRouter models # Should only allow specified OpenRouter models
assert service.is_allowed(ProviderType.OPENROUTER, "opus") assert service.is_allowed(ProviderType.OPENROUTER, "opus")
assert service.is_allowed(ProviderType.OPENROUTER, "sonnet") assert service.is_allowed(ProviderType.OPENROUTER, "sonnet")
assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-opus", "opus") # With original name assert service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-opus-4", "opus") # With original name
assert not service.is_allowed(ProviderType.OPENROUTER, "haiku") assert not service.is_allowed(ProviderType.OPENROUTER, "haiku")
assert not service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-haiku") assert not service.is_allowed(ProviderType.OPENROUTER, "anthropic/claude-3-haiku")
assert not service.is_allowed(ProviderType.OPENROUTER, "mistral-large") assert not service.is_allowed(ProviderType.OPENROUTER, "mistral-large")

View File

@@ -44,7 +44,7 @@ class TestOpenRouterProvider:
# Should accept any model - OpenRouter handles validation # Should accept any model - OpenRouter handles validation
assert provider.validate_model_name("gpt-4") is True assert provider.validate_model_name("gpt-4") is True
assert provider.validate_model_name("claude-3-opus") is True assert provider.validate_model_name("claude-4-opus") is True
assert provider.validate_model_name("any-model-name") is True assert provider.validate_model_name("any-model-name") is True
assert provider.validate_model_name("GPT-4") is True assert provider.validate_model_name("GPT-4") is True
assert provider.validate_model_name("unknown-model") is True assert provider.validate_model_name("unknown-model") is True
@@ -71,26 +71,26 @@ class TestOpenRouterProvider:
provider = OpenRouterProvider(api_key="test-key") provider = OpenRouterProvider(api_key="test-key")
# Test alias resolution # Test alias resolution
assert provider._resolve_model_name("opus") == "anthropic/claude-3-opus" assert provider._resolve_model_name("opus") == "anthropic/claude-opus-4"
assert provider._resolve_model_name("sonnet") == "anthropic/claude-3-sonnet" assert provider._resolve_model_name("sonnet") == "anthropic/claude-sonnet-4"
assert provider._resolve_model_name("o3") == "openai/o3" assert provider._resolve_model_name("o3") == "openai/o3"
assert provider._resolve_model_name("o3-mini") == "openai/o3-mini" assert provider._resolve_model_name("o3-mini") == "openai/o3-mini"
assert provider._resolve_model_name("o3mini") == "openai/o3-mini" assert provider._resolve_model_name("o3mini") == "openai/o3-mini"
assert provider._resolve_model_name("o4-mini") == "openai/o4-mini" assert provider._resolve_model_name("o4-mini") == "openai/o4-mini"
assert provider._resolve_model_name("o4-mini-high") == "openai/o4-mini-high" assert provider._resolve_model_name("o4-mini-high") == "openai/o4-mini-high"
assert provider._resolve_model_name("claude") == "anthropic/claude-3-sonnet" assert provider._resolve_model_name("claude") == "anthropic/claude-sonnet-4"
assert provider._resolve_model_name("mistral") == "mistralai/mistral-large-2411" assert provider._resolve_model_name("mistral") == "mistralai/mistral-large-2411"
assert provider._resolve_model_name("deepseek") == "deepseek/deepseek-r1-0528" assert provider._resolve_model_name("deepseek") == "deepseek/deepseek-r1-0528"
assert provider._resolve_model_name("r1") == "deepseek/deepseek-r1-0528" assert provider._resolve_model_name("r1") == "deepseek/deepseek-r1-0528"
# Test case-insensitive # Test case-insensitive
assert provider._resolve_model_name("OPUS") == "anthropic/claude-3-opus" assert provider._resolve_model_name("OPUS") == "anthropic/claude-opus-4"
assert provider._resolve_model_name("O3") == "openai/o3" assert provider._resolve_model_name("O3") == "openai/o3"
assert provider._resolve_model_name("Mistral") == "mistralai/mistral-large-2411" assert provider._resolve_model_name("Mistral") == "mistralai/mistral-large-2411"
assert provider._resolve_model_name("CLAUDE") == "anthropic/claude-3-sonnet" assert provider._resolve_model_name("CLAUDE") == "anthropic/claude-sonnet-4"
# Test direct model names (should pass through unchanged) # Test direct model names (should pass through unchanged)
assert provider._resolve_model_name("anthropic/claude-3-opus") == "anthropic/claude-3-opus" assert provider._resolve_model_name("anthropic/claude-opus-4") == "anthropic/claude-opus-4"
assert provider._resolve_model_name("openai/o3") == "openai/o3" assert provider._resolve_model_name("openai/o3") == "openai/o3"
# Test unknown models pass through # Test unknown models pass through
@@ -155,8 +155,8 @@ class TestOpenRouterAutoMode:
"google/gemini-2.5-pro", "google/gemini-2.5-pro",
"openai/o3", "openai/o3",
"openai/o3-mini", "openai/o3-mini",
"anthropic/claude-3-opus", "anthropic/claude-opus-4",
"anthropic/claude-3-sonnet", "anthropic/claude-sonnet-4",
] ]
ModelProviderRegistry.register_provider(ProviderType.OPENROUTER, OpenRouterProvider) ModelProviderRegistry.register_provider(ProviderType.OPENROUTER, OpenRouterProvider)
@@ -181,7 +181,7 @@ class TestOpenRouterAutoMode:
os.environ.pop("OPENAI_API_KEY", None) os.environ.pop("OPENAI_API_KEY", None)
os.environ["OPENROUTER_API_KEY"] = "test-openrouter-key" os.environ["OPENROUTER_API_KEY"] = "test-openrouter-key"
os.environ.pop("OPENROUTER_ALLOWED_MODELS", None) os.environ.pop("OPENROUTER_ALLOWED_MODELS", None)
os.environ["OPENROUTER_ALLOWED_MODELS"] = "anthropic/claude-3-opus,google/gemini-2.5-flash" os.environ["OPENROUTER_ALLOWED_MODELS"] = "anthropic/claude-opus-4,google/gemini-2.5-flash"
os.environ["DEFAULT_MODEL"] = "auto" os.environ["DEFAULT_MODEL"] = "auto"
# Force reload to pick up new environment variable # Force reload to pick up new environment variable
@@ -193,8 +193,8 @@ class TestOpenRouterAutoMode:
mock_models = [ mock_models = [
"google/gemini-2.5-flash", "google/gemini-2.5-flash",
"google/gemini-2.5-pro", "google/gemini-2.5-pro",
"anthropic/claude-3-opus", "anthropic/claude-opus-4",
"anthropic/claude-3-sonnet", "anthropic/claude-sonnet-4",
] ]
mock_registry.list_models.return_value = mock_models mock_registry.list_models.return_value = mock_models
@@ -212,7 +212,7 @@ class TestOpenRouterAutoMode:
assert len(available_models) > 0, "Should have some allowed models" assert len(available_models) > 0, "Should have some allowed models"
expected_allowed = {"google/gemini-2.5-flash", "anthropic/claude-3-opus"} expected_allowed = {"google/gemini-2.5-flash", "anthropic/claude-opus-4"}
assert ( assert (
set(available_models.keys()) == expected_allowed set(available_models.keys()) == expected_allowed
@@ -263,7 +263,7 @@ class TestOpenRouterRegistry:
# Should have loaded models # Should have loaded models
models = registry.list_models() models = registry.list_models()
assert len(models) > 0 assert len(models) > 0
assert "anthropic/claude-3-opus" in models assert "anthropic/claude-opus-4" in models
assert "openai/o3" in models assert "openai/o3" in models
# Should have loaded aliases # Should have loaded aliases
@@ -282,13 +282,13 @@ class TestOpenRouterRegistry:
# Test known model # Test known model
caps = registry.get_capabilities("opus") caps = registry.get_capabilities("opus")
assert caps is not None assert caps is not None
assert caps.model_name == "anthropic/claude-3-opus" assert caps.model_name == "anthropic/claude-opus-4"
assert caps.context_window == 200000 # Claude's context window assert caps.context_window == 200000 # Claude's context window
# Test using full model name # Test using full model name
caps = registry.get_capabilities("anthropic/claude-3-opus") caps = registry.get_capabilities("anthropic/claude-opus-4")
assert caps is not None assert caps is not None
assert caps.model_name == "anthropic/claude-3-opus" assert caps.model_name == "anthropic/claude-opus-4"
# Test unknown model # Test unknown model
caps = registry.get_capabilities("non-existent-model") caps = registry.get_capabilities("non-existent-model")
@@ -301,11 +301,11 @@ class TestOpenRouterRegistry:
registry = OpenRouterModelRegistry() registry = OpenRouterModelRegistry()
# All these should resolve to Claude Sonnet # All these should resolve to Claude Sonnet
sonnet_aliases = ["sonnet", "claude", "claude-sonnet", "claude3-sonnet"] sonnet_aliases = ["sonnet", "claude", "claude-sonnet", "claude4-sonnet"]
for alias in sonnet_aliases: for alias in sonnet_aliases:
config = registry.resolve(alias) config = registry.resolve(alias)
assert config is not None assert config is not None
assert config.model_name == "anthropic/claude-3-sonnet" assert config.model_name == "anthropic/claude-sonnet-4"
class TestOpenRouterFunctionality: class TestOpenRouterFunctionality:

View File

@@ -74,9 +74,9 @@ class TestOpenRouterModelRegistry:
# Test various aliases # Test various aliases
test_cases = [ test_cases = [
("opus", "anthropic/claude-3-opus"), ("opus", "anthropic/claude-opus-4"),
("OPUS", "anthropic/claude-3-opus"), # Case insensitive ("OPUS", "anthropic/claude-opus-4"), # Case insensitive
("claude", "anthropic/claude-3-sonnet"), ("claude", "anthropic/claude-sonnet-4"),
("o3", "openai/o3"), ("o3", "openai/o3"),
("deepseek", "deepseek/deepseek-r1-0528"), ("deepseek", "deepseek/deepseek-r1-0528"),
("mistral", "mistralai/mistral-large-2411"), ("mistral", "mistralai/mistral-large-2411"),
@@ -92,9 +92,9 @@ class TestOpenRouterModelRegistry:
registry = OpenRouterModelRegistry() registry = OpenRouterModelRegistry()
# Should be able to look up by full model name # Should be able to look up by full model name
config = registry.resolve("anthropic/claude-3-opus") config = registry.resolve("anthropic/claude-opus-4")
assert config is not None assert config is not None
assert config.model_name == "anthropic/claude-3-opus" assert config.model_name == "anthropic/claude-opus-4"
config = registry.resolve("openai/o3") config = registry.resolve("openai/o3")
assert config is not None assert config is not None
@@ -118,7 +118,7 @@ class TestOpenRouterModelRegistry:
caps = config.to_capabilities() caps = config.to_capabilities()
assert caps.provider == ProviderType.OPENROUTER assert caps.provider == ProviderType.OPENROUTER
assert caps.model_name == "anthropic/claude-3-opus" assert caps.model_name == "anthropic/claude-opus-4"
assert caps.friendly_name == "OpenRouter" assert caps.friendly_name == "OpenRouter"
assert caps.context_window == 200000 assert caps.context_window == 200000
assert not caps.supports_extended_thinking assert not caps.supports_extended_thinking

View File

@@ -288,11 +288,11 @@ class TestProviderHelperMethods:
with patch.object(ModelProviderRegistry, "get_provider") as mock_get_provider: with patch.object(ModelProviderRegistry, "get_provider") as mock_get_provider:
# Mock openrouter provider # Mock openrouter provider
mock_openrouter = MagicMock() mock_openrouter = MagicMock()
mock_openrouter.validate_model_name.side_effect = lambda m: m == "anthropic/claude-3.5-sonnet" mock_openrouter.validate_model_name.side_effect = lambda m: m == "anthropic/claude-sonnet-4"
mock_get_provider.side_effect = lambda ptype: mock_openrouter if ptype == ProviderType.OPENROUTER else None mock_get_provider.side_effect = lambda ptype: mock_openrouter if ptype == ProviderType.OPENROUTER else None
model = ModelProviderRegistry._find_extended_thinking_model() model = ModelProviderRegistry._find_extended_thinking_model()
assert model == "anthropic/claude-3.5-sonnet" assert model == "anthropic/claude-sonnet-4"
def test_find_extended_thinking_model_none_found(self): def test_find_extended_thinking_model_none_found(self):
"""Test when no thinking model is found.""" """Test when no thinking model is found."""

View File

@@ -318,7 +318,7 @@ class TestOpenRouterAliasRestrictions:
os.environ.pop("OPENAI_API_KEY", None) os.environ.pop("OPENAI_API_KEY", None)
os.environ.pop("XAI_API_KEY", None) os.environ.pop("XAI_API_KEY", None)
os.environ["OPENROUTER_API_KEY"] = "test-key" os.environ["OPENROUTER_API_KEY"] = "test-key"
os.environ["OPENROUTER_ALLOWED_MODELS"] = "o3-mini,anthropic/claude-3-opus,flash" os.environ["OPENROUTER_ALLOWED_MODELS"] = "o3-mini,anthropic/claude-opus-4,flash"
# Register OpenRouter provider # Register OpenRouter provider
from providers.openrouter import OpenRouterProvider from providers.openrouter import OpenRouterProvider
@@ -330,7 +330,7 @@ class TestOpenRouterAliasRestrictions:
expected_models = { expected_models = {
"openai/o3-mini", # from alias "openai/o3-mini", # from alias
"anthropic/claude-3-opus", # full name "anthropic/claude-opus-4", # full name
"google/gemini-2.5-flash", # from alias "google/gemini-2.5-flash", # from alias
} }

View File

@@ -448,7 +448,7 @@ class BaseTool(ABC):
except Exception: except Exception:
description += ( description += (
" OpenRouter: Any model available on openrouter.ai " " OpenRouter: Any model available on openrouter.ai "
"(e.g., 'gpt-4', 'claude-3-opus', 'mistral-large')." "(e.g., 'gpt-4', 'claude-4-opus', 'mistral-large')."
) )
description += f" Defaults to '{DEFAULT_MODEL}' if not specified." description += f" Defaults to '{DEFAULT_MODEL}' if not specified."