fix: Bedrock API key region fallback (#25171)

## Why

Users following the Amazon Bedrock API-key setup can export
`AWS_BEARER_TOKEN_BEDROCK` and `AWS_REGION`, but Codex's bearer-token
auth path only accepted `model_providers.amazon-bedrock.aws.region`.
That made the documented env-based setup fail with a missing-region
error even though the standard AWS region environment variable was
present.

## What Changed

- Updates Bedrock bearer-token region resolution to use
`model_providers.amazon-bedrock.aws.region` first, then fall back to
`AWS_REGION`, then `AWS_DEFAULT_REGION`.
- Updates the missing-region error to list all supported region sources.
- Adds focused coverage for config precedence, `AWS_REGION`,
`AWS_DEFAULT_REGION`, and the missing-region failure.
This commit is contained in:
Celia Chen authored and GitHub committed 2026-05-30 01:17:38 +00:00
1 parent e929bb5c88
commit 00ca857d3f
1 file changed
+82 -24
@@ -20,6 +20,8 @@ use super::mantle::aws_auth_config;
use super::mantle::region_from_config; use super::mantle::region_from_config;
const AWS_BEARER_TOKEN_BEDROCK_ENV_VAR: &str = "AWS_BEARER_TOKEN_BEDROCK"; const AWS_BEARER_TOKEN_BEDROCK_ENV_VAR: &str = "AWS_BEARER_TOKEN_BEDROCK";
const AWS_REGION_ENV_VAR: &str = "AWS_REGION";
const AWS_DEFAULT_REGION_ENV_VAR: &str = "AWS_DEFAULT_REGION";
pub(super) enum BedrockAuthMethod { pub(super) enum BedrockAuthMethod {
EnvBearerToken { token: String, region: String }, EnvBearerToken { token: String, region: String },
@@ -29,8 +31,8 @@ pub(super) enum BedrockAuthMethod {
pub(super) async fn resolve_auth_method( pub(super) async fn resolve_auth_method(
aws: &ModelProviderAwsAuthInfo, aws: &ModelProviderAwsAuthInfo,
) -> Result<BedrockAuthMethod> { ) -> Result<BedrockAuthMethod> {
if let Some(token) = bearer_token_from_env() { if let Some(token) = non_empty_env_var_from(AWS_BEARER_TOKEN_BEDROCK_ENV_VAR, std::env::var) {
let region = bearer_token_region_from_config(aws)?; let region = bearer_token_region(aws, std::env::var)?;
return Ok(BedrockAuthMethod::EnvBearerToken { token, region }); return Ok(BedrockAuthMethod::EnvBearerToken { token, region });
} }
@@ -56,21 +58,30 @@ pub(super) async fn resolve_provider_auth(
} }
} }
fn bearer_token_from_env() -> Option<String> { fn non_empty_env_var_from(
std::env::var(AWS_BEARER_TOKEN_BEDROCK_ENV_VAR) name: &'static str,
env_var: impl Fn(&'static str) -> std::result::Result<String, std::env::VarError>,
) -> Option<String> {
env_var(name)
.ok() .ok()
.map(|token| token.trim().to_string()) .map(|value| value.trim().to_string())
.filter(|token| !token.is_empty()) .filter(|value| !value.is_empty())
} }
fn bearer_token_region_from_config(aws: &ModelProviderAwsAuthInfo) -> Result<String> { fn bearer_token_region(
region_from_config(aws).ok_or_else(|| { aws: &ModelProviderAwsAuthInfo,
CodexErr::Fatal( env_var: impl Fn(&'static str) -> std::result::Result<String, std::env::VarError> + Copy,
"Amazon Bedrock bearer token auth requires \ ) -> Result<String> {
`model_providers.amazon-bedrock.aws.region`" region_from_config(aws)
.to_string(), .or_else(|| non_empty_env_var_from(AWS_REGION_ENV_VAR, env_var))
) .or_else(|| non_empty_env_var_from(AWS_DEFAULT_REGION_ENV_VAR, env_var))
}) .ok_or_else(|| {
CodexErr::Fatal(
"Amazon Bedrock bearer token auth requires \
`model_providers.amazon-bedrock.aws.region`, `AWS_REGION`, or `AWS_DEFAULT_REGION`"
.to_string(),
)
})
} }
fn aws_auth_error_to_codex_error(error: AwsAuthError) -> CodexErr { fn aws_auth_error_to_codex_error(error: AwsAuthError) -> CodexErr {
@@ -147,13 +158,23 @@ mod tests {
use super::*; use super::*;
fn missing_env_var(_: &'static str) -> std::result::Result<String, std::env::VarError> {
Err(std::env::VarError::NotPresent)
}
#[test] #[test]
fn bedrock_bearer_auth_uses_configured_region_and_header() { fn bedrock_bearer_auth_prefers_configured_region_and_uses_header() {
let token = "bedrock-api-key-test".to_string(); let token = "bedrock-api-key-test".to_string();
let region = bearer_token_region_from_config(&ModelProviderAwsAuthInfo { let region = bearer_token_region(
profile: None, &ModelProviderAwsAuthInfo {
region: Some(" us-west-2 ".to_string()), profile: None,
}) region: Some(" us-west-2 ".to_string()),
},
|name| match name {
AWS_REGION_ENV_VAR => Ok("eu-west-1".to_string()),
_ => Err(std::env::VarError::NotPresent),
},
)
.expect("configured region should resolve"); .expect("configured region should resolve");
let provider = BearerAuthProvider { let provider = BearerAuthProvider {
token: Some(token), token: Some(token),
@@ -173,18 +194,55 @@ mod tests {
); );
} }
#[test]
fn bedrock_bearer_auth_uses_aws_region_env() {
let region = bearer_token_region(
&ModelProviderAwsAuthInfo {
profile: None,
region: None,
},
|name| match name {
AWS_REGION_ENV_VAR => Ok(" eu-central-1 ".to_string()),
_ => Err(std::env::VarError::NotPresent),
},
)
.expect("AWS_REGION should resolve");
assert_eq!(region, "eu-central-1");
}
#[test]
fn bedrock_bearer_auth_uses_aws_default_region_env() {
let region = bearer_token_region(
&ModelProviderAwsAuthInfo {
profile: None,
region: None,
},
|name| match name {
AWS_DEFAULT_REGION_ENV_VAR => Ok("ap-northeast-1".to_string()),
_ => Err(std::env::VarError::NotPresent),
},
)
.expect("AWS_DEFAULT_REGION should resolve");
assert_eq!(region, "ap-northeast-1");
}
#[test] #[test]
fn bedrock_bearer_auth_rejects_missing_configured_region() { fn bedrock_bearer_auth_rejects_missing_configured_region() {
let err = bearer_token_region_from_config(&ModelProviderAwsAuthInfo { let err = bearer_token_region(
profile: None, &ModelProviderAwsAuthInfo {
region: None, profile: None,
}) region: None,
},
missing_env_var,
)
.expect_err("missing region should fail"); .expect_err("missing region should fail");
assert_eq!( assert_eq!(
err.to_string(), err.to_string(),
"Fatal error: Amazon Bedrock bearer token auth requires \ "Fatal error: Amazon Bedrock bearer token auth requires \
`model_providers.amazon-bedrock.aws.region`" `model_providers.amazon-bedrock.aws.region`, `AWS_REGION`, or `AWS_DEFAULT_REGION`"
); );
} }