diff --git a/codex-rs/app-server-client/src/lib.rs b/codex-rs/app-server-client/src/lib.rs index c75915629c..e0fa45ef0a 100644 --- a/codex-rs/app-server-client/src/lib.rs +++ b/codex-rs/app-server-client/src/lib.rs @@ -1447,6 +1447,7 @@ mod tests { result: serde_json::to_value(GetAccountResponse { account: None, requires_openai_auth: false, + codex_managed_auth: false, }) .expect("response should serialize"), }), @@ -1500,6 +1501,7 @@ mod tests { result: serde_json::to_value(GetAccountResponse { account: None, requires_openai_auth: false, + codex_managed_auth: false, }) .expect("response should serialize"), }), @@ -1575,6 +1577,7 @@ mod tests { GetAccountResponse { account: None, requires_openai_auth: false, + codex_managed_auth: false, } ); @@ -1676,6 +1679,7 @@ mod tests { result: serde_json::to_value(GetAccountResponse { account: None, requires_openai_auth: false, + codex_managed_auth: false, }) .expect("response should serialize"), }), @@ -1729,6 +1733,7 @@ mod tests { GetAccountResponse { account: None, requires_openai_auth: false, + codex_managed_auth: false, } ); diff --git a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json index 961e24c2c2..f2ab783342 100644 --- a/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json +++ b/codex-rs/app-server-protocol/schema/json/PermissionsRequestApprovalParams.json @@ -288,8 +288,8 @@ "environmentId": { "default": null, "type": [ - "null", - "string" + "string", + "null" ] }, "itemId": { @@ -326,4 +326,4 @@ ], "title": "PermissionsRequestApprovalParams", "type": "object" -} +} \ No newline at end of file diff --git a/codex-rs/app-server-protocol/schema/json/ServerRequest.json b/codex-rs/app-server-protocol/schema/json/ServerRequest.json index 13bac2e118..dbfca64f4c 100644 --- a/codex-rs/app-server-protocol/schema/json/ServerRequest.json +++ b/codex-rs/app-server-protocol/schema/json/ServerRequest.json @@ -1593,8 +1593,8 @@ "environmentId": { "default": null, "type": [ - "null", - "string" + "string", + "null" ] }, "itemId": { @@ -2005,4 +2005,4 @@ } ], "title": "ServerRequest" -} +} \ No newline at end of file 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 6fcec76b1d..cf94bc7dfb 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 @@ -3786,8 +3786,8 @@ "environmentId": { "default": null, "type": [ - "null", - "string" + "string", + "null" ] }, "itemId": { @@ -5684,6 +5684,9 @@ }, { "properties": { + "authSource": { + "$ref": "#/definitions/v2/AmazonBedrockAuthSource" + }, "type": { "enum": [ "amazonBedrock" @@ -5693,6 +5696,7 @@ } }, "required": [ + "authSource", "type" ], "title": "AmazonBedrockAccount", @@ -5903,6 +5907,13 @@ "AgentPath": { "type": "string" }, + "AmazonBedrockAuthSource": { + "enum": [ + "codexManaged", + "awsManaged" + ], + "type": "string" + }, "AnalyticsConfig": { "additionalProperties": true, "properties": { @@ -7702,6 +7713,31 @@ "title": "ProjectConfigLayerSource", "type": "object" }, + { + "description": "Local Codex-owned provider state, such as a provider activated by managed auth.", + "properties": { + "file": { + "$ref": "#/definitions/v2/AbsolutePathBuf" + }, + "provider": { + "type": "string" + }, + "type": { + "enum": [ + "providerState" + ], + "title": "ProviderStateConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "provider", + "type" + ], + "title": "ProviderStateConfigLayerSource", + "type": "object" + }, { "description": "Session-layer overrides supplied via `-c`/`--config`.", "properties": { @@ -19279,4 +19315,4 @@ }, "title": "CodexAppServerProtocol", "type": "object" -} +} \ No newline at end of file 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 4d9beb83ba..afda77f37c 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 @@ -49,6 +49,9 @@ }, { "properties": { + "authSource": { + "$ref": "#/definitions/AmazonBedrockAuthSource" + }, "type": { "enum": [ "amazonBedrock" @@ -58,6 +61,7 @@ } }, "required": [ + "authSource", "type" ], "title": "AmazonBedrockAccount", @@ -268,6 +272,13 @@ "AgentPath": { "type": "string" }, + "AmazonBedrockAuthSource": { + "enum": [ + "codexManaged", + "awsManaged" + ], + "type": "string" + }, "AnalyticsConfig": { "additionalProperties": true, "properties": { @@ -4064,6 +4075,31 @@ "title": "ProjectConfigLayerSource", "type": "object" }, + { + "description": "Local Codex-owned provider state, such as a provider activated by managed auth.", + "properties": { + "file": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "provider": { + "type": "string" + }, + "type": { + "enum": [ + "providerState" + ], + "title": "ProviderStateConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "provider", + "type" + ], + "title": "ProviderStateConfigLayerSource", + "type": "object" + }, { "description": "Session-layer overrides supplied via `-c`/`--config`.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json index 09cf897898..7e7b580d15 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigReadResponse.json @@ -589,6 +589,31 @@ "title": "ProjectConfigLayerSource", "type": "object" }, + { + "description": "Local Codex-owned provider state, such as a provider activated by managed auth.", + "properties": { + "file": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "provider": { + "type": "string" + }, + "type": { + "enum": [ + "providerState" + ], + "title": "ProviderStateConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "provider", + "type" + ], + "title": "ProviderStateConfigLayerSource", + "type": "object" + }, { "description": "Session-layer overrides supplied via `-c`/`--config`.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json index d1a2254a92..2105868fe6 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigWriteResponse.json @@ -154,6 +154,31 @@ "title": "ProjectConfigLayerSource", "type": "object" }, + { + "description": "Local Codex-owned provider state, such as a provider activated by managed auth.", + "properties": { + "file": { + "$ref": "#/definitions/AbsolutePathBuf" + }, + "provider": { + "type": "string" + }, + "type": { + "enum": [ + "providerState" + ], + "title": "ProviderStateConfigLayerSourceType", + "type": "string" + } + }, + "required": [ + "file", + "provider", + "type" + ], + "title": "ProviderStateConfigLayerSource", + "type": "object" + }, { "description": "Session-layer overrides supplied via `-c`/`--config`.", "properties": { diff --git a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json index ec333708b7..960a64b674 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/GetAccountResponse.json @@ -45,6 +45,9 @@ }, { "properties": { + "authSource": { + "$ref": "#/definitions/AmazonBedrockAuthSource" + }, "type": { "enum": [ "amazonBedrock" @@ -54,6 +57,7 @@ } }, "required": [ + "authSource", "type" ], "title": "AmazonBedrockAccount", @@ -61,6 +65,13 @@ } ] }, + "AmazonBedrockAuthSource": { + "enum": [ + "codexManaged", + "awsManaged" + ], + "type": "string" + }, "PlanType": { "enum": [ "free", diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts b/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts index 4c3a58e8d6..28b4b38329 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts @@ -2,5 +2,6 @@ // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. import type { PlanType } from "../PlanType"; +import type { AmazonBedrockAuthSource } from "./AmazonBedrockAuthSource"; -export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, } | { "type": "amazonBedrock", }; +export type Account = { "type": "apiKey", } | { "type": "chatgpt", email: string, planType: PlanType, } | { "type": "amazonBedrock", authSource: AmazonBedrockAuthSource, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/AmazonBedrockAuthSource.ts b/codex-rs/app-server-protocol/schema/typescript/v2/AmazonBedrockAuthSource.ts new file mode 100644 index 0000000000..07fdf6ee01 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/AmazonBedrockAuthSource.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type AmazonBedrockAuthSource = "codexManaged" | "awsManaged"; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts index 963c331b5d..0fb787c792 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/ConfigLayerSource.ts @@ -28,4 +28,4 @@ file: AbsolutePathBuf, * Name of the selected profile-v2 config layered on top of the base * user config, when this layer represents one. */ -profile: string | null, } | { "type": "project", dotCodexFolder: AbsolutePathBuf, } | { "type": "sessionFlags" } | { "type": "legacyManagedConfigTomlFromFile", file: AbsolutePathBuf, } | { "type": "legacyManagedConfigTomlFromMdm" }; +profile: string | null, } | { "type": "project", dotCodexFolder: AbsolutePathBuf, } | { "type": "providerState", provider: string, file: AbsolutePathBuf, } | { "type": "sessionFlags" } | { "type": "legacyManagedConfigTomlFromFile", file: AbsolutePathBuf, } | { "type": "legacyManagedConfigTomlFromMdm" }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 48e1f9580b..5a7f176d36 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -13,6 +13,7 @@ export type { AdditionalFileSystemPermissions } from "./AdditionalFileSystemPerm export type { AdditionalNetworkPermissions } from "./AdditionalNetworkPermissions"; export type { AdditionalPermissionProfile } from "./AdditionalPermissionProfile"; export type { AgentMessageDeltaNotification } from "./AgentMessageDeltaNotification"; +export type { AmazonBedrockAuthSource } from "./AmazonBedrockAuthSource"; export type { AnalyticsConfig } from "./AnalyticsConfig"; export type { AppBranding } from "./AppBranding"; export type { AppInfo } from "./AppInfo"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/account.rs b/codex-rs/app-server-protocol/src/protocol/v2/account.rs index e7bf12041a..e0c039d2e7 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/account.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/account.rs @@ -241,6 +241,7 @@ pub struct GetAccountParams { pub struct GetAccountResponse { pub account: Option, pub requires_openai_auth: bool, + pub codex_managed_auth: bool, } #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)] diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config.rs b/codex-rs/app-server-protocol/src/protocol/v2/config.rs index f40192ddc6..c0487e4a55 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -80,6 +80,15 @@ pub enum ConfigLayerSource { dot_codex_folder: AbsolutePathBuf, }, + /// Local Codex-owned provider state, such as a provider activated by + /// managed auth. + #[serde(rename_all = "camelCase")] + #[ts(rename_all = "camelCase")] + ProviderState { + provider: String, + file: AbsolutePathBuf, + }, + /// Session-layer overrides supplied via `-c`/`--config`. SessionFlags, @@ -112,6 +121,7 @@ impl ConfigLayerSource { } } ConfigLayerSource::Project { .. } => 25, + ConfigLayerSource::ProviderState { .. } => 28, ConfigLayerSource::SessionFlags => 30, ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => 40, ConfigLayerSource::LegacyManagedConfigTomlFromMdm => 50, diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index fdc34e524c..37a9e290eb 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1773,7 +1773,7 @@ Codex supports these authentication modes. The current mode is surfaced in `acco - `account/login/start` — begin login (`apiKey`, `chatgpt`, `chatgptDeviceCode`). - `account/login/completed` (notify) — emitted when a login attempt finishes (success or error). - `account/login/cancel` — cancel a pending managed ChatGPT login by `loginId`. -- `account/logout` — sign out; triggers `account/updated`. +- `account/logout` — sign out; triggers `account/updated` when Codex-managed auth changes. - `account/updated` (notify) — emitted whenever auth mode changes (`authMode`: `apikey`, `chatgpt`, or `null`) and includes the current ChatGPT `planType` when available. - `account/rateLimits/read` — fetch ChatGPT rate limits and an optional effective monthly credit limit; updates arrive via `account/rateLimits/updated` (notify). - `account/rateLimits/updated` (notify) — emitted whenever a user's ChatGPT rate limits change. This is a sparse rolling update; merge available values into the most recent `account/rateLimits/read` response or refetch that snapshot. @@ -1792,16 +1792,17 @@ Request: Response examples: ```json -{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false } } // No OpenAI auth needed (e.g., OSS/local models) -{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true } } // OpenAI auth required (typical for OpenAI-hosted models) -{ "id": 1, "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true } } -{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "requiresOpenaiAuth": true } } +{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": false, "codexManagedAuth": false } } // No OpenAI auth needed (e.g., OSS/local models) +{ "id": 1, "result": { "account": null, "requiresOpenaiAuth": true, "codexManagedAuth": false } } // OpenAI auth required (typical for OpenAI-hosted models) +{ "id": 1, "result": { "account": { "type": "apiKey" }, "requiresOpenaiAuth": true, "codexManagedAuth": true } } +{ "id": 1, "result": { "account": { "type": "chatgpt", "email": "user@example.com", "planType": "pro" }, "requiresOpenaiAuth": true, "codexManagedAuth": true } } ``` Field notes: - `refreshToken` (bool): set `true` to force a token refresh. - `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials. +- `codexManagedAuth` is `true` when `account/logout` can clear Codex-owned persisted auth for the current account. When `false`, logout is a successful no-op for auth managed outside Codex. ### 2) Log in with an API key diff --git a/codex-rs/app-server/src/config_manager_service.rs b/codex-rs/app-server/src/config_manager_service.rs index 4b42c28aa9..920001bc80 100644 --- a/codex-rs/app-server/src/config_manager_service.rs +++ b/codex-rs/app-server/src/config_manager_service.rs @@ -631,6 +631,12 @@ fn override_message(layer: &ConfigLayerSource) -> String { "Overridden by project config: {}/{CONFIG_TOML_FILE}", dot_codex_folder.display(), ), + ConfigLayerSource::ProviderState { provider, file } => { + format!( + "Overridden by provider state ({provider}): {}", + file.display() + ) + } ConfigLayerSource::SessionFlags => "Overridden by session flags".to_string(), ConfigLayerSource::User { file, .. } => { format!("Overridden by user config: {}", file.display()) diff --git a/codex-rs/app-server/src/request_processors.rs b/codex-rs/app-server/src/request_processors.rs index 01b1385000..e07357811d 100644 --- a/codex-rs/app-server/src/request_processors.rs +++ b/codex-rs/app-server/src/request_processors.rs @@ -47,8 +47,6 @@ use codex_app_server_protocol::CommandExecParams; use codex_app_server_protocol::CommandExecResizeParams; use codex_app_server_protocol::CommandExecTerminateParams; use codex_app_server_protocol::CommandExecWriteParams; -use codex_app_server_protocol::ConfigBatchWriteParams; -use codex_app_server_protocol::ConfigEdit as ConfigWriteEdit; use codex_app_server_protocol::ConfigWarningNotification; use codex_app_server_protocol::ConversationGitInfo; use codex_app_server_protocol::ConversationSummary; @@ -102,7 +100,6 @@ use codex_app_server_protocol::McpServerStatusDetail; use codex_app_server_protocol::McpServerToolCallParams; use codex_app_server_protocol::McpServerToolCallResponse; use codex_app_server_protocol::MemoryResetResponse; -use codex_app_server_protocol::MergeStrategy; use codex_app_server_protocol::MockExperimentalMethodParams; use codex_app_server_protocol::MockExperimentalMethodResponse; use codex_app_server_protocol::ModelListParams; @@ -365,6 +362,7 @@ use codex_model_provider::is_supported_amazon_bedrock_region; use codex_model_provider::save_amazon_bedrock_auth; use codex_models_manager::collaboration_mode_presets::builtin_collaboration_mode_presets; use codex_protocol::ThreadId; +use codex_protocol::account::ProviderAccount; use codex_protocol::config_types::CollaborationMode; use codex_protocol::config_types::ForcedLoginMethod; use codex_protocol::config_types::Personality; 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 0fa7efe560..0e0a411747 100644 --- a/codex-rs/app-server/src/request_processors/account_processor.rs +++ b/codex-rs/app-server/src/request_processors/account_processor.rs @@ -331,31 +331,6 @@ impl AccountRequestProcessor { save_amazon_bedrock_auth(&self.config.codex_home, key, region) .map_err(|err| internal_error(format!("failed to save Amazon Bedrock auth: {err}")))?; - self.config_manager - .batch_write(ConfigBatchWriteParams { - edits: vec![ - ConfigWriteEdit { - key_path: "model_provider".to_string(), - value: serde_json::json!(AMAZON_BEDROCK_PROVIDER_ID), - merge_strategy: MergeStrategy::Upsert, - }, - ConfigWriteEdit { - key_path: format!( - "model_providers.{AMAZON_BEDROCK_PROVIDER_ID}.aws.region" - ), - value: serde_json::json!(region), - merge_strategy: MergeStrategy::Upsert, - }, - ], - 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) @@ -796,54 +771,63 @@ impl AccountRequestProcessor { } } - match self.auth_manager.logout_with_revoke().await { - Ok(_) => {} - Err(err) => { - return Err(internal_error(format!("logout failed: {err}"))); + 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 { + let provider = create_model_provider( + current_config.model_provider.clone(), + Some(self.auth_manager.clone()), + Some(current_config.codex_home.to_path_buf()), + ); + let account_state = provider.account_state().map_err(|err| match err { + ProviderAccountError::MissingChatgptAccountDetails => { + invalid_request("email and plan type are required for chatgpt authentication") + } + })?; + let codex_managed_auth = account_state.codex_managed_auth; + let Some(ProviderAccount::AmazonBedrock) = account_state.account else { + return Err(invalid_request( + "active Amazon Bedrock provider did not report an Amazon Bedrock account", + )); + }; + if !codex_managed_auth { + return Ok(None); } - } - 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 + let next_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}")) - })?; + 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, + &self.thread_manager, + self.auth_manager.auth_cached(), + ) + .await; + return Ok(None); + } - 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(), - ); + match self.auth_manager.logout_with_revoke().await { + Ok(_) => {} + Err(err) => { + return Err(internal_error(format!("logout failed: {err}"))); } } @@ -997,6 +981,7 @@ impl AccountRequestProcessor { Ok(GetAccountResponse { account, requires_openai_auth: account_state.requires_openai_auth, + codex_managed_auth: account_state.codex_managed_auth, }) } diff --git a/codex-rs/app-server/tests/common/test_app_server.rs b/codex-rs/app-server/tests/common/test_app_server.rs index 88378322a8..97e428130d 100644 --- a/codex-rs/app-server/tests/common/test_app_server.rs +++ b/codex-rs/app-server/tests/common/test_app_server.rs @@ -1180,6 +1180,20 @@ impl TestAppServer { self.send_request("account/login/start", Some(params)).await } + /// Send an `account/login/start` JSON-RPC request for Amazon Bedrock login. + pub async fn send_login_account_amazon_bedrock_request( + &mut self, + key: &str, + region: &str, + ) -> anyhow::Result { + let params = serde_json::json!({ + "type": "amazonBedrock", + "key": key, + "region": region, + }); + self.send_request("account/login/start", Some(params)).await + } + /// Send an `account/login/cancel` JSON-RPC request. pub async fn send_cancel_login_account_request( &mut self, diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index ce15895b61..52062b5c35 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -250,26 +250,34 @@ async fn logout_account_removes_auth_and_notifies() -> Result<()> { } #[tokio::test] -async fn logout_account_removes_bedrock_model_provider_after_managed_auth_removed() -> Result<()> { +async fn logout_account_removes_managed_bedrock_provider_state() -> 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?; + let mut mcp = + TestAppServer::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + let account_id = mcp + .send_get_account_request(GetAccountParams { + refresh_token: false, + }) + .await?; + let account_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(account_id)), + ) + .await??; + let account: GetAccountResponse = to_response(account_resp)?; + assert_eq!(account.account, Some(Account::AmazonBedrock {})); + assert!(account.codex_managed_auth); + let id = mcp.send_logout_account_request().await?; let resp: JSONRPCResponse = timeout( DEFAULT_READ_TIMEOUT, @@ -289,22 +297,14 @@ region = "us-west-2" ); 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" + config.contains("model_provider = \"mock_provider\""), + "managed Bedrock login should not mutate user model provider config" ); Ok(()) } #[tokio::test] -async fn logout_account_keeps_bedrock_model_provider_without_managed_auth() -> Result<()> { +async fn logout_account_noops_for_aws_managed_bedrock_auth() -> Result<()> { let codex_home = TempDir::new()?; create_config_toml( codex_home.path(), @@ -320,7 +320,8 @@ region = "us-west-2" }, )?; - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + let mut mcp = + TestAppServer::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?; @@ -339,6 +340,47 @@ region = "us-west-2" Ok(()) } +#[tokio::test] +async fn login_account_amazon_bedrock_activates_provider_without_user_config_write() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml(codex_home.path(), CreateConfigTomlParams::default())?; + + let mut mcp = TestAppServer::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let login_id = mcp + .send_login_account_amazon_bedrock_request("bedrock-key", "us-west-2") + .await?; + let login_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(login_id)), + ) + .await??; + let login: LoginAccountResponse = to_response(login_resp)?; + assert_eq!(login, LoginAccountResponse::AmazonBedrock {}); + + let get_id = mcp + .send_get_account_request(GetAccountParams { + refresh_token: false, + }) + .await?; + let get_resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(get_id)), + ) + .await??; + let account: GetAccountResponse = to_response(get_resp)?; + assert_eq!(account.account, Some(Account::AmazonBedrock {})); + assert!(account.codex_managed_auth); + + let config = std::fs::read_to_string(codex_home.path().join("config.toml"))?; + assert!( + config.contains("model_provider = \"mock_provider\""), + "Amazon Bedrock login should not mutate user model provider config" + ); + Ok(()) +} + #[tokio::test] async fn set_auth_token_updates_account_and_notifies() -> Result<()> { let codex_home = TempDir::new()?; @@ -410,6 +452,7 @@ async fn set_auth_token_updates_account_and_notifies() -> Result<()> { plan_type: AccountPlanType::Pro, }), requires_openai_auth: true, + codex_managed_auth: true, } ); @@ -478,6 +521,7 @@ async fn account_read_refresh_token_is_noop_in_external_mode() -> Result<()> { plan_type: AccountPlanType::Pro, }), requires_openai_auth: true, + codex_managed_auth: true, } ); @@ -1688,6 +1732,7 @@ async fn get_account_with_api_key() -> Result<()> { let expected = GetAccountResponse { account: Some(Account::ApiKey {}), requires_openai_auth: true, + codex_managed_auth: true, }; assert_eq!(received, expected); Ok(()) @@ -1722,6 +1767,7 @@ async fn get_account_when_auth_not_required() -> Result<()> { let expected = GetAccountResponse { account: None, requires_openai_auth: false, + codex_managed_auth: false, }; assert_eq!(received, expected); Ok(()) @@ -1763,6 +1809,7 @@ region = "us-west-2" let expected = GetAccountResponse { account: Some(Account::AmazonBedrock {}), requires_openai_auth: false, + codex_managed_auth: false, }; assert_eq!(received, expected); Ok(()) @@ -1808,6 +1855,7 @@ async fn get_account_with_chatgpt() -> Result<()> { plan_type: AccountPlanType::Pro, }), requires_openai_auth: true, + codex_managed_auth: true, }; assert_eq!(received, expected); Ok(()) @@ -1891,6 +1939,7 @@ async fn get_account_omits_chatgpt_after_permanent_refresh_failure() -> Result<( GetAccountResponse { account: None, requires_openai_auth: true, + codex_managed_auth: false, } ); server.verify().await; @@ -1935,6 +1984,7 @@ async fn get_account_with_chatgpt_missing_plan_claim_returns_unknown() -> Result plan_type: AccountPlanType::Unknown, }), requires_openai_auth: true, + codex_managed_auth: true, }; assert_eq!(received, expected); Ok(()) diff --git a/codex-rs/app-server/tests/suite/v2/image_generation.rs b/codex-rs/app-server/tests/suite/v2/image_generation.rs index 4752f1497e..e1f4cfbc9a 100644 --- a/codex-rs/app-server/tests/suite/v2/image_generation.rs +++ b/codex-rs/app-server/tests/suite/v2/image_generation.rs @@ -4,7 +4,7 @@ use std::time::Duration; use anyhow::Context; use anyhow::Result; use app_test_support::ChatGptAuthFixture; -use app_test_support::McpProcess; +use app_test_support::TestAppServer; use app_test_support::to_response; use app_test_support::write_chatgpt_auth; use codex_app_server_protocol::ItemCompletedNotification; @@ -76,7 +76,8 @@ async fn standalone_image_generation_persists_image_and_returns_it_to_model() -> AuthCredentialsStoreMode::File, )?; - let mut mcp = McpProcess::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; + let mut mcp = + TestAppServer::new_with_env(codex_home.path(), &[("OPENAI_API_KEY", None)]).await?; timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; let thread_req = mcp @@ -157,7 +158,7 @@ async fn standalone_image_generation_persists_image_and_returns_it_to_model() -> } async fn wait_for_image_generation_completed( - mcp: &mut McpProcess, + mcp: &mut TestAppServer, ) -> Result { loop { let notification = mcp diff --git a/codex-rs/config/src/config_layer_source.rs b/codex-rs/config/src/config_layer_source.rs index 7bff779b19..a3c1db1f0d 100644 --- a/codex-rs/config/src/config_layer_source.rs +++ b/codex-rs/config/src/config_layer_source.rs @@ -20,6 +20,9 @@ pub fn format_config_layer_source(source: &ConfigLayerSource, config_toml_file: dot_codex_folder.as_path().display() ) } + ConfigLayerSource::ProviderState { provider, file } => { + format!("provider state ({provider}, {})", file.as_path().display()) + } ConfigLayerSource::SessionFlags => "session-flags".to_string(), ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => { format!("legacy managed_config.toml ({})", file.as_path().display()) diff --git a/codex-rs/config/src/diagnostics.rs b/codex-rs/config/src/diagnostics.rs index ca7df712d6..080258cefd 100644 --- a/codex-rs/config/src/diagnostics.rs +++ b/codex-rs/config/src/diagnostics.rs @@ -253,6 +253,7 @@ fn config_path_for_layer(layer: &ConfigLayerEntry, config_toml_file: &str) -> Op ConfigLayerSource::Project { dot_codex_folder } => { Some(dot_codex_folder.as_path().join(config_toml_file)) } + ConfigLayerSource::ProviderState { file, .. } => Some(file.to_path_buf()), ConfigLayerSource::LegacyManagedConfigTomlFromFile { file } => Some(file.to_path_buf()), ConfigLayerSource::Mdm { .. } | ConfigLayerSource::EnterpriseManaged { .. } diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index 0c79ffacff..5f6405e4ac 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -19,6 +19,7 @@ pub mod permissions_toml; mod plugin_edit; pub mod profile_toml; mod project_root_markers; +mod provider_state; mod requirements_exec_policy; mod requirements_layers; pub mod schema; diff --git a/codex-rs/config/src/loader/mod.rs b/codex-rs/config/src/loader/mod.rs index bc0fb482e4..6936f40468 100644 --- a/codex-rs/config/src/loader/mod.rs +++ b/codex-rs/config/src/loader/mod.rs @@ -22,6 +22,7 @@ use crate::merge::merge_toml_values; use crate::overrides::build_cli_overrides_layer; use crate::project_root_markers::default_project_root_markers; use crate::project_root_markers::project_root_markers_from_config; +use crate::provider_state::load_provider_state_layer; use crate::state::ConfigLayerEntry; use crate::state::ConfigLayerStack; use crate::state::ConfigLoadOptions; @@ -348,6 +349,10 @@ pub async fn load_config_layers_state( startup_warnings = Some(project_layers.startup_warnings); } + if let Some(provider_state_layer) = load_provider_state_layer(fs, codex_home).await? { + layers.push(provider_state_layer); + } + // Add a layer for runtime overrides from the CLI or UI, if any exist. if let Some(cli_overrides_layer) = cli_overrides_layer { layers.push(ConfigLayerEntry::new( diff --git a/codex-rs/config/src/provider_state.rs b/codex-rs/config/src/provider_state.rs new file mode 100644 index 0000000000..6d014344a2 --- /dev/null +++ b/codex-rs/config/src/provider_state.rs @@ -0,0 +1,73 @@ +use codex_app_server_protocol::ConfigLayerSource; +use codex_file_system::ExecutorFileSystem; +use codex_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID; +use codex_utils_absolute_path::AbsolutePathBuf; +use serde::Deserialize; +use std::io; +use std::path::Path; +use toml::Value as TomlValue; + +use crate::ConfigLayerEntry; + +#[derive(Deserialize)] +#[serde(rename_all = "camelCase")] +struct AmazonBedrockAuthState { + #[serde(default = "default_activates_provider")] + activates_provider: bool, +} + +pub(crate) async fn load_provider_state_layer( + fs: &dyn ExecutorFileSystem, + codex_home: &Path, +) -> io::Result> { + let auth_file = amazon_bedrock_auth_file(codex_home); + let contents = match fs.read_file_text(&auth_file, /*sandbox*/ None).await { + Ok(contents) => contents, + Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None), + Err(err) => { + return Err(io::Error::new( + err.kind(), + format!( + "Failed to read provider state file {}: {err}", + auth_file.as_path().display() + ), + )); + } + }; + let state: AmazonBedrockAuthState = serde_json::from_str(&contents).map_err(|err| { + io::Error::new( + io::ErrorKind::InvalidData, + format!( + "Failed to parse provider state file {}: {err}", + auth_file.as_path().display() + ), + ) + })?; + if !state.activates_provider { + return Ok(None); + } + + let mut root = toml::map::Map::new(); + root.insert( + "model_provider".to_string(), + TomlValue::String(AMAZON_BEDROCK_PROVIDER_ID.to_string()), + ); + Ok(Some(ConfigLayerEntry::new( + ConfigLayerSource::ProviderState { + provider: AMAZON_BEDROCK_PROVIDER_ID.to_string(), + file: auth_file, + }, + TomlValue::Table(root), + ))) +} + +pub(crate) fn amazon_bedrock_auth_file(codex_home: &Path) -> AbsolutePathBuf { + AbsolutePathBuf::resolve_path_against_base( + "model-providers/amazon-bedrock/auth.json", + codex_home, + ) +} + +fn default_activates_provider() -> bool { + true +} diff --git a/codex-rs/config/src/state.rs b/codex-rs/config/src/state.rs index d5f2b050c9..e6fb68a2a7 100644 --- a/codex-rs/config/src/state.rs +++ b/codex-rs/config/src/state.rs @@ -207,6 +207,7 @@ impl ConfigLayerEntry { ConfigLayerSource::EnterpriseManaged { .. } => None, ConfigLayerSource::User { file, .. } => file.parent(), ConfigLayerSource::Project { dot_codex_folder } => Some(dot_codex_folder.clone()), + ConfigLayerSource::ProviderState { .. } => None, ConfigLayerSource::SessionFlags => None, ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => None, ConfigLayerSource::LegacyManagedConfigTomlFromMdm => None, diff --git a/codex-rs/core-skills/src/loader.rs b/codex-rs/core-skills/src/loader.rs index 7dac256010..00e31abbdd 100644 --- a/codex-rs/core-skills/src/loader.rs +++ b/codex-rs/core-skills/src/loader.rs @@ -351,6 +351,7 @@ fn skill_roots_from_layer_stack_inner( } ConfigLayerSource::Mdm { .. } | ConfigLayerSource::EnterpriseManaged { .. } + | ConfigLayerSource::ProviderState { .. } | ConfigLayerSource::SessionFlags | ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromMdm => {} diff --git a/codex-rs/hooks/src/engine/discovery.rs b/codex-rs/hooks/src/engine/discovery.rs index 92d17fbabd..6ed78acc6f 100644 --- a/codex-rs/hooks/src/engine/discovery.rs +++ b/codex-rs/hooks/src/engine/discovery.rs @@ -371,6 +371,9 @@ fn config_toml_source_path(layer: &ConfigLayerEntry) -> AbsolutePathBuf { ConfigLayerSource::EnterpriseManaged { id, name } => synthetic_layer_path(&format!( "/{CONFIG_TOML_FILE}" )), + ConfigLayerSource::ProviderState { provider, .. } => { + synthetic_layer_path(&format!("/{CONFIG_TOML_FILE}")) + } ConfigLayerSource::LegacyManagedConfigTomlFromMdm => { synthetic_layer_path("/managed_config.toml") } @@ -612,6 +615,7 @@ fn hook_metadata_for_config_layer_source(source: &ConfigLayerSource) -> (HookSou ConfigLayerSource::Project { .. } => (HookSource::Project, false), ConfigLayerSource::Mdm { .. } => (HookSource::Mdm, true), ConfigLayerSource::EnterpriseManaged { .. } => (HookSource::CloudManagedConfig, true), + ConfigLayerSource::ProviderState { .. } => (HookSource::Unknown, false), ConfigLayerSource::SessionFlags => (HookSource::SessionFlags, false), ConfigLayerSource::LegacyManagedConfigTomlFromFile { .. } => { (HookSource::LegacyManagedConfigFile, true) diff --git a/codex-rs/model-provider/src/amazon_bedrock/auth.rs b/codex-rs/model-provider/src/amazon_bedrock/auth.rs index f9eede24c8..7f73127001 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/auth.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/auth.rs @@ -217,6 +217,7 @@ mod tests { let auth = AmazonBedrockAuth { bearer_token: "stored-bedrock-key".to_string(), region: "eu-west-1".to_string(), + activates_provider: true, }; let stored_auth: StoredAmazonBedrockAuth = Ok(Some(auth)); diff --git a/codex-rs/model-provider/src/amazon_bedrock/mod.rs b/codex-rs/model-provider/src/amazon_bedrock/mod.rs index 828003664f..f8ce333d73 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/mod.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/mod.rs @@ -90,9 +90,11 @@ impl ModelProvider for AmazonBedrockModelProvider { } fn account_state(&self) -> ProviderAccountResult { + let codex_managed_auth = matches!(&self.stored_auth, Ok(Some(_))); Ok(ProviderAccountState { account: Some(ProviderAccount::AmazonBedrock), requires_openai_auth: false, + codex_managed_auth, }) } diff --git a/codex-rs/model-provider/src/amazon_bedrock/provider_auth.rs b/codex-rs/model-provider/src/amazon_bedrock/provider_auth.rs index 1397fb6c83..43e540218c 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/provider_auth.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/provider_auth.rs @@ -11,6 +11,8 @@ use serde::Serialize; pub struct AmazonBedrockAuth { pub bearer_token: String, pub region: String, + #[serde(default = "default_activates_provider")] + pub activates_provider: bool, } pub(super) type StoredAmazonBedrockAuth = std::result::Result, String>; @@ -23,6 +25,7 @@ pub fn save_amazon_bedrock_auth( let auth = AmazonBedrockAuth { bearer_token: bearer_token.to_string(), region: region.to_string(), + activates_provider: true, }; let auth_path = amazon_bedrock_auth_file(codex_home); if let Some(parent) = auth_path.parent() { @@ -97,6 +100,7 @@ mod tests { Some(AmazonBedrockAuth { bearer_token: "bedrock-key".to_string(), region: "us-east-1".to_string(), + activates_provider: true, }) ); assert!(delete_amazon_bedrock_auth(&codex_home).expect("delete auth")); @@ -108,3 +112,7 @@ mod tests { let _ = fs::remove_dir_all(&codex_home); } } + +fn default_activates_provider() -> bool { + true +} diff --git a/codex-rs/model-provider/src/provider.rs b/codex-rs/model-provider/src/provider.rs index 167990c52b..5f6aab9071 100644 --- a/codex-rs/model-provider/src/provider.rs +++ b/codex-rs/model-provider/src/provider.rs @@ -45,6 +45,7 @@ impl Default for ProviderCapabilities { pub struct ProviderAccountState { pub account: Option, pub requires_openai_auth: bool, + pub codex_managed_auth: bool, } /// Error returned when a provider cannot construct its app-visible account state. @@ -231,6 +232,7 @@ impl ModelProvider for ConfiguredModelProvider { }; Ok(ProviderAccountState { + codex_managed_auth: account.is_some(), account, requires_openai_auth: self.info.requires_openai_auth, }) @@ -438,6 +440,7 @@ mod tests { Ok(ProviderAccountState { account: None, requires_openai_auth: true, + codex_managed_auth: false, }) ); } @@ -457,6 +460,7 @@ mod tests { Ok(ProviderAccountState { account: Some(ProviderAccount::ApiKey), requires_openai_auth: true, + codex_managed_auth: true, }) ); } @@ -480,6 +484,7 @@ mod tests { Ok(ProviderAccountState { account: None, requires_openai_auth: false, + codex_managed_auth: false, }) ); } @@ -497,6 +502,7 @@ mod tests { Ok(ProviderAccountState { account: Some(ProviderAccount::AmazonBedrock), requires_openai_auth: false, + codex_managed_auth: false, }) ); } diff --git a/codex-rs/protocol/src/account.rs b/codex-rs/protocol/src/account.rs index bfbccbeb24..57f1799c0b 100644 --- a/codex-rs/protocol/src/account.rs +++ b/codex-rs/protocol/src/account.rs @@ -34,7 +34,10 @@ pub enum PlanType { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ProviderAccount { ApiKey, - Chatgpt { email: String, plan_type: PlanType }, + Chatgpt { + email: String, + plan_type: PlanType, + }, AmazonBedrock, } diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 2290640ab4..36d5905cb7 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -308,7 +308,7 @@ impl AppServerSession { true, ) } - Some(Account::AmazonBedrock {}) => { + Some(Account::AmazonBedrock { .. }) => { (None, None, None, None, FeedbackAudience::External, false) } None => (None, None, None, None, FeedbackAudience::External, false), diff --git a/codex-rs/tui/src/debug_config.rs b/codex-rs/tui/src/debug_config.rs index 562df95989..05a9361b99 100644 --- a/codex-rs/tui/src/debug_config.rs +++ b/codex-rs/tui/src/debug_config.rs @@ -271,6 +271,7 @@ fn render_non_file_layer_details(layer: &ConfigLayerEntry) -> Vec> ConfigLayerSource::SessionFlags => render_session_flag_details(&layer.config), ConfigLayerSource::Mdm { .. } | ConfigLayerSource::EnterpriseManaged { .. } + | ConfigLayerSource::ProviderState { .. } | ConfigLayerSource::LegacyManagedConfigTomlFromMdm => render_non_file_layer_value(layer), ConfigLayerSource::System { .. } | ConfigLayerSource::User { .. } @@ -336,6 +337,7 @@ fn non_file_layer_value_label(source: &ConfigLayerSource) -> &'static str { } ConfigLayerSource::EnterpriseManaged { .. } => "Enterprise-managed config value", ConfigLayerSource::SessionFlags + | ConfigLayerSource::ProviderState { .. } | ConfigLayerSource::System { .. } | ConfigLayerSource::User { .. } | ConfigLayerSource::Project { .. } diff --git a/codex-rs/tui/src/lib.rs b/codex-rs/tui/src/lib.rs index b53ec0f83d..f7034cf629 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -1901,7 +1901,7 @@ async fn get_login_status( Ok(match account.account { Some(AppServerAccount::ApiKey {}) => LoginStatus::AuthMode(AppServerAuthMode::ApiKey), Some(AppServerAccount::Chatgpt { .. }) => LoginStatus::AuthMode(AppServerAuthMode::Chatgpt), - Some(AppServerAccount::AmazonBedrock {}) => LoginStatus::NotAuthenticated, + Some(AppServerAccount::AmazonBedrock { .. }) => LoginStatus::NotAuthenticated, None => LoginStatus::NotAuthenticated, }) }