diff --git a/codex-rs/app-server/src/request_processors/account_processor.rs b/codex-rs/app-server/src/request_processors/account_processor.rs index 9592563bece0..eccf73903536 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -177,13 +177,14 @@ impl AccountRequestProcessor { .set_auth_mode(self.auth_manager.get_api_auth_mode()); } - fn current_account_updated_notification(&self) -> AccountUpdatedNotification { - let auth = self.auth_manager.auth_cached(); + async fn current_account_updated_notification(&self) -> AccountUpdatedNotification { + let provider = create_model_provider( + self.config.model_provider.clone(), + Some(self.auth_manager.clone()), + ); + let auth = provider.auth().await; AccountUpdatedNotification { - auth_mode: auth - .as_ref() - .map(CodexAuth::api_auth_mode) - .map(auth_mode_to_api), + auth_mode: provider.auth_mode(auth.as_ref()).map(auth_mode_to_api), plan_type: auth.as_ref().and_then(CodexAuth::account_plan_type), } } @@ -657,7 +658,7 @@ impl AccountRequestProcessor { self.outgoing .send_server_notification(ServerNotification::AccountUpdated( - self.current_account_updated_notification(), + self.current_account_updated_notification().await, )) .await; } diff --git a/codex-rs/cli/src/doctor.rs b/codex-rs/cli/src/doctor.rs index ef587cd1d4eb..95cb63f26c03 100644 --- a/codex-rs/cli/src/doctor.rs +++ b/codex-rs/cli/src/doctor.rs @@ -47,7 +47,6 @@ use codex_login::AuthDotJson; use codex_login::AuthManager; use codex_login::CODEX_ACCESS_TOKEN_ENV_VAR; use codex_login::CODEX_API_KEY_ENV_VAR; -use codex_login::CodexAuth; use codex_login::OPENAI_API_KEY_ENV_VAR; use codex_login::default_client::build_reqwest_client; use codex_login::default_client::default_headers; @@ -2321,9 +2320,10 @@ async fn websocket_reachability_check( let runtime_provider = create_model_provider(provider.clone(), auth_manager); let auth = runtime_provider.auth().await; + let auth_mode = runtime_provider.auth_mode(auth.as_ref()); details.push(format!( "auth mode: {}", - auth.as_ref().map(auth_mode_name).unwrap_or("none") + auth_mode.map(auth_mode_name).unwrap_or("none") )); let api_provider = match runtime_provider.api_provider().await { @@ -2461,8 +2461,8 @@ fn websocket_error_detail(err: &ApiError) -> String { } } -fn auth_mode_name(auth: &CodexAuth) -> &'static str { - match auth.auth_mode() { +fn auth_mode_name(auth_mode: AuthMode) -> &'static str { + match auth_mode { AuthMode::ApiKey => "api_key", AuthMode::Chatgpt => "chatgpt", AuthMode::ChatgptAuthTokens => "chatgpt_auth_tokens", diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index d3f9b7b96b02..c7492960de99 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -10,15 +10,16 @@ use codex_config::types::AuthCredentialsStoreMode; use codex_core::config::Config; use codex_login::AuthKeyringBackendKind; +use codex_login::AuthManager; use codex_login::AuthRouteConfig; use codex_login::CLIENT_ID; -use codex_login::CodexAuth; use codex_login::ServerOptions; use codex_login::login_with_access_token; use codex_login::login_with_api_key; use codex_login::logout_with_revoke; use codex_login::run_device_code_login; use codex_login::run_login_server; +use codex_model_provider::create_model_provider; use codex_protocol::auth::AuthMode; use codex_protocol::config_types::ForcedLoginMethod; use codex_utils_cli::CliConfigOverrides; @@ -423,51 +424,50 @@ pub async fn run_login_with_device_code_fallback_to_browser( pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { let config = load_config_or_exit(cli_config_overrides).await; - let auth_route_config = config.auth_route_config(); + let auth_manager = + AuthManager::shared_from_config(&config, /*enable_codex_api_key_env*/ true).await; + let provider = create_model_provider(config.model_provider.clone(), Some(auth_manager)); + let auth = provider.auth().await; + let auth_mode = provider.auth_mode(auth.as_ref()); + + if config.model_provider.is_amazon_bedrock() { + if auth_mode == Some(AuthMode::BedrockApiKey) { + eprintln!("Logged in using Amazon Bedrock API key"); + } else { + eprintln!("Using Amazon Bedrock AWS SDK credential chain"); + } + std::process::exit(0); + } - match CodexAuth::from_auth_storage( - &config.codex_home, - config.cli_auth_credentials_store_mode, - Some(&config.chatgpt_base_url), - config.auth_keyring_backend_kind(), - auth_route_config.as_ref(), - ) - .await - { - Ok(Some(auth)) => match auth.auth_mode() { - AuthMode::ApiKey => match auth.get_token() { - Ok(api_key) => { - eprintln!("Logged in using an API key - {}", safe_format_key(&api_key)); - std::process::exit(0); - } - Err(e) => { - eprintln!("Unexpected error retrieving API key: {e}"); - std::process::exit(1); - } - }, - AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens => { - eprintln!("Logged in using ChatGPT"); + match (auth_mode, auth) { + (Some(AuthMode::ApiKey), Some(auth)) => match auth.get_token() { + Ok(api_key) => { + eprintln!("Logged in using an API key - {}", safe_format_key(&api_key)); std::process::exit(0); } - AuthMode::AgentIdentity => { - eprintln!("Logged in using access token"); - std::process::exit(0); - } - AuthMode::PersonalAccessToken => { - eprintln!("Logged in using personal access token"); - std::process::exit(0); - } - AuthMode::BedrockApiKey => { - eprintln!("Logged in using Amazon Bedrock API key"); - std::process::exit(0); + Err(e) => { + eprintln!("Unexpected error retrieving API key: {e}"); + std::process::exit(1); } }, - Ok(None) => { + (Some(AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens), Some(_)) => { + eprintln!("Logged in using ChatGPT"); + std::process::exit(0); + } + (Some(AuthMode::AgentIdentity), Some(_)) => { + eprintln!("Logged in using access token"); + std::process::exit(0); + } + (Some(AuthMode::PersonalAccessToken), Some(_)) => { + eprintln!("Logged in using personal access token"); + std::process::exit(0); + } + (None, None) => { eprintln!("Not logged in"); std::process::exit(1); } - Err(e) => { - eprintln!("Error checking login status: {e}"); + (auth_mode, _) => { + eprintln!("Unexpected provider auth state: mode={auth_mode:?}"); std::process::exit(1); } } diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 43a758b0e851..9156061ee41a 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -216,6 +216,7 @@ struct ModelClientState { /// share the same auth/provider setup flow. struct CurrentClientSetup { auth: Option, + auth_mode: Option, api_provider: ApiProvider, api_auth: SharedAuthProvider, agent_identity_telemetry: Option, @@ -536,7 +537,7 @@ impl ModelClient { let request_telemetry = Self::build_request_telemetry( session_telemetry, AuthRequestTelemetryContext::new( - client_setup.auth.as_ref().map(CodexAuth::auth_mode), + client_setup.auth_mode, client_setup.api_auth.as_ref(), client_setup.agent_identity_telemetry.clone(), PendingUnauthorizedRetry::default(), @@ -669,7 +670,7 @@ impl ModelClient { let request_telemetry = Self::build_request_telemetry( session_telemetry, AuthRequestTelemetryContext::new( - client_setup.auth.as_ref().map(CodexAuth::auth_mode), + client_setup.auth_mode, client_setup.api_auth.as_ref(), client_setup.agent_identity_telemetry.clone(), PendingUnauthorizedRetry::default(), @@ -920,6 +921,7 @@ impl ModelClient { /// lockstep when auth/provider resolution changes. async fn current_client_setup(&self) -> Result { let auth = self.state.provider.auth().await; + let auth_mode = self.state.provider.auth_mode(auth.as_ref()); let api_provider = self.state.provider.api_provider().await?; let resolved_auth = self .state @@ -932,6 +934,7 @@ impl ModelClient { .await?; Ok(CurrentClientSetup { auth, + auth_mode, api_provider, api_auth: resolved_auth.auth, agent_identity_telemetry: resolved_auth.agent_identity_telemetry, @@ -1235,7 +1238,7 @@ impl ModelClientSession { )) })?; let auth_context = AuthRequestTelemetryContext::new( - client_setup.auth.as_ref().map(CodexAuth::auth_mode), + client_setup.auth_mode, client_setup.api_auth.as_ref(), client_setup.agent_identity_telemetry.clone(), PendingUnauthorizedRetry::default(), @@ -1374,7 +1377,7 @@ impl ModelClientSession { let client_setup = self.client.current_client_setup().await?; let transport = ReqwestTransport::new(build_reqwest_client()); let request_auth_context = AuthRequestTelemetryContext::new( - client_setup.auth.as_ref().map(CodexAuth::auth_mode), + client_setup.auth_mode, client_setup.api_auth.as_ref(), client_setup.agent_identity_telemetry.clone(), pending_retry, @@ -1502,7 +1505,7 @@ impl ModelClientSession { loop { let client_setup = self.client.current_client_setup().await?; let request_auth_context = AuthRequestTelemetryContext::new( - client_setup.auth.as_ref().map(CodexAuth::auth_mode), + client_setup.auth_mode, client_setup.api_auth.as_ref(), client_setup.agent_identity_telemetry.clone(), pending_retry, diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index 9509cab45662..3936a17cdabf 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -1596,26 +1596,47 @@ async fn personal_access_token_does_not_offer_unauthorized_recovery() { #[serial(codex_auth_env)] async fn load_auth_keeps_codex_api_key_env_precedence() { let codex_home = tempdir().unwrap(); + let bedrock_auth = BedrockApiKeyAuth { + api_key: "bedrock-api-key-test".to_string(), + region: "us-east-1".to_string(), + }; let record = agent_identity_record(WORKSPACE_ID_ALLOWED); let agent_identity = fake_agent_identity_jwt(&record).expect("fake agent identity"); let _access_token_guard = EnvVarGuard::set(CODEX_ACCESS_TOKEN_ENV_VAR, &agent_identity); let _api_key_guard = EnvVarGuard::set(CODEX_API_KEY_ENV_VAR, "sk-env"); - let auth = super::load_auth( - codex_home.path(), + let auth_manager = AuthManager::new( + codex_home.path().to_path_buf(), /*enable_codex_api_key_env*/ true, AuthCredentialsStoreMode::File, /*forced_chatgpt_workspace_id*/ None, /*chatgpt_base_url*/ None, AuthKeyringBackendKind::Direct, - /*agent_identity_authapi_base_url*/ None, /*auth_route_config*/ None, ) - .await - .expect("env auth should load") - .expect("env auth should be present"); + .await; + assert_eq!(auth_manager.bedrock_api_key_auth_cached(), None); - assert_eq!(auth.api_key(), Some("sk-env")); + crate::auth::login_with_bedrock_api_key( + codex_home.path(), + &bedrock_auth.api_key, + &bedrock_auth.region, + AuthCredentialsStoreMode::File, + AuthKeyringBackendKind::Direct, + ) + .expect("seed Bedrock auth"); + assert!(auth_manager.reload().await); + + assert_eq!( + auth_manager + .auth_cached() + .and_then(|auth| auth.api_key().map(str::to_string)), + Some("sk-env".to_string()) + ); + assert_eq!( + auth_manager.bedrock_api_key_auth_cached(), + Some(bedrock_auth) + ); } #[tokio::test] diff --git a/codex-rs/login/src/auth/bedrock_api_key.rs b/codex-rs/login/src/auth/bedrock_api_key.rs index 3445878365a1..57c02e058ebd 100644 --- a/codex-rs/login/src/auth/bedrock_api_key.rs +++ b/codex-rs/login/src/auth/bedrock_api_key.rs @@ -1,21 +1,33 @@ +use std::fmt; use std::path::Path; use codex_config::types::AuthCredentialsStoreMode; use serde::Deserialize; use serde::Serialize; +use super::manager::AuthManager; +use super::manager::load_auth_dot_json; use super::manager::save_auth; use super::storage::AuthDotJson; use super::storage::AuthKeyringBackendKind; use codex_protocol::auth::AuthMode; /// Managed Amazon Bedrock API key persisted in `auth.json`. -#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +#[derive(Deserialize, Serialize, Clone, PartialEq, Eq)] pub struct BedrockApiKeyAuth { pub api_key: String, pub region: String, } +impl fmt::Debug for BedrockApiKeyAuth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("BedrockApiKeyAuth") + .field("api_key", &"") + .field("region", &self.region) + .finish() + } +} + /// Writes an `auth.json` that contains only the Amazon Bedrock API key auth. pub fn login_with_bedrock_api_key( codex_home: &Path, @@ -44,6 +56,67 @@ pub fn login_with_bedrock_api_key( ) } +pub(super) fn load_stored_bedrock_api_key( + codex_home: &Path, + auth_credentials_store_mode: AuthCredentialsStoreMode, + keyring_backend_kind: AuthKeyringBackendKind, +) -> std::io::Result> { + load_stored_bedrock_api_key_auth( + codex_home, + auth_credentials_store_mode, + keyring_backend_kind, + ) + .map(|auth| auth.and_then(|auth| auth.bedrock_api_key)) +} + +fn load_stored_bedrock_api_key_auth( + codex_home: &Path, + auth_credentials_store_mode: AuthCredentialsStoreMode, + keyring_backend_kind: AuthKeyringBackendKind, +) -> std::io::Result> { + load_auth_dot_json( + codex_home, + auth_credentials_store_mode, + keyring_backend_kind, + ) + .map(|auth| auth.filter(AuthDotJson::is_bedrock_api_key)) +} + +impl AuthManager { + pub fn has_stored_bedrock_api_key_auth(&self) -> std::io::Result { + load_stored_bedrock_api_key_auth( + &self.codex_home, + self.auth_credentials_store_mode, + self.keyring_backend_kind, + ) + .map(|auth| auth.is_some()) + } + + /// Persists managed Bedrock auth without changing the in-memory auth snapshot. + /// + /// The account coordinator publishes the provider-scoped cache update only + /// after the associated config mutation succeeds. + pub fn persist_bedrock_api_key_auth(&self, api_key: &str, region: &str) -> std::io::Result<()> { + login_with_bedrock_api_key( + &self.codex_home, + api_key, + region, + self.auth_credentials_store_mode, + self.keyring_backend_kind, + ) + } + + /// Reloads only managed Bedrock auth, preserving cached general Codex auth. + pub fn reload_bedrock_api_key_auth(&self) -> std::io::Result { + let bedrock_api_key = load_stored_bedrock_api_key( + &self.codex_home, + self.auth_credentials_store_mode, + self.keyring_backend_kind, + )?; + Ok(self.set_cached_bedrock_api_key_auth(bedrock_api_key)) + } +} + #[cfg(test)] #[path = "bedrock_api_key_tests.rs"] mod tests; diff --git a/codex-rs/login/src/auth/bedrock_api_key_tests.rs b/codex-rs/login/src/auth/bedrock_api_key_tests.rs index 1f8fab68b2c9..a00fbe68e2f4 100644 --- a/codex-rs/login/src/auth/bedrock_api_key_tests.rs +++ b/codex-rs/login/src/auth/bedrock_api_key_tests.rs @@ -1,13 +1,15 @@ use codex_config::types::AuthCredentialsStoreMode; use codex_protocol::auth::AuthMode; +use codex_protocol::config_types::ForcedLoginMethod; use pretty_assertions::assert_eq; use serial_test::serial; use tempfile::tempdir; use super::*; +use crate::auth::AuthConfig; use crate::auth::AuthKeyringBackendKind; use crate::auth::AuthManager; -use crate::auth::CodexAuth; +use crate::auth::enforce_login_restrictions; use crate::auth::storage::AuthStorageBackend; use crate::auth::storage::FileAuthStorage; @@ -42,20 +44,24 @@ fn bedrock_auth() -> BedrockApiKeyAuth { } } +fn auth_config(codex_home: &std::path::Path, forced_login_method: ForcedLoginMethod) -> AuthConfig { + AuthConfig { + codex_home: codex_home.to_path_buf(), + auth_credentials_store_mode: AuthCredentialsStoreMode::File, + keyring_backend_kind: AuthKeyringBackendKind::default(), + forced_login_method: Some(forced_login_method), + chatgpt_base_url: None, + forced_chatgpt_workspace_id: None, + auth_route_config: None, + } +} + #[tokio::test] #[serial(codex_auth_env)] -async fn login_with_bedrock_api_key_replaces_openai_auth() -> anyhow::Result<()> { +async fn managed_bedrock_persistence_preserves_cached_openai_auth() -> anyhow::Result<()> { let codex_home = tempdir()?; let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); storage.save(&api_key_auth())?; - login_with_bedrock_api_key( - codex_home.path(), - "bedrock-api-key-test", - "us-east-1", - AuthCredentialsStoreMode::File, - AuthKeyringBackendKind::default(), - )?; - let auth_manager = AuthManager::new( codex_home.path().to_path_buf(), /*enable_codex_api_key_env*/ false, @@ -67,6 +73,8 @@ async fn login_with_bedrock_api_key_replaces_openai_auth() -> anyhow::Result<()> ) .await; + auth_manager.persist_bedrock_api_key_auth("bedrock-api-key-test", "us-east-1")?; + let loaded = storage.load()?.expect("auth should be stored"); let expected = AuthDotJson { auth_mode: Some(AuthMode::BedrockApiKey), @@ -78,24 +86,33 @@ async fn login_with_bedrock_api_key_replaces_openai_auth() -> anyhow::Result<()> bedrock_api_key: Some(bedrock_auth()), }; assert_eq!(loaded, expected); - assert_eq!(auth_manager.auth_mode(), Some(AuthMode::BedrockApiKey)); + assert_eq!(auth_manager.auth_mode(), Some(AuthMode::ApiKey)); + assert_eq!(auth_manager.bedrock_api_key_auth_cached(), None); + assert!(auth_manager.reload_bedrock_api_key_auth()?); assert_eq!( - auth_manager.auth_cached().and_then(|auth| match auth { - CodexAuth::BedrockApiKey(auth) => Some(auth), - CodexAuth::ApiKey(_) - | CodexAuth::Chatgpt(_) - | CodexAuth::ChatgptAuthTokens(_) - | CodexAuth::AgentIdentity(_) - | CodexAuth::PersonalAccessToken(_) => None, - }), + auth_manager + .auth_cached() + .and_then(|auth| auth.api_key().map(str::to_string)), + Some("sk-test-key".to_string()) + ); + assert_eq!( + auth_manager.bedrock_api_key_auth_cached(), Some(bedrock_auth()) ); Ok(()) } +#[test] +fn bedrock_auth_debug_redacts_api_key() { + assert_eq!( + format!("{:?}", bedrock_auth()), + "BedrockApiKeyAuth { api_key: \"\", region: \"us-east-1\" }" + ); +} + #[tokio::test] #[serial(codex_auth_env)] -async fn logout_removes_bedrock_auth() -> anyhow::Result<()> { +async fn auth_manager_logout_removes_bedrock_api_key_auth() -> anyhow::Result<()> { let codex_home = tempdir()?; let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); login_with_bedrock_api_key( @@ -120,12 +137,13 @@ async fn logout_removes_bedrock_auth() -> anyhow::Result<()> { assert_eq!(storage.load()?, None); assert_eq!(auth_manager.auth_cached(), None); + assert_eq!(auth_manager.bedrock_api_key_auth_cached(), None); Ok(()) } #[tokio::test] #[serial(codex_auth_env)] -async fn bedrock_only_auth_storage_creates_primary_auth() -> anyhow::Result<()> { +async fn bedrock_only_auth_storage_does_not_create_codex_auth() -> anyhow::Result<()> { let codex_home = tempdir()?; let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); storage.save(&bedrock_only_auth())?; @@ -141,18 +159,12 @@ async fn bedrock_only_auth_storage_creates_primary_auth() -> anyhow::Result<()> ) .await; - assert_eq!(auth_manager.auth_mode(), Some(AuthMode::BedrockApiKey)); + assert_eq!(auth_manager.auth_mode(), None); assert_eq!( - auth_manager.auth_cached().and_then(|auth| match auth { - CodexAuth::BedrockApiKey(auth) => Some(auth), - CodexAuth::ApiKey(_) - | CodexAuth::Chatgpt(_) - | CodexAuth::ChatgptAuthTokens(_) - | CodexAuth::AgentIdentity(_) - | CodexAuth::PersonalAccessToken(_) => None, - }), + auth_manager.bedrock_api_key_auth_cached(), Some(bedrock_auth()) ); + assert_eq!(auth_manager.auth_cached(), None); Ok(()) } @@ -178,3 +190,35 @@ async fn login_with_api_key_clears_bedrock_api_key() -> anyhow::Result<()> { assert_eq!(storage.load()?, Some(api_key_auth())); Ok(()) } + +#[tokio::test] +async fn forced_chatgpt_login_removes_bedrock_auth() -> anyhow::Result<()> { + let codex_home = tempdir()?; + let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); + storage.save(&bedrock_only_auth())?; + + let error = + enforce_login_restrictions(&auth_config(codex_home.path(), ForcedLoginMethod::Chatgpt)) + .await + .expect_err("Bedrock auth should violate forced ChatGPT login"); + + assert_eq!( + error.to_string(), + "ChatGPT login is required, but an API key is currently being used. Logging out." + ); + assert_eq!(storage.load()?, None); + Ok(()) +} + +#[tokio::test] +async fn forced_api_login_allows_bedrock_auth() -> anyhow::Result<()> { + let codex_home = tempdir()?; + let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); + let auth = bedrock_only_auth(); + storage.save(&auth)?; + + enforce_login_restrictions(&auth_config(codex_home.path(), ForcedLoginMethod::Api)).await?; + + assert_eq!(storage.load()?, Some(auth)); + Ok(()) +} diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 844410874d58..d2d7d3300080 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -36,6 +36,7 @@ use super::agent_identity::record_needs_task_registration; use super::agent_identity::register_managed_chatgpt_agent_identity; use super::agent_identity::require_agent_identity_authapi_base_url; use super::agent_identity::verified_record_from_jwt; +use super::bedrock_api_key::load_stored_bedrock_api_key; use super::external_bearer::BearerTokenRefresher; use super::revoke::revoke_auth_tokens; pub use crate::auth::agent_identity::AgentIdentityAuth; @@ -73,7 +74,6 @@ pub enum CodexAuth { ChatgptAuthTokens(ChatgptAuthTokens), AgentIdentity(AgentIdentityAuth), PersonalAccessToken(PersonalAccessTokenAuth), - BedrockApiKey(BedrockApiKeyAuth), } /// Policy for resolving Agent Identity auth from a broader Codex auth snapshot. @@ -146,7 +146,6 @@ impl PartialEq for CodexAuth { fn eq(&self, other: &Self) -> bool { match (self, other) { (Self::PersonalAccessToken(a), Self::PersonalAccessToken(b)) => a == b, - (Self::BedrockApiKey(a), Self::BedrockApiKey(b)) => a == b, _ => self.api_auth_mode() == other.api_auth_mode(), } } @@ -350,12 +349,9 @@ impl CodexAuth { .await; } if auth_mode == AuthMode::BedrockApiKey { - let Some(auth) = auth_dot_json.bedrock_api_key else { - return Err(std::io::Error::other( - "Bedrock API key auth is missing a Bedrock API key.", - )); - }; - return Ok(Self::BedrockApiKey(auth)); + return Err(std::io::Error::other( + "Amazon Bedrock API key auth is provider-specific", + )); } let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode); @@ -380,7 +376,9 @@ impl CodexAuth { AuthMode::PersonalAccessToken => { unreachable!("personal access token mode is handled above") } - AuthMode::BedrockApiKey => unreachable!("bedrock api key mode is handled above"), + AuthMode::BedrockApiKey => { + unreachable!("bedrock api key mode is rejected above") + } } } @@ -460,7 +458,6 @@ impl CodexAuth { Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => AuthMode::Chatgpt, Self::AgentIdentity(_) => AuthMode::AgentIdentity, Self::PersonalAccessToken(_) => AuthMode::PersonalAccessToken, - Self::BedrockApiKey(_) => AuthMode::BedrockApiKey, } } @@ -472,7 +469,6 @@ impl CodexAuth { Self::ChatgptAuthTokens(_) => AuthMode::ChatgptAuthTokens, Self::AgentIdentity(_) => AuthMode::AgentIdentity, Self::PersonalAccessToken(_) => AuthMode::PersonalAccessToken, - Self::BedrockApiKey(_) => AuthMode::BedrockApiKey, } } @@ -507,8 +503,7 @@ impl CodexAuth { Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) | Self::AgentIdentity(_) - | Self::PersonalAccessToken(_) - | Self::BedrockApiKey(_) => None, + | Self::PersonalAccessToken(_) => None, } } @@ -537,9 +532,6 @@ impl CodexAuth { "agent identity auth does not expose a bearer token", )), Self::PersonalAccessToken(auth) => Ok(auth.access_token().to_string()), - Self::BedrockApiKey(_) => Err(std::io::Error::other( - "Bedrock API key auth does not expose a Codex bearer token", - )), } } @@ -612,10 +604,7 @@ impl CodexAuth { let state = match self { Self::Chatgpt(auth) => &auth.state, Self::ChatgptAuthTokens(auth) => &auth.state, - Self::ApiKey(_) - | Self::AgentIdentity(_) - | Self::PersonalAccessToken(_) - | Self::BedrockApiKey(_) => return None, + Self::ApiKey(_) | Self::AgentIdentity(_) | Self::PersonalAccessToken(_) => return None, }; #[expect(clippy::unwrap_used)] state.auth_dot_json.lock().unwrap().clone() @@ -656,10 +645,7 @@ impl CodexAuth { ) -> std::io::Result> { match self { Self::AgentIdentity(auth) => Ok(Some(auth.clone())), - Self::ApiKey(_) - | Self::ChatgptAuthTokens(_) - | Self::PersonalAccessToken(_) - | Self::BedrockApiKey(_) => Ok(None), + Self::ApiKey(_) | Self::ChatgptAuthTokens(_) | Self::PersonalAccessToken(_) => Ok(None), Self::Chatgpt(_) => { if policy == AgentIdentityAuthPolicy::JwtOnly { return Ok(None); @@ -1065,7 +1051,7 @@ async fn enforce_login_restrictions_with_agent_identity_authapi_base_url( config: &AuthConfig, agent_identity_authapi_base_url: Option<&str>, ) -> std::io::Result<()> { - let Some(auth) = load_auth( + let auth = load_auth( &config.codex_home, /*enable_codex_api_key_env*/ true, config.auth_credentials_store_mode, @@ -1075,13 +1061,23 @@ async fn enforce_login_restrictions_with_agent_identity_authapi_base_url( agent_identity_authapi_base_url, config.auth_route_config.as_ref(), ) - .await? - else { + .await?; + let stored_bedrock_api_key = load_stored_bedrock_api_key( + &config.codex_home, + config.auth_credentials_store_mode, + config.keyring_backend_kind, + )?; + let auth_mode = auth.as_ref().map(CodexAuth::auth_mode).or_else(|| { + stored_bedrock_api_key + .as_ref() + .map(|_| AuthMode::BedrockApiKey) + }); + let Some(auth_mode) = auth_mode else { return Ok(()); }; if let Some(required_method) = config.forced_login_method { - let method_violation = match (required_method, auth.auth_mode()) { + let method_violation = match (required_method, auth_mode) { (ForcedLoginMethod::Api, AuthMode::ApiKey) | (ForcedLoginMethod::Api, AuthMode::BedrockApiKey) => None, (ForcedLoginMethod::Chatgpt, AuthMode::Chatgpt) @@ -1113,8 +1109,11 @@ async fn enforce_login_restrictions_with_agent_identity_authapi_base_url( } if let Some(expected_account_ids) = config.forced_chatgpt_workspace_id.as_deref() { - let chatgpt_account_id = match &auth { - CodexAuth::ApiKey(_) | CodexAuth::BedrockApiKey(_) => return Ok(()), + let Some(auth) = auth.as_ref() else { + return Ok(()); + }; + let chatgpt_account_id = match auth { + CodexAuth::ApiKey(_) => return Ok(()), CodexAuth::AgentIdentity(_) | CodexAuth::PersonalAccessToken(_) => { auth.get_account_id() } @@ -1232,7 +1231,10 @@ async fn load_auth( AuthCredentialsStoreMode::Ephemeral, AuthKeyringBackendKind::default(), ); - if let Some(auth_dot_json) = ephemeral_storage.load()? { + if let Some(auth_dot_json) = ephemeral_storage + .load()? + .filter(|auth| !auth.is_bedrock_api_key()) + { let auth = CodexAuth::from_auth_dot_json( codex_home, auth_dot_json, @@ -1281,7 +1283,8 @@ async fn load_auth( keyring_backend_kind, ); let auth_dot_json = match storage.load()? { - Some(auth) => auth, + Some(auth) if !auth.is_bedrock_api_key() => auth, + Some(_) => return Ok(None), None => return Ok(None), }; @@ -1456,6 +1459,11 @@ fn refresh_token_endpoint() -> String { } impl AuthDotJson { + /// Returns whether this stored payload resolves to managed Amazon Bedrock API key auth. + pub fn is_bedrock_api_key(&self) -> bool { + self.resolved_mode() == AuthMode::BedrockApiKey + } + fn from_external_tokens(external: &ExternalAuthTokens) -> std::io::Result { let Some(chatgpt_metadata) = external.chatgpt_metadata() else { return Err(std::io::Error::other( @@ -1534,6 +1542,7 @@ impl AuthDotJson { #[derive(Clone)] struct CachedAuth { auth: Option, + bedrock_api_key: Option, /// Permanent refresh failure cached for the current auth snapshot so /// later refresh attempts for the same credentials fail fast without network. permanent_refresh_failure: Option, @@ -1552,6 +1561,7 @@ impl Debug for CachedAuth { "auth_mode", &self.auth.as_ref().map(CodexAuth::api_auth_mode), ) + .field("has_bedrock_api_key", &self.bedrock_api_key.is_some()) .field( "permanent_refresh_failure", &self @@ -1790,12 +1800,12 @@ impl UnauthorizedRecovery { /// `reload()` is called explicitly. This matches the design goal of avoiding /// different parts of the program seeing inconsistent auth data mid‑run. pub struct AuthManager { - codex_home: PathBuf, + pub(super) codex_home: PathBuf, inner: RwLock, auth_change_tx: watch::Sender, enable_codex_api_key_env: bool, - auth_credentials_store_mode: AuthCredentialsStoreMode, - keyring_backend_kind: AuthKeyringBackendKind, + pub(super) auth_credentials_store_mode: AuthCredentialsStoreMode, + pub(super) keyring_backend_kind: AuthKeyringBackendKind, forced_chatgpt_workspace_id: RwLock>>, chatgpt_base_url: Option, agent_identity_authapi_base_url: Option, @@ -1874,6 +1884,13 @@ impl AuthManager { ) -> Self { let agent_identity_authapi_base_url = agent_identity_authapi_base_url(chatgpt_base_url.as_deref()).ok(); + let bedrock_api_key = load_stored_bedrock_api_key( + &codex_home, + auth_credentials_store_mode, + keyring_backend_kind, + ) + .ok() + .flatten(); let managed_auth = load_auth( &codex_home, enable_codex_api_key_env, @@ -1892,6 +1909,7 @@ impl AuthManager { codex_home, inner: RwLock::new(CachedAuth { auth: managed_auth, + bedrock_api_key, permanent_refresh_failure: None, }), auth_change_tx, @@ -1913,6 +1931,7 @@ impl AuthManager { pub fn from_auth_for_testing(auth: CodexAuth) -> Arc { let cached = CachedAuth { auth: Some(auth), + bedrock_api_key: None, permanent_refresh_failure: None, }; let (auth_change_tx, _auth_change_rx) = watch::channel(0); @@ -1939,6 +1958,7 @@ impl AuthManager { pub fn from_auth_for_testing_with_home(auth: CodexAuth, codex_home: PathBuf) -> Arc { let cached = CachedAuth { auth: Some(auth), + bedrock_api_key: None, permanent_refresh_failure: None, }; let (auth_change_tx, _auth_change_rx) = watch::channel(0); @@ -1968,6 +1988,7 @@ impl AuthManager { ) -> Arc { let cached = CachedAuth { auth: Some(auth), + bedrock_api_key: None, permanent_refresh_failure: None, }; let (auth_change_tx, _auth_change_rx) = watch::channel(0); @@ -1999,6 +2020,7 @@ impl AuthManager { codex_home: PathBuf::from("non-existent"), inner: RwLock::new(CachedAuth { auth: None, + bedrock_api_key: None, permanent_refresh_failure: None, }), auth_change_tx, @@ -2023,6 +2045,14 @@ impl AuthManager { self.inner.read().ok().and_then(|c| c.auth.clone()) } + /// Current managed Amazon Bedrock API key without applying Codex auth precedence. + pub fn bedrock_api_key_auth_cached(&self) -> Option { + self.inner + .read() + .ok() + .and_then(|cached| cached.bedrock_api_key.clone()) + } + /// Subscribes to cached auth changes that can affect request recovery. pub fn auth_change_receiver(&self) -> watch::Receiver { self.auth_change_tx.subscribe() @@ -2125,7 +2155,8 @@ impl AuthManager { pub async fn reload(&self) -> bool { tracing::info!("Reloading auth"); let new_auth = self.load_auth_from_storage().await; - self.set_cached_auth(new_auth) + let new_bedrock_api_key = self.load_bedrock_api_key_from_storage(); + self.set_cached_auth_state(new_auth, new_bedrock_api_key) } async fn reload_if_account_id_matches( @@ -2179,7 +2210,6 @@ impl AuthManager { _ => false, }, (AuthMode::PersonalAccessToken, AuthMode::PersonalAccessToken) => a == b, - (AuthMode::BedrockApiKey, AuthMode::BedrockApiKey) => a == b, _ => false, }, _ => false, @@ -2230,6 +2260,60 @@ impl AuthManager { .flatten() } + fn load_bedrock_api_key_from_storage(&self) -> Option { + load_stored_bedrock_api_key( + &self.codex_home, + self.auth_credentials_store_mode, + self.keyring_backend_kind, + ) + .ok() + .flatten() + } + + fn set_cached_auth_state( + &self, + new_auth: Option, + new_bedrock_api_key: Option, + ) -> bool { + if let Ok(mut guard) = self.inner.write() { + let previous = guard.auth.as_ref(); + let auth_changed = !Self::auths_equal(previous, new_auth.as_ref()); + let auth_changed_for_refresh = + !Self::auths_equal_for_refresh(previous, new_auth.as_ref()); + let bedrock_auth_changed = guard.bedrock_api_key != new_bedrock_api_key; + if auth_changed_for_refresh { + guard.permanent_refresh_failure = None; + } + let changed = auth_changed || bedrock_auth_changed; + tracing::info!("Reloaded auth, changed: {changed}"); + guard.auth = new_auth; + guard.bedrock_api_key = new_bedrock_api_key; + if auth_changed_for_refresh || bedrock_auth_changed { + self.auth_change_tx.send_modify(|revision| *revision += 1); + } + changed + } else { + false + } + } + + pub(super) fn set_cached_bedrock_api_key_auth( + &self, + new_bedrock_api_key: Option, + ) -> bool { + if let Ok(mut guard) = self.inner.write() { + let changed = guard.bedrock_api_key != new_bedrock_api_key; + tracing::info!("Reloaded managed Bedrock auth, changed: {changed}"); + guard.bedrock_api_key = new_bedrock_api_key; + if changed { + self.auth_change_tx.send_modify(|revision| *revision += 1); + } + changed + } else { + false + } + } + fn set_cached_auth(&self, new_auth: Option) -> bool { if let Ok(mut guard) = self.inner.write() { let previous = guard.auth.as_ref(); @@ -2453,8 +2537,7 @@ impl AuthManager { } CodexAuth::ApiKey(_) | CodexAuth::AgentIdentity(_) - | CodexAuth::PersonalAccessToken(_) - | CodexAuth::BedrockApiKey(_) => Ok(()), + | CodexAuth::PersonalAccessToken(_) => Ok(()), }; if let Err(RefreshTokenError::Permanent(error)) = &result { self.record_permanent_refresh_failure_if_unchanged(&attempted_auth, error); diff --git a/codex-rs/model-provider/src/amazon_bedrock/mod.rs b/codex-rs/model-provider/src/amazon_bedrock/mod.rs index 108308c1bfae..73a4a8818c16 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/mod.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/mod.rs @@ -19,6 +19,7 @@ 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::auth::AuthMode; use codex_protocol::error::CodexErr; use codex_protocol::error::Result; use codex_protocol::openai_models::ModelsResponse; @@ -38,13 +39,13 @@ use mantle::runtime_base_url; pub(crate) struct AmazonBedrockModelProvider { pub(crate) info: ModelProviderInfo, pub(crate) aws: ModelProviderAwsAuthInfo, - auth_manager: Option>, + managed_auth: Option, } impl AmazonBedrockModelProvider { pub(crate) fn new( provider_info: ModelProviderInfo, - auth_manager: Option>, + managed_auth: Option, ) -> Self { let aws = provider_info .aws @@ -56,26 +57,16 @@ impl AmazonBedrockModelProvider { Self { info: provider_info, aws, - auth_manager, + managed_auth, } } fn managed_auth(&self) -> Option { - self.auth_manager - .as_ref() - .and_then(|auth_manager| auth_manager.auth_cached()) - .and_then(|auth| match auth { - CodexAuth::BedrockApiKey(auth) => Some(auth), - CodexAuth::ApiKey(_) - | CodexAuth::Chatgpt(_) - | CodexAuth::ChatgptAuthTokens(_) - | CodexAuth::AgentIdentity(_) - | CodexAuth::PersonalAccessToken(_) => None, - }) + self.managed_auth.clone() } async fn auth(&self) -> Option { - self.managed_auth().map(CodexAuth::BedrockApiKey) + None } async fn api_provider(&self) -> Result { @@ -125,14 +116,17 @@ impl ModelProvider for AmazonBedrockModelProvider { } fn auth_manager(&self) -> Option> { - self.managed_auth() - .and_then(|_| self.auth_manager.as_ref().cloned()) + None } fn auth(&self) -> ModelProviderFuture<'_, Option> { Box::pin(AmazonBedrockModelProvider::auth(self)) } + fn auth_mode(&self, _auth: Option<&CodexAuth>) -> Option { + self.managed_auth().map(|_| AuthMode::BedrockApiKey) + } + fn account_state(&self) -> ProviderAccountResult { let credential_source = if self.managed_auth().is_some() { AmazonBedrockCredentialSource::CodexManaged @@ -206,25 +200,20 @@ mod tests { api_key: "managed-bedrock-api-key".to_string(), region: "us-east-1".to_string(), }; - let auth_manager = - AuthManager::from_auth_for_testing(CodexAuth::BedrockApiKey(managed_auth.clone())); let provider = AmazonBedrockModelProvider::new( ModelProviderInfo::create_amazon_bedrock_provider(Some(ModelProviderAwsAuthInfo { profile: Some("aws-profile-that-should-not-be-loaded".to_string()), region: Some("us-west-2".to_string()), })), - Some(auth_manager.clone()), + Some(managed_auth.clone()), ); - assert!(Arc::ptr_eq( - &provider - .auth_manager() - .expect("managed Bedrock auth manager should be exposed"), - &auth_manager, - )); + assert!(provider.auth_manager().is_none()); + let auth = provider.auth().await; + assert_eq!(auth, None); assert_eq!( - provider.auth().await, - Some(CodexAuth::BedrockApiKey(managed_auth)) + provider.auth_mode(auth.as_ref()), + Some(AuthMode::BedrockApiKey) ); assert_eq!( provider.account_state(), @@ -254,16 +243,16 @@ mod tests { } #[tokio::test] - async fn openai_auth_is_not_exposed_to_bedrock() { + async fn bedrock_without_managed_auth_uses_aws_account() { let provider = AmazonBedrockModelProvider::new( ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None), - Some(AuthManager::from_auth_for_testing(CodexAuth::from_api_key( - "openai-api-key", - ))), + /*managed_auth*/ None, ); assert!(provider.auth_manager().is_none()); - assert_eq!(provider.auth().await, None); + let auth = provider.auth().await; + assert_eq!(auth, None); + assert_eq!(provider.auth_mode(auth.as_ref()), None); assert_eq!( provider.account_state(), Ok(ProviderAccountState { @@ -279,7 +268,7 @@ mod tests { fn capabilities_disable_unsupported_hosted_tools() { let provider = AmazonBedrockModelProvider::new( ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None), - /*auth_manager*/ None, + /*managed_auth*/ None, ); assert_eq!( @@ -296,7 +285,7 @@ mod tests { fn approval_review_preferred_model_uses_bedrock_gpt_5_4() { let provider = AmazonBedrockModelProvider::new( ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None), - /*auth_manager*/ None, + /*managed_auth*/ None, ); assert_eq!( diff --git a/codex-rs/model-provider/src/auth.rs b/codex-rs/model-provider/src/auth.rs index 5b667e454973..8e6d63edd99b 100644 --- a/codex-rs/model-provider/src/auth.rs +++ b/codex-rs/model-provider/src/auth.rs @@ -13,16 +13,12 @@ use codex_login::auth::AgentIdentityAuth; use codex_login::auth::AgentIdentityAuthError; use codex_login::auth::AgentIdentityAuthPolicy; use codex_model_provider_info::ModelProviderInfo; -use codex_protocol::error::CodexErr; use codex_protocol::protocol::SessionSource; use http::HeaderMap; use http::HeaderValue; use crate::bearer_auth_provider::BearerAuthProvider; -const BEDROCK_API_KEY_UNSUPPORTED_MESSAGE: &str = - "Bedrock API key auth is only supported by the Amazon Bedrock model provider"; - #[derive(Clone, Debug)] pub struct ProviderAuthScope { pub agent_identity_policy: AgentIdentityAuthPolicy, @@ -139,12 +135,6 @@ pub(crate) fn resolve_provider_auth( auth: Option<&CodexAuth>, provider: &ModelProviderInfo, ) -> codex_protocol::error::Result { - if matches!(auth, Some(CodexAuth::BedrockApiKey(_))) { - return Err(CodexErr::UnsupportedOperation( - BEDROCK_API_KEY_UNSUPPORTED_MESSAGE.to_string(), - )); - } - if let Some(auth) = bearer_auth_for_provider(provider)? { return Ok(Arc::new(auth)); } @@ -243,7 +233,6 @@ pub fn auth_provider_from_auth(auth: &CodexAuth) -> SharedAuthProvider { CodexAuth::AgentIdentity(auth) => { Arc::new(AgentIdentityAuthProvider { auth: auth.clone() }) } - CodexAuth::BedrockApiKey(_) => unreachable!("{BEDROCK_API_KEY_UNSUPPORTED_MESSAGE}"), CodexAuth::ApiKey(_) | CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) @@ -261,7 +250,6 @@ mod tests { use codex_login::AuthCredentialsStoreMode; use codex_login::AuthKeyringBackendKind; use codex_login::auth::AgentIdentityAuthRecord; - use codex_login::auth::BedrockApiKeyAuth; use codex_model_provider_info::WireApi; use codex_model_provider_info::create_oss_provider_with_base_url; use codex_protocol::account::PlanType; @@ -387,24 +375,6 @@ mod tests { assert!(auth.to_auth_headers().is_empty()); } - - #[test] - fn openai_provider_rejects_bedrock_api_key_auth() { - let provider = ModelProviderInfo::create_openai_provider(/*base_url*/ None); - let auth = CodexAuth::BedrockApiKey(BedrockApiKeyAuth { - api_key: "bedrock-api-key-test".to_string(), - region: "us-east-1".to_string(), - }); - - match resolve_provider_auth(Some(&auth), &provider) { - Err(CodexErr::UnsupportedOperation(message)) => { - assert_eq!(message, BEDROCK_API_KEY_UNSUPPORTED_MESSAGE); - } - Err(err) => panic!("unexpected auth error: {err:?}"), - Ok(_) => panic!("Bedrock API key auth should be rejected"), - } - } - #[tokio::test] async fn first_party_run_scope_uses_agent_assertion_and_exposes_telemetry() { let auth = CodexAuth::AgentIdentity( diff --git a/codex-rs/model-provider/src/provider.rs b/codex-rs/model-provider/src/provider.rs index 5f101e52cd6b..5e19e0e0bfb3 100644 --- a/codex-rs/model-provider/src/provider.rs +++ b/codex-rs/model-provider/src/provider.rs @@ -14,6 +14,7 @@ use codex_models_manager::manager::OpenAiModelsManager; use codex_models_manager::manager::SharedModelsManager; use codex_models_manager::manager::StaticModelsManager; use codex_protocol::account::ProviderAccount; +use codex_protocol::auth::AuthMode; use codex_protocol::error::CodexErr; use codex_protocol::openai_models::ModelsResponse; @@ -58,7 +59,6 @@ pub struct ProviderAccountState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProviderAccountError { MissingChatgptAccountDetails, - UnsupportedBedrockApiKeyAuth, } impl fmt::Display for ProviderAccountError { @@ -67,12 +67,6 @@ impl fmt::Display for ProviderAccountError { Self::MissingChatgptAccountDetails => { write!(f, "plan type is required for chatgpt authentication") } - Self::UnsupportedBedrockApiKeyAuth => { - write!( - f, - "Bedrock API key auth is only supported by the Amazon Bedrock model provider" - ) - } } } } @@ -144,6 +138,11 @@ pub trait ModelProvider: fmt::Debug + Send + Sync { /// Returns the current provider-scoped auth value, if one is configured. fn auth(&self) -> ModelProviderFuture<'_, Option>; + /// Returns the reporting mode for the resolved provider auth. + fn auth_mode(&self, auth: Option<&CodexAuth>) -> Option { + auth.map(CodexAuth::auth_mode) + } + /// Returns the current app-visible account state for this provider. fn account_state(&self) -> ProviderAccountResult; @@ -156,8 +155,7 @@ pub trait ModelProvider: fmt::Debug + Send + Sync { fn api_provider(&self) -> ModelProviderFuture<'_, codex_protocol::error::Result> { Box::pin(async move { let auth = self.auth().await; - self.info() - .to_api_provider(auth.as_ref().map(CodexAuth::auth_mode)) + self.info().to_api_provider(self.auth_mode(auth.as_ref())) }) } @@ -220,7 +218,10 @@ pub fn create_model_provider( auth_manager: Option>, ) -> SharedModelProvider { if provider_info.is_amazon_bedrock() { - Arc::new(AmazonBedrockModelProvider::new(provider_info, auth_manager)) + let managed_auth = auth_manager + .as_ref() + .and_then(|auth_manager| auth_manager.bedrock_api_key_auth_cached()); + Arc::new(AmazonBedrockModelProvider::new(provider_info, managed_auth)) } else { Arc::new(ConfiguredModelProvider::new(provider_info, auth_manager)) } @@ -281,9 +282,6 @@ impl ModelProvider for ConfiguredModelProvider { }) .map(|auth| match &auth { CodexAuth::ApiKey(_) => Ok(ProviderAccount::ApiKey), - CodexAuth::BedrockApiKey(_) => { - Err(ProviderAccountError::UnsupportedBedrockApiKeyAuth) - } CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) | CodexAuth::AgentIdentity(_) @@ -337,7 +335,6 @@ mod tests { use std::num::NonZeroU64; use codex_login::auth::AgentIdentityAuthPolicy; - use codex_login::auth::BedrockApiKeyAuth; use codex_model_provider_info::ModelProviderAwsAuthInfo; use codex_model_provider_info::WireApi; use codex_model_provider_info::create_oss_provider_with_base_url; @@ -429,13 +426,6 @@ mod tests { .expect("valid model") } - fn bedrock_api_key_auth() -> CodexAuth { - CodexAuth::BedrockApiKey(BedrockApiKeyAuth { - api_key: "bedrock-api-key-test".to_string(), - region: "us-east-1".to_string(), - }) - } - #[tokio::test] async fn scoped_auth_ignores_scope_for_non_openai_provider() { let provider = create_model_provider( @@ -523,17 +513,6 @@ mod tests { assert!(provider.auth_manager().is_none()); } - #[tokio::test] - async fn create_model_provider_uses_managed_auth_for_amazon_bedrock_provider() { - let auth = bedrock_api_key_auth(); - let provider = create_model_provider( - ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None), - Some(AuthManager::from_auth_for_testing(auth.clone())), - ); - - assert_eq!(provider.auth().await, Some(auth)); - } - #[test] fn openai_provider_returns_unauthenticated_openai_account_state() { let provider = create_model_provider( @@ -589,19 +568,6 @@ mod tests { ); } - #[test] - fn openai_provider_rejects_bedrock_api_key_account_state() { - let provider = create_model_provider( - ModelProviderInfo::create_openai_provider(/*base_url*/ None), - Some(AuthManager::from_auth_for_testing(bedrock_api_key_auth())), - ); - - assert_eq!( - provider.account_state(), - Err(ProviderAccountError::UnsupportedBedrockApiKeyAuth) - ); - } - #[test] fn custom_non_openai_provider_returns_no_account_state() { let provider = create_model_provider(