diff --git a/agent/dashboard/options.py b/agent/dashboard/options.py index 48e7932a..7a150466 100644 --- a/agent/dashboard/options.py +++ b/agent/dashboard/options.py @@ -99,9 +99,18 @@ def _provider_of(model_id: str) -> str | None: def _claude_family_of(model_id: str) -> str | None: provider, _, name = model_id.partition(":") - if provider != "anthropic" or not name.startswith("claude-"): + if provider == "anthropic": + claude = name + elif provider == "bedrock_converse": + # Bedrock ids embed the model as a region-prefixed path, e.g. + # "us.anthropic.claude-sonnet-5" — take the trailing "claude-*" segment so + # our Bedrock Claude models get the same family-aware fallback. + claude = name.rpartition(".")[2] + else: return None - parts = name.split("-") + if not claude.startswith("claude-"): + return None + parts = claude.split("-") if len(parts) < 2: return None return "-".join(parts[:2]) diff --git a/tests/test_model_fallback_resolution.py b/tests/test_model_fallback_resolution.py index c4fee9f3..dc11f9df 100644 --- a/tests/test_model_fallback_resolution.py +++ b/tests/test_model_fallback_resolution.py @@ -12,12 +12,21 @@ from agent.dashboard.team_settings import get_team_default_model STALE_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-7" SUPPORTED_ANTHROPIC = "bedrock_converse:us.anthropic.claude-opus-4-8" +STALE_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-4-9" +SUPPORTED_SONNET = "bedrock_converse:us.anthropic.claude-sonnet-5" def test_provider_fallback_preserves_provider_and_effort() -> None: assert provider_fallback_pair(STALE_ANTHROPIC, "xhigh") == (SUPPORTED_ANTHROPIC, "xhigh") +def test_provider_fallback_keeps_bedrock_sonnet_in_family() -> None: + # A dropped Bedrock Sonnet must prefer the current Bedrock Sonnet, not cross to + # the Bedrock Opus that happens to sit first in the provider's list. Requires + # _claude_family_of to understand bedrock_converse ids, not just anthropic:. + assert provider_fallback_pair(STALE_SONNET, "high") == (SUPPORTED_SONNET, "high") + + def test_provider_fallback_uses_default_effort_when_unsupported() -> None: assert provider_fallback_pair(STALE_ANTHROPIC, "bogus") == (SUPPORTED_ANTHROPIC, "high") assert provider_fallback_pair(STALE_ANTHROPIC, None) == (SUPPORTED_ANTHROPIC, "high")