mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
## Summary Add first-class Amazon Bedrock Mantle provider support so Codex can keep using its existing Responses API transport with OpenAI-compatible AWS-hosted endpoints such as AOA/Mantle. This is needed for the AWS launch path, where provider traffic should authenticate with AWS credentials instead of OpenAI bearer credentials. Requests are authenticated immediately before transport send, so SigV4 signs the final method, URL, headers, and body bytes that `reqwest` will send. ## What Changed - Added a new `codex-aws-auth` crate for loading AWS SDK config, resolving credentials, and signing finalized HTTP requests with AWS SigV4. - Added a built-in `amazon-bedrock` provider that targets Bedrock Mantle Responses endpoints, defaults to `us-east-1`, supports region/profile overrides, disables WebSockets, and does not require OpenAI auth. - Added Amazon Bedrock auth resolution in `codex-model-provider`: prefer `AWS_BEARER_TOKEN_BEDROCK` when set, otherwise use AWS SDK credentials and SigV4 signing. - Added `AuthProvider::apply_auth` and `Request::prepare_body_for_send` so request-signing providers can sign the exact outbound request after JSON serialization/compression. - Determine the region by taking the `aws.region` config first (required for bearer token codepath), and fallback to SDK default region. ## Testing Amazon Bedrock Mantle Responses paths: - Built the local Codex binary with `cargo build`. - Verified the custom proxy-backed `aws` provider using `env_key = "AWS_BEARER_TOKEN_BEDROCK"` streamed raw `responses` output with `response.output_text.delta`, `response.completed`, and `mantle-env-ok`. - Verified a full `codex exec --profile aws` turn returned `mantle-env-ok`. - Confirmed the custom provider used the bearer env var, not AWS profile auth: bogus `AWS_PROFILE` still passed, empty env var failed locally, and malformed env var reached Mantle and failed with `401 invalid_api_key`. - Verified built-in `amazon-bedrock` with `AWS_BEARER_TOKEN_BEDROCK` set passed despite bogus AWS profiles, returning `amazon-bedrock-env-ok`. - Verified built-in `amazon-bedrock` SDK/SigV4 auth passed with `AWS_BEARER_TOKEN_BEDROCK` unset and temporary AWS session env credentials, returning `amazon-bedrock-sdk-env-ok`.
159 lines
5.3 KiB
Rust
159 lines
5.3 KiB
Rust
use std::fmt;
|
|
use std::sync::Arc;
|
|
|
|
use codex_api::Provider;
|
|
use codex_api::SharedAuthProvider;
|
|
use codex_login::AuthManager;
|
|
use codex_login::CodexAuth;
|
|
use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
|
use codex_model_provider_info::ModelProviderInfo;
|
|
|
|
use crate::amazon_bedrock::AmazonBedrockModelProvider;
|
|
use crate::auth::auth_manager_for_provider;
|
|
use crate::auth::resolve_provider_auth;
|
|
|
|
/// Runtime provider abstraction used by model execution.
|
|
///
|
|
/// Implementations own provider-specific behavior for a model backend. The
|
|
/// `ModelProviderInfo` returned by `info` is the serialized/configured provider
|
|
/// metadata used by the default OpenAI-compatible implementation.
|
|
#[async_trait::async_trait]
|
|
pub trait ModelProvider: fmt::Debug + Send + Sync {
|
|
/// Returns the configured provider metadata.
|
|
fn info(&self) -> &ModelProviderInfo;
|
|
|
|
/// Returns the provider-scoped auth manager, when this provider uses one.
|
|
///
|
|
/// TODO(celia-oai): Make auth manager access internal to this crate so callers
|
|
/// resolve provider-specific auth only through `ModelProvider`. We first need
|
|
/// to think through whether Codex should have a unified provider-specific auth
|
|
/// manager throughout the codebase; that is a larger refactor than this change.
|
|
fn auth_manager(&self) -> Option<Arc<AuthManager>>;
|
|
|
|
/// Returns the current provider-scoped auth value, if one is configured.
|
|
async fn auth(&self) -> Option<CodexAuth>;
|
|
|
|
/// Returns provider configuration adapted for the API client.
|
|
async fn api_provider(&self) -> codex_protocol::error::Result<Provider> {
|
|
let auth = self.auth().await;
|
|
self.info()
|
|
.to_api_provider(auth.as_ref().map(CodexAuth::auth_mode))
|
|
}
|
|
|
|
/// Returns the auth provider used to attach request credentials.
|
|
async fn api_auth(&self) -> codex_protocol::error::Result<SharedAuthProvider> {
|
|
let auth = self.auth().await;
|
|
resolve_provider_auth(auth.as_ref(), self.info())
|
|
}
|
|
}
|
|
|
|
/// Shared runtime model provider handle.
|
|
pub type SharedModelProvider = Arc<dyn ModelProvider>;
|
|
|
|
/// Creates the default runtime model provider for configured provider metadata.
|
|
pub fn create_model_provider(
|
|
provider_info: ModelProviderInfo,
|
|
auth_manager: Option<Arc<AuthManager>>,
|
|
) -> SharedModelProvider {
|
|
if provider_info.is_amazon_bedrock() {
|
|
let aws = provider_info
|
|
.aws
|
|
.clone()
|
|
.unwrap_or(ModelProviderAwsAuthInfo {
|
|
profile: None,
|
|
region: None,
|
|
});
|
|
return Arc::new(AmazonBedrockModelProvider {
|
|
info: provider_info,
|
|
aws,
|
|
});
|
|
}
|
|
|
|
let auth_manager = auth_manager_for_provider(auth_manager, &provider_info);
|
|
Arc::new(ConfiguredModelProvider {
|
|
info: provider_info,
|
|
auth_manager,
|
|
})
|
|
}
|
|
|
|
/// Runtime model provider backed by configured `ModelProviderInfo`.
|
|
#[derive(Clone, Debug)]
|
|
struct ConfiguredModelProvider {
|
|
info: ModelProviderInfo,
|
|
auth_manager: Option<Arc<AuthManager>>,
|
|
}
|
|
|
|
#[async_trait::async_trait]
|
|
impl ModelProvider for ConfiguredModelProvider {
|
|
fn info(&self) -> &ModelProviderInfo {
|
|
&self.info
|
|
}
|
|
|
|
fn auth_manager(&self) -> Option<Arc<AuthManager>> {
|
|
self.auth_manager.clone()
|
|
}
|
|
|
|
async fn auth(&self) -> Option<CodexAuth> {
|
|
match self.auth_manager.as_ref() {
|
|
Some(auth_manager) => auth_manager.auth().await,
|
|
None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::num::NonZeroU64;
|
|
|
|
use codex_model_provider_info::ModelProviderAwsAuthInfo;
|
|
use codex_protocol::config_types::ModelProviderAuthInfo;
|
|
|
|
use super::*;
|
|
|
|
fn provider_info_with_command_auth() -> ModelProviderInfo {
|
|
ModelProviderInfo {
|
|
auth: Some(ModelProviderAuthInfo {
|
|
command: "print-token".to_string(),
|
|
args: Vec::new(),
|
|
timeout_ms: NonZeroU64::new(5_000).expect("timeout should be non-zero"),
|
|
refresh_interval_ms: 300_000,
|
|
cwd: std::env::current_dir()
|
|
.expect("current dir should be available")
|
|
.try_into()
|
|
.expect("current dir should be absolute"),
|
|
}),
|
|
requires_openai_auth: false,
|
|
..ModelProviderInfo::create_openai_provider(/*base_url*/ None)
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn create_model_provider_builds_command_auth_manager_without_base_manager() {
|
|
let provider = create_model_provider(
|
|
provider_info_with_command_auth(),
|
|
/*auth_manager*/ None,
|
|
);
|
|
|
|
let auth_manager = provider
|
|
.auth_manager()
|
|
.expect("command auth provider should have an auth manager");
|
|
|
|
assert!(auth_manager.has_external_auth());
|
|
}
|
|
|
|
#[test]
|
|
fn create_model_provider_does_not_use_openai_auth_manager_for_amazon_bedrock_provider() {
|
|
let provider = create_model_provider(
|
|
ModelProviderInfo::create_amazon_bedrock_provider(Some(ModelProviderAwsAuthInfo {
|
|
profile: Some("codex-bedrock".to_string()),
|
|
region: None,
|
|
})),
|
|
Some(AuthManager::from_auth_for_testing(CodexAuth::from_api_key(
|
|
"openai-api-key",
|
|
))),
|
|
);
|
|
|
|
assert!(provider.auth_manager().is_none());
|
|
}
|
|
}
|