diff --git a/codex-rs/app-server-protocol/schema/json/ServerNotification.json b/codex-rs/app-server-protocol/schema/json/ServerNotification.json index 89831b3fe..1c37ea6f3 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerNotification.json +++ b/codex-rs/app-server-protocol/schema/json/ServerNotification.json @@ -532,6 +532,13 @@ "personalAccessToken" ], "type": "string" + }, + { + "description": "Amazon Bedrock bearer token managed by Codex.", + "enum": [ + "bedrockApiKey" + ], + "type": "string" } ] }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index ba23f67a6..82646d0ec 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -6724,6 +6724,13 @@ "personalAccessToken" ], "type": "string" + }, + { + "description": "Amazon Bedrock bearer token managed by Codex.", + "enum": [ + "bedrockApiKey" + ], + "type": "string" } ] }, diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index 2ff8c8b55..6be7e76eb 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -1002,6 +1002,13 @@ "personalAccessToken" ], "type": "string" + }, + { + "description": "Amazon Bedrock bearer token managed by Codex.", + "enum": [ + "bedrockApiKey" + ], + "type": "string" } ] }, diff --git a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json index 013592c9e..5c1847c56 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json +++ b/codex-rs/app-server-protocol/schema/json/v2/AccountUpdatedNotification.json @@ -38,6 +38,13 @@ "personalAccessToken" ], "type": "string" + }, + { + "description": "Amazon Bedrock bearer token managed by Codex.", + "enum": [ + "bedrockApiKey" + ], + "type": "string" } ] }, diff --git a/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts b/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts index 1cb6ccb6b..77bc037eb 100644 --- a/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts +++ b/codex-rs/app-server-protocol/schema/typescript/AuthMode.ts @@ -5,4 +5,4 @@ /** * Authentication mode for OpenAI-backed providers. */ -export type AuthMode = "apikey" | "chatgpt" | "chatgptAuthTokens" | "agentIdentity" | "personalAccessToken"; +export type AuthMode = "apikey" | "chatgpt" | "chatgptAuthTokens" | "agentIdentity" | "personalAccessToken" | "bedrockApiKey"; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 5526f5c54..9311fe905 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -41,6 +41,11 @@ pub enum AuthMode { #[ts(rename = "personalAccessToken")] #[strum(serialize = "personalAccessToken")] PersonalAccessToken, + /// Amazon Bedrock bearer token managed by Codex. + #[serde(rename = "bedrockApiKey")] + #[ts(rename = "bedrockApiKey")] + #[strum(serialize = "bedrockApiKey")] + BedrockApiKey, } impl AuthMode { @@ -48,7 +53,7 @@ impl AuthMode { pub fn has_chatgpt_account(self) -> bool { match self { Self::Chatgpt | Self::ChatgptAuthTokens | Self::PersonalAccessToken => true, - Self::ApiKey | Self::AgentIdentity => false, + Self::ApiKey | Self::AgentIdentity | Self::BedrockApiKey => false, } } } diff --git a/codex-rs/app-server-transport/src/transport/remote_control/tests.rs b/codex-rs/app-server-transport/src/transport/remote_control/tests.rs index 1e653c296..1d4640485 100644 --- a/codex-rs/app-server-transport/src/transport/remote_control/tests.rs +++ b/codex-rs/app-server-transport/src/transport/remote_control/tests.rs @@ -116,6 +116,7 @@ fn remote_control_auth_dot_json(account_id: Option<&str>) -> AuthDotJson { last_refresh: Some(chrono::Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, } } diff --git a/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs b/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs index e0208b1f1..ef74527e0 100644 --- a/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs +++ b/codex-rs/app-server-transport/src/transport/remote_control/websocket.rs @@ -1842,6 +1842,7 @@ mod tests { last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, } } diff --git a/codex-rs/app-server/tests/common/auth_fixtures.rs b/codex-rs/app-server/tests/common/auth_fixtures.rs index cf78e788d..18cfd7ed6 100644 --- a/codex-rs/app-server/tests/common/auth_fixtures.rs +++ b/codex-rs/app-server/tests/common/auth_fixtures.rs @@ -165,6 +165,7 @@ pub fn write_chatgpt_auth( last_refresh, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth(codex_home, &auth, cli_auth_credentials_store_mode).context("write auth.json") diff --git a/codex-rs/app-server/tests/suite/v2/app_list.rs b/codex-rs/app-server/tests/suite/v2/app_list.rs index ace43d284..fa4c2042a 100644 --- a/codex-rs/app-server/tests/suite/v2/app_list.rs +++ b/codex-rs/app-server/tests/suite/v2/app_list.rs @@ -119,6 +119,7 @@ async fn list_apps_returns_empty_with_api_key_auth() -> Result<()> { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }, AuthCredentialsStoreMode::File, )?; diff --git a/codex-rs/cli/src/doctor.rs b/codex-rs/cli/src/doctor.rs index abb5daf97..0b7e7cfe7 100644 --- a/codex-rs/cli/src/doctor.rs +++ b/codex-rs/cli/src/doctor.rs @@ -1324,6 +1324,7 @@ fn stored_auth_mode(auth: &codex_login::AuthDotJson) -> &'static str { codex_app_server_protocol::AuthMode::ChatgptAuthTokens => "chatgpt_auth_tokens", codex_app_server_protocol::AuthMode::AgentIdentity => "agent_identity", codex_app_server_protocol::AuthMode::PersonalAccessToken => "personal_access_token", + codex_app_server_protocol::AuthMode::BedrockApiKey => "bedrock_api_key", } } @@ -1331,10 +1332,12 @@ fn stored_auth_mode_value(auth: &AuthDotJson) -> codex_app_server_protocol::Auth if let Some(mode) = auth.auth_mode { return mode; } - if auth.openai_api_key.is_some() { - codex_app_server_protocol::AuthMode::ApiKey - } else if auth.personal_access_token.is_some() { + if auth.personal_access_token.is_some() { codex_app_server_protocol::AuthMode::PersonalAccessToken + } else if auth.bedrock_api_key.is_some() { + codex_app_server_protocol::AuthMode::BedrockApiKey + } else if auth.openai_api_key.is_some() { + codex_app_server_protocol::AuthMode::ApiKey } else { codex_app_server_protocol::AuthMode::Chatgpt } @@ -1407,6 +1410,11 @@ fn stored_auth_issues( issues.push("personal access token auth is missing a personal access token"); } } + codex_app_server_protocol::AuthMode::BedrockApiKey => { + if auth.bedrock_api_key.is_none() { + issues.push("Bedrock API key auth is missing a Bedrock API key"); + } + } } issues } @@ -2437,6 +2445,7 @@ fn auth_mode_name(auth: &CodexAuth) -> &'static str { codex_app_server_protocol::AuthMode::ChatgptAuthTokens => "chatgpt_auth_tokens", codex_app_server_protocol::AuthMode::AgentIdentity => "agent_identity", codex_app_server_protocol::AuthMode::PersonalAccessToken => "personal_access_token", + codex_app_server_protocol::AuthMode::BedrockApiKey => "bedrock_api_key", } } @@ -2566,7 +2575,10 @@ fn provider_auth_reachability_mode_from_auth( return ProviderAuthReachabilityMode::Chatgpt; } match stored_auth.map(stored_auth_mode_value) { - Some(codex_app_server_protocol::AuthMode::ApiKey) => ProviderAuthReachabilityMode::ApiKey, + Some( + codex_app_server_protocol::AuthMode::ApiKey + | codex_app_server_protocol::AuthMode::BedrockApiKey, + ) => ProviderAuthReachabilityMode::ApiKey, Some( codex_app_server_protocol::AuthMode::Chatgpt | codex_app_server_protocol::AuthMode::ChatgptAuthTokens @@ -3471,6 +3483,7 @@ mod tests { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; assert_eq!( @@ -3489,6 +3502,7 @@ mod tests { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; assert_eq!( @@ -3509,6 +3523,7 @@ mod tests { last_refresh: None, agent_identity: None, personal_access_token: Some("at-test".to_string()), + bedrock_api_key: None, }; assert_eq!(stored_auth_mode(&auth), "personal_access_token"); @@ -3531,6 +3546,7 @@ mod tests { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; assert_eq!( diff --git a/codex-rs/cli/src/login.rs b/codex-rs/cli/src/login.rs index b3fc1bd54..388a9f452 100644 --- a/codex-rs/cli/src/login.rs +++ b/codex-rs/cli/src/login.rs @@ -395,6 +395,10 @@ pub async fn run_login_status(cli_config_overrides: CliConfigOverrides) -> ! { 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); + } }, Ok(None) => { eprintln!("Not logged in"); diff --git a/codex-rs/core/src/client.rs b/codex-rs/core/src/client.rs index 87381fca7..b143ff23b 100644 --- a/codex-rs/core/src/client.rs +++ b/codex-rs/core/src/client.rs @@ -1985,7 +1985,7 @@ impl AuthRequestTelemetryContext { let auth_telemetry = auth_header_telemetry(api_auth); Self { auth_mode: auth_mode.map(|mode| match mode { - AuthMode::ApiKey => "ApiKey", + AuthMode::ApiKey | AuthMode::BedrockApiKey => "ApiKey", AuthMode::Chatgpt | AuthMode::ChatgptAuthTokens | AuthMode::AgentIdentity diff --git a/codex-rs/login/src/auth/auth_tests.rs b/codex-rs/login/src/auth/auth_tests.rs index e7b5502eb..ff12aaff6 100644 --- a/codex-rs/login/src/auth/auth_tests.rs +++ b/codex-rs/login/src/auth/auth_tests.rs @@ -166,6 +166,7 @@ async fn login_with_access_token_writes_only_personal_access_token() { last_refresh: None, agent_identity: None, personal_access_token: Some("at-login-test".to_string()), + bedrock_api_key: None, } ); assert_eq!(auth.resolved_mode(), AuthMode::PersonalAccessToken); @@ -325,6 +326,7 @@ async fn pro_account_with_no_api_key_uses_chatgpt_auth() { last_refresh: Some(last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }, auth_dot_json ); @@ -367,6 +369,7 @@ fn logout_removes_auth_file() -> Result<(), std::io::Error> { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; super::save_auth(dir.path(), &auth_dot_json, AuthCredentialsStoreMode::File)?; let auth_file = get_auth_file(dir.path()); @@ -1112,6 +1115,7 @@ async fn enforce_login_restrictions_logs_out_for_agent_identity_workspace_mismat last_refresh: None, agent_identity: Some(agent_identity), personal_access_token: None, + bedrock_api_key: None, }, AuthCredentialsStoreMode::File, ) diff --git a/codex-rs/login/src/auth/bedrock_api_key.rs b/codex-rs/login/src/auth/bedrock_api_key.rs new file mode 100644 index 000000000..803b7a01e --- /dev/null +++ b/codex-rs/login/src/auth/bedrock_api_key.rs @@ -0,0 +1,42 @@ +use std::path::Path; + +use codex_config::types::AuthCredentialsStoreMode; +use serde::Deserialize; +use serde::Serialize; + +use super::manager::save_auth; +use super::storage::AuthDotJson; +use codex_app_server_protocol::AuthMode; + +/// Managed Amazon Bedrock API key persisted in `auth.json`. +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] +pub struct BedrockApiKeyAuth { + pub api_key: String, + pub region: String, +} + +/// Writes an `auth.json` that contains only the Amazon Bedrock API key auth. +pub fn login_with_bedrock_api_key( + codex_home: &Path, + api_key: &str, + region: &str, + auth_credentials_store_mode: AuthCredentialsStoreMode, +) -> std::io::Result<()> { + let auth_dot_json = AuthDotJson { + auth_mode: Some(AuthMode::BedrockApiKey), + openai_api_key: None, + tokens: None, + last_refresh: None, + agent_identity: None, + personal_access_token: None, + bedrock_api_key: Some(BedrockApiKeyAuth { + api_key: api_key.to_string(), + region: region.to_string(), + }), + }; + save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode) +} + +#[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 new file mode 100644 index 000000000..372baadec --- /dev/null +++ b/codex-rs/login/src/auth/bedrock_api_key_tests.rs @@ -0,0 +1,162 @@ +use codex_app_server_protocol::AuthMode; +use codex_config::types::AuthCredentialsStoreMode; +use pretty_assertions::assert_eq; +use tempfile::tempdir; + +use super::*; +use crate::auth::AuthManager; +use crate::auth::CodexAuth; +use crate::auth::storage::AuthStorageBackend; +use crate::auth::storage::FileAuthStorage; + +fn api_key_auth() -> AuthDotJson { + AuthDotJson { + auth_mode: Some(AuthMode::ApiKey), + openai_api_key: Some("sk-test-key".to_string()), + tokens: None, + last_refresh: None, + agent_identity: None, + personal_access_token: None, + bedrock_api_key: None, + } +} + +fn bedrock_only_auth() -> AuthDotJson { + AuthDotJson { + auth_mode: None, + openai_api_key: None, + tokens: None, + last_refresh: None, + agent_identity: None, + personal_access_token: None, + bedrock_api_key: Some(bedrock_auth()), + } +} + +fn bedrock_auth() -> BedrockApiKeyAuth { + BedrockApiKeyAuth { + api_key: "bedrock-api-key-test".to_string(), + region: "us-east-1".to_string(), + } +} + +#[tokio::test] +async fn login_with_bedrock_api_key_replaces_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, + )?; + + let auth_manager = AuthManager::new( + codex_home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await; + + let loaded = storage.load()?.expect("auth should be stored"); + let expected = AuthDotJson { + auth_mode: Some(AuthMode::BedrockApiKey), + openai_api_key: None, + tokens: None, + last_refresh: None, + agent_identity: None, + personal_access_token: None, + bedrock_api_key: Some(bedrock_auth()), + }; + assert_eq!(loaded, expected); + assert_eq!(auth_manager.auth_mode(), Some(AuthMode::BedrockApiKey)); + 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, + }), + Some(bedrock_auth()) + ); + Ok(()) +} + +#[tokio::test] +async fn logout_removes_bedrock_auth() -> anyhow::Result<()> { + let codex_home = tempdir()?; + let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); + login_with_bedrock_api_key( + codex_home.path(), + "bedrock-api-key-test", + "us-east-1", + AuthCredentialsStoreMode::File, + )?; + let auth_manager = AuthManager::new( + codex_home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await; + + assert!(auth_manager.logout().await?); + + assert_eq!(storage.load()?, None); + assert_eq!(auth_manager.auth_cached(), None); + Ok(()) +} + +#[tokio::test] +async fn bedrock_only_auth_storage_creates_primary_auth() -> anyhow::Result<()> { + let codex_home = tempdir()?; + let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); + storage.save(&bedrock_only_auth())?; + + let auth_manager = AuthManager::new( + codex_home.path().to_path_buf(), + /*enable_codex_api_key_env*/ false, + AuthCredentialsStoreMode::File, + /*chatgpt_base_url*/ None, + ) + .await; + + assert_eq!(auth_manager.auth_mode(), Some(AuthMode::BedrockApiKey)); + 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, + }), + Some(bedrock_auth()) + ); + Ok(()) +} + +#[tokio::test] +async fn login_with_api_key_clears_bedrock_api_key() -> anyhow::Result<()> { + let codex_home = tempdir()?; + let storage = FileAuthStorage::new(codex_home.path().to_path_buf()); + login_with_bedrock_api_key( + codex_home.path(), + "bedrock-api-key-test", + "us-east-1", + AuthCredentialsStoreMode::File, + )?; + + crate::auth::login_with_api_key( + codex_home.path(), + "sk-test-key", + AuthCredentialsStoreMode::File, + )?; + + assert_eq!(storage.load()?, Some(api_key_auth())); + Ok(()) +} diff --git a/codex-rs/login/src/auth/manager.rs b/codex-rs/login/src/auth/manager.rs index 9e255a99a..383011f24 100644 --- a/codex-rs/login/src/auth/manager.rs +++ b/codex-rs/login/src/auth/manager.rs @@ -29,6 +29,7 @@ use super::access_token::classify_codex_access_token; use super::external_bearer::BearerTokenRefresher; use super::revoke::revoke_auth_tokens; pub use crate::auth::agent_identity::AgentIdentityAuth; +pub use crate::auth::bedrock_api_key::BedrockApiKeyAuth; pub use crate::auth::personal_access_token::PersonalAccessTokenAuth; pub use crate::auth::storage::AgentIdentityAuthRecord; pub use crate::auth::storage::AuthDotJson; @@ -57,12 +58,14 @@ pub enum CodexAuth { ChatgptAuthTokens(ChatgptAuthTokens), AgentIdentity(AgentIdentityAuth), PersonalAccessToken(PersonalAccessTokenAuth), + BedrockApiKey(BedrockApiKeyAuth), } 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(), } } @@ -235,6 +238,14 @@ impl CodexAuth { }; return Self::from_personal_access_token(personal_access_token).await; } + if auth_mode == ApiAuthMode::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)); + } let storage_mode = auth_dot_json.storage_mode(auth_credentials_store_mode); let state = ChatgptAuthState { @@ -255,6 +266,7 @@ impl CodexAuth { ApiAuthMode::PersonalAccessToken => { unreachable!("personal access token mode is handled above") } + ApiAuthMode::BedrockApiKey => unreachable!("bedrock api key mode is handled above"), } } @@ -296,6 +308,7 @@ impl CodexAuth { Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) => AuthMode::Chatgpt, Self::AgentIdentity(_) => AuthMode::AgentIdentity, Self::PersonalAccessToken(_) => AuthMode::PersonalAccessToken, + Self::BedrockApiKey(_) => AuthMode::BedrockApiKey, } } @@ -306,6 +319,7 @@ impl CodexAuth { Self::ChatgptAuthTokens(_) => ApiAuthMode::ChatgptAuthTokens, Self::AgentIdentity(_) => ApiAuthMode::AgentIdentity, Self::PersonalAccessToken(_) => ApiAuthMode::PersonalAccessToken, + Self::BedrockApiKey(_) => ApiAuthMode::BedrockApiKey, } } @@ -346,7 +360,8 @@ impl CodexAuth { Self::Chatgpt(_) | Self::ChatgptAuthTokens(_) | Self::AgentIdentity(_) - | Self::PersonalAccessToken(_) => None, + | Self::PersonalAccessToken(_) + | Self::BedrockApiKey(_) => None, } } @@ -375,6 +390,9 @@ 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", + )), } } @@ -447,7 +465,10 @@ impl CodexAuth { let state = match self { Self::Chatgpt(auth) => &auth.state, Self::ChatgptAuthTokens(auth) => &auth.state, - Self::ApiKey(_) | Self::AgentIdentity(_) | Self::PersonalAccessToken(_) => return None, + Self::ApiKey(_) + | Self::AgentIdentity(_) + | Self::PersonalAccessToken(_) + | Self::BedrockApiKey(_) => return None, }; #[expect(clippy::unwrap_used)] state.auth_dot_json.lock().unwrap().clone() @@ -472,6 +493,7 @@ impl CodexAuth { last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; let client = create_client(); @@ -589,6 +611,7 @@ pub fn login_with_api_key( last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth(codex_home, &auth_dot_json, auth_credentials_store_mode) } @@ -612,6 +635,7 @@ pub async fn login_with_access_token( last_refresh: None, agent_identity: None, personal_access_token: Some(access_token.to_string()), + bedrock_api_key: None, } } CodexAccessToken::AgentIdentityJwt(jwt) => { @@ -627,6 +651,7 @@ pub async fn login_with_access_token( last_refresh: None, agent_identity: Some(jwt.to_string()), personal_access_token: None, + bedrock_api_key: None, } } }; @@ -698,7 +723,8 @@ pub async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result< if let Some(required_method) = config.forced_login_method { let method_violation = match (required_method, auth.auth_mode()) { - (ForcedLoginMethod::Api, AuthMode::ApiKey) => None, + (ForcedLoginMethod::Api, AuthMode::ApiKey) + | (ForcedLoginMethod::Api, AuthMode::BedrockApiKey) => None, (ForcedLoginMethod::Chatgpt, AuthMode::Chatgpt) | (ForcedLoginMethod::Chatgpt, AuthMode::ChatgptAuthTokens) | (ForcedLoginMethod::Chatgpt, AuthMode::AgentIdentity) @@ -710,7 +736,8 @@ pub async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result< "API key login is required, but ChatGPT is currently being used. Logging out." .to_string(), ), - (ForcedLoginMethod::Chatgpt, AuthMode::ApiKey) => Some( + (ForcedLoginMethod::Chatgpt, AuthMode::ApiKey) + | (ForcedLoginMethod::Chatgpt, AuthMode::BedrockApiKey) => Some( "ChatGPT login is required, but an API key is currently being used. Logging out." .to_string(), ), @@ -727,7 +754,9 @@ pub async fn enforce_login_restrictions(config: &AuthConfig) -> std::io::Result< if let Some(expected_account_ids) = config.forced_chatgpt_workspace_id.as_deref() { let chatgpt_account_id = match &auth { - CodexAuth::ApiKey(_) | CodexAuth::PersonalAccessToken(_) => return Ok(()), + CodexAuth::ApiKey(_) + | CodexAuth::PersonalAccessToken(_) + | CodexAuth::BedrockApiKey(_) => return Ok(()), CodexAuth::AgentIdentity(_) => auth.get_account_id(), CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) => { let token_data = match auth.get_token_data() { @@ -1043,6 +1072,7 @@ impl AuthDotJson { last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }) } @@ -1059,13 +1089,16 @@ impl AuthDotJson { Self::from_external_tokens(&external) } - fn resolved_mode(&self) -> ApiAuthMode { + pub(super) fn resolved_mode(&self) -> ApiAuthMode { if let Some(mode) = self.auth_mode { return mode; } if self.personal_access_token.is_some() { return ApiAuthMode::PersonalAccessToken; } + if self.bedrock_api_key.is_some() { + return ApiAuthMode::BedrockApiKey; + } if self.openai_api_key.is_some() { return ApiAuthMode::ApiKey; } @@ -1590,6 +1623,7 @@ impl AuthManager { _ => false, }, (ApiAuthMode::PersonalAccessToken, ApiAuthMode::PersonalAccessToken) => a == b, + (ApiAuthMode::BedrockApiKey, ApiAuthMode::BedrockApiKey) => a == b, _ => false, }, _ => false, @@ -1851,7 +1885,8 @@ impl AuthManager { } CodexAuth::ApiKey(_) | CodexAuth::AgentIdentity(_) - | CodexAuth::PersonalAccessToken(_) => Ok(()), + | CodexAuth::PersonalAccessToken(_) + | CodexAuth::BedrockApiKey(_) => Ok(()), }; if let Err(RefreshTokenError::Permanent(error)) = &result { self.record_permanent_refresh_failure_if_unchanged(&attempted_auth, error); diff --git a/codex-rs/login/src/auth/mod.rs b/codex-rs/login/src/auth/mod.rs index 9bf6c350f..2e18d530f 100644 --- a/codex-rs/login/src/auth/mod.rs +++ b/codex-rs/login/src/auth/mod.rs @@ -1,5 +1,6 @@ mod access_token; mod agent_identity; +mod bedrock_api_key; pub mod default_client; pub mod error; mod personal_access_token; @@ -10,6 +11,8 @@ mod external_bearer; mod manager; mod revoke; +pub use bedrock_api_key::BedrockApiKeyAuth; +pub use bedrock_api_key::login_with_bedrock_api_key; pub use error::RefreshTokenFailedError; pub use error::RefreshTokenFailedReason; pub use manager::*; diff --git a/codex-rs/login/src/auth/storage.rs b/codex-rs/login/src/auth/storage.rs index d0e606a8c..2edb4ca30 100644 --- a/codex-rs/login/src/auth/storage.rs +++ b/codex-rs/login/src/auth/storage.rs @@ -18,6 +18,7 @@ use std::sync::Arc; use std::sync::Mutex; use tracing::warn; +use super::BedrockApiKeyAuth; use crate::token_data::TokenData; use codex_agent_identity::AgentIdentityJwtClaims; use codex_agent_identity::decode_agent_identity_jwt; @@ -48,6 +49,9 @@ pub struct AuthDotJson { #[serde(default, skip_serializing_if = "Option::is_none")] pub personal_access_token: Option, + + #[serde(default, skip_serializing_if = "Option::is_none")] + pub bedrock_api_key: Option, } #[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq)] diff --git a/codex-rs/login/src/auth/storage_tests.rs b/codex-rs/login/src/auth/storage_tests.rs index debe0e18d..c3491a8c4 100644 --- a/codex-rs/login/src/auth/storage_tests.rs +++ b/codex-rs/login/src/auth/storage_tests.rs @@ -20,6 +20,7 @@ async fn file_storage_load_returns_auth_dot_json() -> anyhow::Result<()> { last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; storage @@ -42,6 +43,7 @@ async fn file_storage_save_persists_auth_dot_json() -> anyhow::Result<()> { last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; let file = get_auth_file(codex_home.path()); @@ -76,6 +78,7 @@ async fn file_storage_round_trips_agent_identity_auth() -> anyhow::Result<()> { last_refresh: None, agent_identity: Some(agent_identity), personal_access_token: None, + bedrock_api_key: None, }; storage.save(&auth_dot_json)?; @@ -96,6 +99,7 @@ async fn file_storage_round_trips_personal_access_token_auth() -> anyhow::Result last_refresh: None, agent_identity: None, personal_access_token: Some("at-example".to_string()), + bedrock_api_key: None, }; storage.save(&auth_dot_json)?; @@ -146,6 +150,7 @@ fn file_storage_delete_removes_auth_file() -> anyhow::Result<()> { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; let storage = create_auth_storage(dir.path().to_path_buf(), AuthCredentialsStoreMode::File); storage.save(&auth_dot_json)?; @@ -171,6 +176,7 @@ fn ephemeral_storage_save_load_delete_is_in_memory_only() -> anyhow::Result<()> last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; storage.save(&auth_dot_json)?; @@ -271,6 +277,7 @@ fn auth_with_prefix(prefix: &str) -> AuthDotJson { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, } } @@ -297,6 +304,7 @@ fn keyring_auth_storage_load_returns_deserialized_auth() -> anyhow::Result<()> { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; seed_keyring_with_auth( &mock_keyring, @@ -341,6 +349,7 @@ fn keyring_auth_storage_save_persists_and_removes_fallback_file() -> anyhow::Res last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; storage.save(&auth)?; diff --git a/codex-rs/login/src/lib.rs b/codex-rs/login/src/lib.rs index 990cf8b80..b857592ad 100644 --- a/codex-rs/login/src/lib.rs +++ b/codex-rs/login/src/lib.rs @@ -40,6 +40,7 @@ pub use auth::enforce_login_restrictions; pub use auth::load_auth_dot_json; pub use auth::login_with_access_token; pub use auth::login_with_api_key; +pub use auth::login_with_bedrock_api_key; pub use auth::logout; pub use auth::logout_with_revoke; pub use auth::read_codex_access_token_from_env; diff --git a/codex-rs/login/src/server.rs b/codex-rs/login/src/server.rs index 5a1833b58..a91017bea 100644 --- a/codex-rs/login/src/server.rs +++ b/codex-rs/login/src/server.rs @@ -823,6 +823,7 @@ pub(crate) async fn persist_tokens_async( last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth(&codex_home, &auth, auth_credentials_store_mode)?; Ok::<_, io::Error>((previous_auth, auth)) @@ -1327,6 +1328,7 @@ mod tests { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, } } diff --git a/codex-rs/login/tests/suite/auth_refresh.rs b/codex-rs/login/tests/suite/auth_refresh.rs index b30d738cd..c86338e70 100644 --- a/codex-rs/login/tests/suite/auth_refresh.rs +++ b/codex-rs/login/tests/suite/auth_refresh.rs @@ -56,6 +56,7 @@ async fn refresh_token_succeeds_updates_storage() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -121,6 +122,7 @@ async fn refresh_token_refreshes_when_auth_is_unchanged() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -187,6 +189,7 @@ async fn auth_refreshes_when_access_token_is_near_expiry() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -238,6 +241,7 @@ async fn auth_skips_access_token_outside_refresh_window() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -275,6 +279,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -286,6 +291,7 @@ async fn refresh_token_skips_refresh_when_auth_changed() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -342,6 +348,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -354,6 +361,7 @@ async fn refresh_token_errors_on_account_mismatch() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -414,6 +422,7 @@ async fn returns_fresh_tokens_as_is() -> Result<()> { last_refresh: Some(stale_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -463,6 +472,7 @@ async fn refreshes_token_when_access_token_is_expired() -> Result<()> { last_refresh: Some(fresh_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -514,6 +524,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> { last_refresh: Some(stale_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -526,6 +537,7 @@ async fn auth_reloads_disk_auth_when_cached_auth_is_stale() -> Result<()> { last_refresh: Some(fresh_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -579,6 +591,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul last_refresh: Some(stale_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -591,6 +604,7 @@ async fn auth_reloads_disk_auth_without_calling_expired_refresh_token() -> Resul last_refresh: Some(fresh_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -642,6 +656,7 @@ async fn refresh_token_returns_permanent_error_for_expired_refresh_token() -> Re last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -696,6 +711,7 @@ async fn refresh_token_does_not_retry_after_permanent_failure() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -764,6 +780,7 @@ async fn refresh_token_does_not_retry_after_bad_request_reused_failure() -> Resu last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -832,6 +849,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result< last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -855,6 +873,7 @@ async fn refresh_token_reloads_changed_auth_after_permanent_failure() -> Result< last_refresh: Some(fresh_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -915,6 +934,7 @@ async fn refresh_token_returns_transient_error_on_server_failure() -> Result<()> last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -969,6 +989,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -980,6 +1001,7 @@ async fn unauthorized_recovery_reloads_then_refreshes_tokens() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -1065,6 +1087,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&initial_auth).await?; @@ -1077,6 +1100,7 @@ async fn unauthorized_recovery_errors_on_account_mismatch() -> Result<()> { last_refresh: Some(initial_last_refresh), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth( ctx.codex_home.path(), @@ -1136,6 +1160,7 @@ async fn unauthorized_recovery_requires_chatgpt_auth() -> Result<()> { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; ctx.write_auth(&auth).await?; diff --git a/codex-rs/login/tests/suite/logout.rs b/codex-rs/login/tests/suite/logout.rs index 2dd1bef83..5d7580397 100644 --- a/codex-rs/login/tests/suite/logout.rs +++ b/codex-rs/login/tests/suite/logout.rs @@ -196,6 +196,7 @@ fn chatgpt_auth_with_refresh_token(refresh_token: &str) -> AuthDotJson { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, } } diff --git a/codex-rs/model-provider/src/auth.rs b/codex-rs/model-provider/src/auth.rs index 4f87d46ba..8f5e02b17 100644 --- a/codex-rs/model-provider/src/auth.rs +++ b/codex-rs/model-provider/src/auth.rs @@ -8,11 +8,15 @@ use codex_api::SharedAuthProvider; use codex_login::AuthManager; use codex_login::CodexAuth; use codex_model_provider_info::ModelProviderInfo; +use codex_protocol::error::CodexErr; 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)] struct AgentIdentityAuthProvider { auth: codex_login::auth::AgentIdentityAuth, @@ -79,6 +83,12 @@ 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)); } @@ -109,6 +119,7 @@ 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(_) @@ -122,8 +133,10 @@ pub fn auth_provider_from_auth(auth: &CodexAuth) -> SharedAuthProvider { #[cfg(test)] mod tests { + use codex_login::auth::BedrockApiKeyAuth; use codex_model_provider_info::WireApi; use codex_model_provider_info::create_oss_provider_with_base_url; + use pretty_assertions::assert_eq; use super::*; @@ -135,4 +148,21 @@ 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"), + } + } } diff --git a/codex-rs/model-provider/src/provider.rs b/codex-rs/model-provider/src/provider.rs index e78619243..95427465e 100644 --- a/codex-rs/model-provider/src/provider.rs +++ b/codex-rs/model-provider/src/provider.rs @@ -51,6 +51,7 @@ pub struct ProviderAccountState { #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ProviderAccountError { MissingChatgptAccountDetails, + UnsupportedBedrockApiKeyAuth, } impl fmt::Display for ProviderAccountError { @@ -62,6 +63,12 @@ impl fmt::Display for ProviderAccountError { "email and plan type are required for chatgpt authentication" ) } + Self::UnsupportedBedrockApiKeyAuth => { + write!( + f, + "Bedrock API key auth is only supported by the Amazon Bedrock model provider" + ) + } } } } @@ -232,6 +239,9 @@ impl ModelProvider for ConfiguredModelProvider { }) .map(|auth| match &auth { CodexAuth::ApiKey(_) => Ok(ProviderAccount::ApiKey), + CodexAuth::BedrockApiKey(_) => { + Err(ProviderAccountError::UnsupportedBedrockApiKeyAuth) + } CodexAuth::Chatgpt(_) | CodexAuth::ChatgptAuthTokens(_) | CodexAuth::AgentIdentity(_) @@ -287,6 +297,7 @@ impl ModelProvider for ConfiguredModelProvider { mod tests { use std::num::NonZeroU64; + use codex_login::auth::BedrockApiKeyAuth; use codex_model_provider_info::ModelProviderAwsAuthInfo; use codex_model_provider_info::WireApi; use codex_models_manager::manager::RefreshStrategy; @@ -374,6 +385,13 @@ 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(), + }) + } + #[test] fn configured_provider_uses_default_capabilities() { let provider = create_model_provider( @@ -491,6 +509,19 @@ 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( diff --git a/codex-rs/models-manager/src/manager_tests.rs b/codex-rs/models-manager/src/manager_tests.rs index 2e39ef316..6f3a6a27f 100644 --- a/codex-rs/models-manager/src/manager_tests.rs +++ b/codex-rs/models-manager/src/manager_tests.rs @@ -212,6 +212,7 @@ c2ln", last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; std::fs::create_dir_all(codex_home).expect("codex home should be created"); std::fs::write( diff --git a/codex-rs/otel/src/lib.rs b/codex-rs/otel/src/lib.rs index 586dfa15a..fe7a171ce 100644 --- a/codex-rs/otel/src/lib.rs +++ b/codex-rs/otel/src/lib.rs @@ -54,7 +54,8 @@ pub enum TelemetryAuthMode { impl From for TelemetryAuthMode { fn from(mode: codex_app_server_protocol::AuthMode) -> Self { match mode { - codex_app_server_protocol::AuthMode::ApiKey => Self::ApiKey, + codex_app_server_protocol::AuthMode::ApiKey + | codex_app_server_protocol::AuthMode::BedrockApiKey => Self::ApiKey, codex_app_server_protocol::AuthMode::Chatgpt | codex_app_server_protocol::AuthMode::ChatgptAuthTokens | codex_app_server_protocol::AuthMode::AgentIdentity diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 2ef681091..47184cfee 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -1253,6 +1253,7 @@ pub(crate) fn status_account_display_from_auth_mode( email: None, plan: plan_type.map(plan_type_display_name), }), + Some(AuthMode::BedrockApiKey) => None, None => None, } } diff --git a/codex-rs/tui/src/local_chatgpt_auth.rs b/codex-rs/tui/src/local_chatgpt_auth.rs index d7ca01f61..8a9ba58fc 100644 --- a/codex-rs/tui/src/local_chatgpt_auth.rs +++ b/codex-rs/tui/src/local_chatgpt_auth.rs @@ -110,6 +110,7 @@ mod tests { last_refresh: Some(Utc::now()), agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }; save_auth(codex_home, &auth, AuthCredentialsStoreMode::File) .expect("chatgpt auth should save"); @@ -158,6 +159,7 @@ mod tests { last_refresh: None, agent_identity: None, personal_access_token: None, + bedrock_api_key: None, }, AuthCredentialsStoreMode::File, )