fix(bedrock): inference profile prefix resolution

Christian Rocha created

Resolve Bedrock model IDs using AWS region-group prefixes with
AWS_DEFAULT_REGION fallback, preserve already-prefixed IDs, and default
to global for unmapped regions. Add tests to lock in mapping and
prefixing behavior.

💘 Generated with Crush

Assisted-by: GPT-5.3 Codex via Crush <crush@charm.land>

Change summary

providers/anthropic/anthropic_test.go | 30 +++++++++++++++++++++
providers/anthropic/bedrock.go        | 40 +++++++++++++++++++++++-----
2 files changed, 62 insertions(+), 8 deletions(-)

Detailed changes

providers/anthropic/anthropic_test.go 🔗

@@ -1838,6 +1838,36 @@ func TestGenerate_BetaAPI(t *testing.T) {
 	})
 }
 
+func TestBedrockRegionPrefixing(t *testing.T) {
+	t.Run("uses us prefix when region is unset", func(t *testing.T) {
+		t.Setenv("AWS_REGION", "")
+		t.Setenv("AWS_DEFAULT_REGION", "")
+		require.Equal(t, "us.anthropic.claude-sonnet-4-5-20250929-v1:0", bedrockPrefixModelWithRegion("anthropic.claude-sonnet-4-5-20250929-v1:0"))
+	})
+
+	t.Run("uses AWS_DEFAULT_REGION when AWS_REGION is unset", func(t *testing.T) {
+		t.Setenv("AWS_REGION", "")
+		t.Setenv("AWS_DEFAULT_REGION", "eu-west-1")
+		require.Equal(t, "eu.anthropic.claude-sonnet-4-5-20250929-v1:0", bedrockPrefixModelWithRegion("anthropic.claude-sonnet-4-5-20250929-v1:0"))
+	})
+
+	t.Run("keeps already-prefixed model IDs", func(t *testing.T) {
+		t.Setenv("AWS_REGION", "us-east-1")
+		t.Setenv("AWS_DEFAULT_REGION", "")
+		require.Equal(t, "global.anthropic.claude-sonnet-4-5-20250929-v1:0", bedrockPrefixModelWithRegion("global.anthropic.claude-sonnet-4-5-20250929-v1:0"))
+	})
+
+	t.Run("maps known regions to profile prefixes", func(t *testing.T) {
+		require.Equal(t, "us", bedrockRegionPrefix("us-west-2"))
+		require.Equal(t, "us", bedrockRegionPrefix("ca-central-1"))
+		require.Equal(t, "eu", bedrockRegionPrefix("eu-central-1"))
+		require.Equal(t, "jp", bedrockRegionPrefix("ap-northeast-1"))
+		require.Equal(t, "au", bedrockRegionPrefix("ap-southeast-2"))
+		require.Equal(t, "global", bedrockRegionPrefix("ap-south-1"))
+		require.Equal(t, "global", bedrockRegionPrefix("sa-east-1"))
+	})
+}
+
 func TestStream_BetaAPI(t *testing.T) {
 	t.Parallel()
 

providers/anthropic/bedrock.go 🔗

@@ -11,19 +11,43 @@ import (
 
 func bedrockBasicAuthConfig(apiKey string) aws.Config {
 	return aws.Config{
-		Region:                  cmp.Or(os.Getenv("AWS_REGION"), "us-east-1"),
+		Region:                  cmp.Or(awsRegion(), "us-east-1"),
 		BearerAuthTokenProvider: bearer.StaticTokenProvider{Token: bearer.Token{Value: apiKey}},
 	}
 }
 
 func bedrockPrefixModelWithRegion(modelID string) string {
-	region := os.Getenv("AWS_REGION")
-	if len(region) < 2 {
-		region = "us-east-1"
-	}
-	prefix := region[:2] + "."
-	if strings.HasPrefix(modelID, prefix) {
+	if hasBedrockInferenceProfilePrefix(modelID) {
 		return modelID
 	}
-	return prefix + modelID
+	region := cmp.Or(awsRegion(), "us-east-1")
+	return bedrockRegionPrefix(region) + "." + modelID
+}
+
+func awsRegion() string {
+	return cmp.Or(os.Getenv("AWS_REGION"), os.Getenv("AWS_DEFAULT_REGION"))
+}
+
+func bedrockRegionPrefix(region string) string {
+	switch {
+	case strings.HasPrefix(region, "us-") || region == "ca-central-1":
+		return "us"
+	case strings.HasPrefix(region, "eu-"):
+		return "eu"
+	case region == "ap-northeast-1":
+		return "jp"
+	case region == "ap-southeast-2":
+		return "au"
+	default:
+		return "global"
+	}
+}
+
+func hasBedrockInferenceProfilePrefix(modelID string) bool {
+	for _, prefix := range []string{"us.", "eu.", "jp.", "au.", "global."} {
+		if strings.HasPrefix(modelID, prefix) {
+			return true
+		}
+	}
+	return false
 }