diff --git a/agent/anthropic_adapter.py b/agent/anthropic_adapter.py index bf2b8a62c5c51..915bef381199b 100644 --- a/agent/anthropic_adapter.py +++ b/agent/anthropic_adapter.py @@ -831,6 +831,22 @@ def read_hermes_oauth_credentials() -> Optional[Dict[str, Any]]: # --------------------------------------------------------------------------- +def _is_bedrock_model_id(model: str) -> bool: + """Return True if the model string is a Bedrock-native model identifier. + + Bedrock IDs use dots as structural separators (e.g. + ``global.anthropic.claude-opus-4-6-v1``, ``anthropic.claude-sonnet-4-6``). + These must NOT have their dots replaced with hyphens. + """ + lower = model.lower() + for prefix in ("global.", "us.", "eu.", "ap.", "apac.", "jp."): + if lower.startswith(prefix): + return True + if lower.startswith("anthropic.claude"): + return True + return False + + def normalize_model_name(model: str, preserve_dots: bool = False) -> str: """Normalize a model name for the Anthropic API. @@ -838,13 +854,15 @@ def normalize_model_name(model: str, preserve_dots: bool = False) -> str: - Converts dots to hyphens in version numbers (OpenRouter uses dots, Anthropic uses hyphens: claude-opus-4.6 → claude-opus-4-6), unless preserve_dots is True (e.g. for Alibaba/DashScope: qwen3.5-plus). + - Preserves dots in Bedrock model IDs where dots are structural + separators (e.g. global.anthropic.claude-opus-4-6-v1). """ lower = model.lower() if lower.startswith("anthropic/"): model = model[len("anthropic/"):] + if _is_bedrock_model_id(model): + return model if not preserve_dots: - # OpenRouter uses dots for version separators (claude-opus-4.6), - # Anthropic uses hyphens (claude-opus-4-6). Convert dots to hyphens. model = model.replace(".", "-") return model diff --git a/tests/agent/test_anthropic_adapter.py b/tests/agent/test_anthropic_adapter.py index 737db01a352d7..2038b7e90e0be 100644 --- a/tests/agent/test_anthropic_adapter.py +++ b/tests/agent/test_anthropic_adapter.py @@ -19,6 +19,7 @@ convert_tools_to_anthropic, is_claude_code_token_valid, normalize_anthropic_response, + _is_bedrock_model_id, normalize_model_name, read_claude_code_credentials, resolve_anthropic_token, @@ -481,6 +482,42 @@ def test_preserve_dots_for_alibaba_dashscope(self): assert normalize_model_name("anthropic/qwen3.5-plus", preserve_dots=True) == "qwen3.5-plus" assert normalize_model_name("qwen3.5-flash", preserve_dots=True) == "qwen3.5-flash" + def test_preserves_dots_in_bedrock_model_ids(self): + """Bedrock model IDs use dots as structural separators, not version delimiters.""" + assert normalize_model_name("global.anthropic.claude-opus-4-6-v1") == "global.anthropic.claude-opus-4-6-v1" + assert normalize_model_name("global.anthropic.claude-opus-4-7") == "global.anthropic.claude-opus-4-7" + assert normalize_model_name("global.anthropic.claude-sonnet-4-6") == "global.anthropic.claude-sonnet-4-6" + assert normalize_model_name("apac.anthropic.claude-sonnet-4-20250514-v1:0") == "apac.anthropic.claude-sonnet-4-20250514-v1:0" + assert normalize_model_name("us.anthropic.claude-opus-4-6-v1") == "us.anthropic.claude-opus-4-6-v1" + assert normalize_model_name("eu.anthropic.claude-sonnet-4-6") == "eu.anthropic.claude-sonnet-4-6" + assert normalize_model_name("anthropic.claude-opus-4-6-v1") == "anthropic.claude-opus-4-6-v1" + + +class TestIsBedrockModelId: + def test_global_prefix(self): + assert _is_bedrock_model_id("global.anthropic.claude-opus-4-6-v1") is True + + def test_regional_prefixes(self): + assert _is_bedrock_model_id("us.anthropic.claude-opus-4-6-v1") is True + assert _is_bedrock_model_id("eu.anthropic.claude-sonnet-4-6") is True + assert _is_bedrock_model_id("ap.anthropic.claude-sonnet-4-6") is True + assert _is_bedrock_model_id("apac.anthropic.claude-sonnet-4-20250514-v1:0") is True + assert _is_bedrock_model_id("jp.anthropic.claude-opus-4-7") is True + + def test_foundation_model_prefix(self): + assert _is_bedrock_model_id("anthropic.claude-opus-4-6-v1") is True + assert _is_bedrock_model_id("anthropic.claude-sonnet-4-6") is True + + def test_non_bedrock_ids(self): + assert _is_bedrock_model_id("claude-opus-4-6") is False + assert _is_bedrock_model_id("claude-opus-4.6") is False + assert _is_bedrock_model_id("anthropic/claude-opus-4.6") is False + assert _is_bedrock_model_id("qwen3.5-plus") is False + + def test_case_insensitive(self): + assert _is_bedrock_model_id("Global.Anthropic.Claude-Opus-4-6-v1") is True + assert _is_bedrock_model_id("GLOBAL.ANTHROPIC.CLAUDE-OPUS-4-6-V1") is True + # --------------------------------------------------------------------------- # Tool conversion