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 58a1c8e4ff..0fa7efe560 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -802,9 +802,50 @@ impl AccountRequestProcessor { return Err(internal_error(format!("logout failed: {err}"))); } } - delete_amazon_bedrock_auth(&self.config.codex_home).map_err(|err| { - internal_error(format!("failed to remove Amazon Bedrock auth: {err}")) - })?; + let removed_bedrock_auth = + delete_amazon_bedrock_auth(&self.config.codex_home).map_err(|err| { + internal_error(format!("failed to remove Amazon Bedrock auth: {err}")) + })?; + if removed_bedrock_auth { + let current_config = self + .config_manager + .load_latest_config(/*fallback_cwd*/ None) + .await + .map_err(|err| internal_error(format!("failed to reload config: {err}")))?; + if current_config.model_provider_id == AMAZON_BEDROCK_PROVIDER_ID { + self.config_manager + .batch_write(ConfigBatchWriteParams { + edits: vec![ConfigWriteEdit { + key_path: "model_provider".to_string(), + value: serde_json::Value::Null, + merge_strategy: MergeStrategy::Replace, + }], + file_path: None, + expected_version: None, + reload_user_config: false, + }) + .await + .map_err(|err| { + internal_error(format!("failed to update Amazon Bedrock config: {err}")) + })?; + + let next_config = self + .config_manager + .load_latest_config(/*fallback_cwd*/ None) + .await + .map_err(|err| internal_error(format!("failed to reload config: {err}")))?; + for thread_id in self.thread_manager.list_thread_ids().await { + let Ok(thread) = self.thread_manager.get_thread(thread_id).await else { + continue; + }; + thread.refresh_runtime_config(next_config.clone()).await; + } + Self::spawn_effective_plugins_changed_task( + Arc::clone(&self.thread_manager), + self.config_manager.clone(), + ); + } + } Self::maybe_refresh_remote_installed_plugins_cache_for_current_config( &self.config_manager, diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index 1b95bc7d5b..ce15895b61 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -35,6 +35,7 @@ use codex_app_server_protocol::TurnStatus; use codex_config::types::AuthCredentialsStoreMode; use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR; use codex_login::login_with_api_key; +use codex_model_provider::save_amazon_bedrock_auth; use codex_protocol::account::PlanType as AccountPlanType; use core_test_support::responses; use pretty_assertions::assert_eq; @@ -248,6 +249,96 @@ async fn logout_account_removes_auth_and_notifies() -> Result<()> { Ok(()) } +#[tokio::test] +async fn logout_account_removes_bedrock_model_provider_after_managed_auth_removed() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + model_provider_id: Some("amazon-bedrock".to_string()), + extra_provider_config: Some( + r#"[model_providers.amazon-bedrock.aws] +region = "us-west-2" +"# + .to_string(), + ), + ..Default::default() + }, + )?; + save_amazon_bedrock_auth(codex_home.path(), "bedrock-key", "us-west-2")?; + + let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let id = mcp.send_logout_account_request().await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(id)), + ) + .await??; + let _ok: LogoutAccountResponse = to_response(resp)?; + + assert!( + !codex_home + .path() + .join("model-providers") + .join("amazon-bedrock") + .join("auth.json") + .exists(), + "managed Amazon Bedrock auth should be deleted" + ); + let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?; + assert!( + !config.contains("model_provider = \"amazon-bedrock\""), + "active Bedrock model provider should be removed after managed Bedrock logout" + ); + assert!( + config.contains("[model_providers.amazon-bedrock.aws]"), + "Bedrock provider config should be preserved" + ); + assert!( + config.contains("region = \"us-west-2\""), + "Bedrock region should be preserved" + ); + Ok(()) +} + +#[tokio::test] +async fn logout_account_keeps_bedrock_model_provider_without_managed_auth() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + model_provider_id: Some("amazon-bedrock".to_string()), + extra_provider_config: Some( + r#"[model_providers.amazon-bedrock.aws] +region = "us-west-2" +"# + .to_string(), + ), + ..Default::default() + }, + )?; + + let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let id = mcp.send_logout_account_request().await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(id)), + ) + .await??; + let _ok: LogoutAccountResponse = to_response(resp)?; + + let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?; + assert!( + config.contains("model_provider = \"amazon-bedrock\""), + "unmanaged Bedrock logout should not change the active provider" + ); + Ok(()) +} + #[tokio::test] async fn set_auth_token_updates_account_and_notifies() -> Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/model-provider/src/amazon_bedrock/auth.rs b/codex-rs/model-provider/src/amazon_bedrock/auth.rs index 75f7de1409..f9eede24c8 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/auth.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/auth.rs @@ -18,7 +18,6 @@ use crate::BearerAuthProvider; use super::mantle::aws_auth_config; use super::mantle::region_from_config; -use super::provider_auth::AmazonBedrockAuth; use super::provider_auth::StoredAmazonBedrockAuth; const AWS_BEARER_TOKEN_BEDROCK_ENV_VAR: &str = "AWS_BEARER_TOKEN_BEDROCK"; @@ -174,6 +173,7 @@ mod tests { use http::HeaderValue; use pretty_assertions::assert_eq; + use super::super::provider_auth::AmazonBedrockAuth; use super::*; fn missing_env_var(_: &'static str) -> std::result::Result {