mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
Support custom transports for Amazon Bedrock (#33695)
## What changed - Allow the built-in `amazon-bedrock` provider to override `base_url`, `auth`, and `http_headers` in addition to its AWS profile and region. - Use command-based bearer authentication and configured endpoints without applying AWS request signing, while retaining regional endpoint resolution for the default Bedrock configuration. - Replace the Bedrock account `credentialSource` enum with the `usesCodexManagedCredentials` boolean so command-authenticated and other externally managed configurations are reported consistently. ## Testing - Cover configuration merging and validation, command-authenticated proxy requests with custom headers, and account reporting for managed and external credentials. GitOrigin-RevId: d1acbe602060470583b5e12f8d304bee5be46f4c
This commit is contained in:
1 parent
18110b810f
commit
315195492c
20 files changed
+362
-148
No files matched your search
@@ -80,7 +80,7 @@ fn non_empty_env_var_from(
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn bearer_token_region(
|
||||
pub(super) fn bearer_token_region(
|
||||
aws: &ModelProviderAwsAuthInfo,
|
||||
env_var: impl Fn(&'static str) -> std::result::Result<String, std::env::VarError> + Copy,
|
||||
) -> Result<String> {
|
||||
|
||||
@@ -54,7 +54,7 @@ pub(super) fn base_url(region: &str) -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn runtime_base_url(
|
||||
pub(super) async fn bedrock_mantle_runtime_base_url(
|
||||
managed_auth: Option<&BedrockApiKeyAuth>,
|
||||
aws: &ModelProviderAwsAuthInfo,
|
||||
) -> Result<String> {
|
||||
|
||||
@@ -17,22 +17,23 @@ use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
||||
use codex_model_provider_info::ModelProviderInfo;
|
||||
use codex_models_manager::manager::SharedModelsManager;
|
||||
use codex_models_manager::manager::StaticModelsManager;
|
||||
use codex_protocol::account::AmazonBedrockCredentialSource;
|
||||
use codex_protocol::account::ProviderAccount;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::Result;
|
||||
use codex_protocol::openai_models::ModelsResponse;
|
||||
|
||||
use crate::auth::auth_manager_for_provider;
|
||||
use crate::auth::resolve_provider_auth as resolve_configured_provider_auth;
|
||||
use crate::provider::ModelProvider;
|
||||
use crate::provider::ModelProviderFuture;
|
||||
use crate::provider::ProviderAccountResult;
|
||||
use crate::provider::ProviderAccountState;
|
||||
use crate::provider::ProviderCapabilities;
|
||||
use auth::resolve_provider_auth;
|
||||
use auth::resolve_provider_auth as resolve_bedrock_provider_auth;
|
||||
pub(crate) use catalog::static_model_catalog;
|
||||
use catalog::with_default_only_service_tier;
|
||||
use mantle::bedrock_mantle_runtime_base_url;
|
||||
pub use mantle::is_supported_amazon_bedrock_region;
|
||||
use mantle::runtime_base_url;
|
||||
|
||||
/// Runtime provider for Amazon Bedrock's OpenAI-compatible Mantle endpoint.
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -47,6 +48,7 @@ impl AmazonBedrockModelProvider {
|
||||
provider_info: ModelProviderInfo,
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
) -> Self {
|
||||
let auth_manager = auth_manager_for_provider(auth_manager, &provider_info);
|
||||
let aws = provider_info
|
||||
.aws
|
||||
.clone()
|
||||
@@ -77,27 +79,39 @@ impl AmazonBedrockModelProvider {
|
||||
}
|
||||
|
||||
async fn auth(&self) -> Option<CodexAuth> {
|
||||
self.managed_auth().map(CodexAuth::BedrockApiKey)
|
||||
if self.info.has_command_auth() {
|
||||
match self.auth_manager.as_ref() {
|
||||
Some(auth_manager) => auth_manager.auth().await,
|
||||
None => None,
|
||||
}
|
||||
} else {
|
||||
self.managed_auth().map(CodexAuth::BedrockApiKey)
|
||||
}
|
||||
}
|
||||
|
||||
async fn api_provider(&self) -> Result<Provider> {
|
||||
let managed_auth = self.managed_auth();
|
||||
let mut api_provider_info = self.info.clone();
|
||||
api_provider_info.base_url =
|
||||
Some(runtime_base_url(managed_auth.as_ref(), &self.aws).await?);
|
||||
api_provider_info.base_url = self.runtime_base_url().await?;
|
||||
api_provider_info.to_api_provider(/*auth_mode*/ None)
|
||||
}
|
||||
|
||||
async fn runtime_base_url(&self) -> Result<Option<String>> {
|
||||
if let Some(base_url) = self.info.base_url.clone() {
|
||||
return Ok(Some(base_url));
|
||||
}
|
||||
let managed_auth = self.managed_auth();
|
||||
Ok(Some(
|
||||
runtime_base_url(managed_auth.as_ref(), &self.aws).await?,
|
||||
bedrock_mantle_runtime_base_url(managed_auth.as_ref(), &self.aws).await?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn api_auth(&self) -> Result<SharedAuthProvider> {
|
||||
if self.info.has_command_auth() {
|
||||
let auth = self.auth().await;
|
||||
return resolve_configured_provider_auth(auth.as_ref(), &self.info);
|
||||
}
|
||||
let managed_auth = self.managed_auth();
|
||||
resolve_provider_auth(managed_auth.as_ref(), &self.aws).await
|
||||
resolve_bedrock_provider_auth(managed_auth.as_ref(), &self.aws).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,8 +141,11 @@ impl ModelProvider for AmazonBedrockModelProvider {
|
||||
}
|
||||
|
||||
fn auth_manager(&self) -> Option<Arc<AuthManager>> {
|
||||
self.managed_auth()
|
||||
.and_then(|_| self.auth_manager.as_ref().cloned())
|
||||
if self.info.has_command_auth() || self.managed_auth().is_some() {
|
||||
self.auth_manager.clone()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn auth(&self) -> ModelProviderFuture<'_, Option<CodexAuth>> {
|
||||
@@ -136,13 +153,10 @@ impl ModelProvider for AmazonBedrockModelProvider {
|
||||
}
|
||||
|
||||
fn account_state(&self) -> ProviderAccountResult {
|
||||
let credential_source = if self.managed_auth().is_some() {
|
||||
AmazonBedrockCredentialSource::CodexManaged
|
||||
} else {
|
||||
AmazonBedrockCredentialSource::AwsManaged
|
||||
};
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock { credential_source }),
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
uses_codex_managed_credentials: self.managed_auth().is_some(),
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
}
|
||||
@@ -191,11 +205,30 @@ mod error_tests;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::num::NonZeroU64;
|
||||
|
||||
use codex_protocol::config_types::ModelProviderAuthInfo;
|
||||
use http::HeaderValue;
|
||||
use pretty_assertions::assert_eq;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn command_auth_provider(base_url: Option<&str>) -> ModelProviderInfo {
|
||||
let mut provider = ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None);
|
||||
provider.base_url = base_url.map(str::to_string);
|
||||
provider.auth = Some(ModelProviderAuthInfo {
|
||||
command: "token-fetcher".to_string(),
|
||||
args: vec!["fetch".to_string()],
|
||||
timeout_ms: NonZeroU64::new(5_000).expect("timeout should be non-zero"),
|
||||
refresh_interval_ms: 300_000,
|
||||
cwd: std::env::current_dir()
|
||||
.expect("current directory should be available")
|
||||
.try_into()
|
||||
.expect("current directory should be absolute"),
|
||||
});
|
||||
provider
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn api_provider_for_bedrock_bearer_token_uses_configured_region_endpoint() {
|
||||
let region = "eu-central-1";
|
||||
@@ -212,6 +245,39 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn command_auth_uses_configured_base_url_without_resolving_aws() {
|
||||
let mut provider_info = command_auth_provider(Some("https://proxy.example.com/v1"));
|
||||
provider_info.aws = Some(ModelProviderAwsAuthInfo {
|
||||
profile: Some("aws-profile-that-should-not-be-loaded".to_string()),
|
||||
region: Some("us-west-2".to_string()),
|
||||
});
|
||||
let provider = AmazonBedrockModelProvider::new(provider_info, /*auth_manager*/ None);
|
||||
|
||||
assert_eq!(
|
||||
provider
|
||||
.runtime_base_url()
|
||||
.await
|
||||
.expect("configured base URL should resolve"),
|
||||
Some("https://proxy.example.com/v1".to_string())
|
||||
);
|
||||
assert!(
|
||||
provider
|
||||
.auth_manager()
|
||||
.expect("command auth manager should be exposed")
|
||||
.has_external_auth()
|
||||
);
|
||||
assert_eq!(
|
||||
provider.account_state(),
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
uses_codex_managed_credentials: false,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn managed_auth_takes_precedence_over_aws_auth() {
|
||||
let managed_auth = BedrockApiKeyAuth {
|
||||
@@ -242,7 +308,7 @@ mod tests {
|
||||
provider.account_state(),
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::CodexManaged,
|
||||
uses_codex_managed_credentials: true,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
@@ -280,7 +346,7 @@ mod tests {
|
||||
provider.account_state(),
|
||||
Ok(ProviderAccountState {
|
||||
account: Some(ProviderAccount::AmazonBedrock {
|
||||
credential_source: AmazonBedrockCredentialSource::AwsManaged,
|
||||
uses_codex_managed_credentials: false,
|
||||
}),
|
||||
requires_openai_auth: false,
|
||||
})
|
||||
|
||||
Reference in new issue
Block a user