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 34046d224..eb659fda3 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 @@ -5847,6 +5847,14 @@ }, { "properties": { + "credentialSource": { + "allOf": [ + { + "$ref": "#/definitions/v2/AmazonBedrockCredentialSource" + } + ], + "default": "awsManaged" + }, "type": { "enum": [ "amazonBedrock" @@ -6166,6 +6174,13 @@ "AgentPath": { "type": "string" }, + "AmazonBedrockCredentialSource": { + "enum": [ + "codexManaged", + "awsManaged" + ], + "type": "string" + }, "AnalyticsConfig": { "additionalProperties": true, "properties": { 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 f3f6fb323..e0562760f 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,14 @@ }, { "properties": { + "credentialSource": { + "allOf": [ + { + "$ref": "#/definitions/AmazonBedrockCredentialSource" + } + ], + "default": "awsManaged" + }, "type": { "enum": [ "amazonBedrock" @@ -368,6 +376,13 @@ "AgentPath": { "type": "string" }, + "AmazonBedrockCredentialSource": { + "enum": [ + "codexManaged", + "awsManaged" + ], + "type": "string" + }, "AnalyticsConfig": { "additionalProperties": true, "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 ec333708b..62b520de4 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,14 @@ }, { "properties": { + "credentialSource": { + "allOf": [ + { + "$ref": "#/definitions/AmazonBedrockCredentialSource" + } + ], + "default": "awsManaged" + }, "type": { "enum": [ "amazonBedrock" @@ -61,6 +69,13 @@ } ] }, + "AmazonBedrockCredentialSource": { + "enum": [ + "codexManaged", + "awsManaged" + ], + "type": "string" + }, "PlanType": { "enum": [ "free", diff --git a/codex-rs/app-server-protocol/schema/typescript/AmazonBedrockCredentialSource.ts b/codex-rs/app-server-protocol/schema/typescript/AmazonBedrockCredentialSource.ts new file mode 100644 index 000000000..7822a2f8b --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/AmazonBedrockCredentialSource.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 AmazonBedrockCredentialSource = "codexManaged" | "awsManaged"; diff --git a/codex-rs/app-server-protocol/schema/typescript/index.ts b/codex-rs/app-server-protocol/schema/typescript/index.ts index 66fea07dc..c3bc3ec76 100644 --- a/codex-rs/app-server-protocol/schema/typescript/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/index.ts @@ -3,6 +3,7 @@ export type { AbsolutePathBuf } from "./AbsolutePathBuf"; export type { AgentMessageInputContent } from "./AgentMessageInputContent"; export type { AgentPath } from "./AgentPath"; +export type { AmazonBedrockCredentialSource } from "./AmazonBedrockCredentialSource"; export type { ApiPathString } from "./ApiPathString"; export type { ApplyPatchApprovalParams } from "./ApplyPatchApprovalParams"; export type { ApplyPatchApprovalResponse } from "./ApplyPatchApprovalResponse"; 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 4c3a58e8d..09f74469b 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/Account.ts @@ -1,6 +1,7 @@ // 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. +import type { AmazonBedrockCredentialSource } from "../AmazonBedrockCredentialSource"; import type { PlanType } from "../PlanType"; -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", credentialSource: AmazonBedrockCredentialSource, }; diff --git a/codex-rs/app-server-protocol/src/protocol/common.rs b/codex-rs/app-server-protocol/src/protocol/common.rs index 581a7a3b0..7611c27ff 100644 --- a/codex-rs/app-server-protocol/src/protocol/common.rs +++ b/codex-rs/app-server-protocol/src/protocol/common.rs @@ -1674,6 +1674,7 @@ mod tests { use super::*; use anyhow::Result; use codex_protocol::ThreadId; + use codex_protocol::account::AmazonBedrockCredentialSource; use codex_protocol::account::PlanType; use codex_protocol::models::BUILT_IN_PERMISSION_PROFILE_READ_ONLY; use codex_protocol::parse_command::ParsedCommand; @@ -2777,6 +2778,41 @@ mod tests { serde_json::to_value(&chatgpt)?, ); + let codex_managed_bedrock = v2::Account::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::CodexManaged, + }; + assert_eq!( + json!({ + "type": "amazonBedrock", + "credentialSource": "codexManaged", + }), + serde_json::to_value(&codex_managed_bedrock)?, + ); + + let aws_managed_bedrock = v2::Account::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::AwsManaged, + }; + assert_eq!( + json!({ + "type": "amazonBedrock", + "credentialSource": "awsManaged", + }), + serde_json::to_value(&aws_managed_bedrock)?, + ); + + Ok(()) + } + + #[test] + fn account_defaults_legacy_bedrock_credential_source() -> Result<()> { + assert_eq!( + v2::Account::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::AwsManaged, + }, + serde_json::from_value(json!({ + "type": "amazonBedrock", + }))?, + ); Ok(()) } 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 f649f99a4..2a71a092c 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/account.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/account.rs @@ -1,5 +1,6 @@ use crate::protocol::common::AuthMode; use codex_experimental_api_macros::ExperimentalApi; +use codex_protocol::account::AmazonBedrockCredentialSource; use codex_protocol::account::PlanType; use codex_protocol::account::ProviderAccount; use codex_protocol::protocol::CreditsSnapshot as CoreCreditsSnapshot; @@ -28,7 +29,14 @@ pub enum Account { #[serde(rename = "amazonBedrock", rename_all = "camelCase")] #[ts(rename = "amazonBedrock", rename_all = "camelCase")] - AmazonBedrock {}, + AmazonBedrock { + #[serde(default = "default_bedrock_credential_source")] + credential_source: AmazonBedrockCredentialSource, + }, +} + +fn default_bedrock_credential_source() -> AmazonBedrockCredentialSource { + AmazonBedrockCredentialSource::AwsManaged } impl From for Account { @@ -36,7 +44,9 @@ impl From for Account { match account { ProviderAccount::ApiKey => Self::ApiKey {}, ProviderAccount::Chatgpt { email, plan_type } => Self::Chatgpt { email, plan_type }, - ProviderAccount::AmazonBedrock => Self::AmazonBedrock {}, + ProviderAccount::AmazonBedrock { credential_source } => { + Self::AmazonBedrock { credential_source } + } } } } diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 918887738..336f769d7 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -1902,12 +1902,15 @@ Response examples: { "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": { "type": "amazonBedrock", "credentialSource": "codexManaged" }, "requiresOpenaiAuth": false } } +{ "id": 1, "result": { "account": { "type": "amazonBedrock", "credentialSource": "awsManaged" }, "requiresOpenaiAuth": false } } ``` Field notes: - `refreshToken` (bool): set `true` to force a token refresh. - `requiresOpenaiAuth` reflects the active provider; when `false`, Codex can run without OpenAI credentials. +- Amazon Bedrock reports `credentialSource: "codexManaged"` when it uses a Bedrock API key managed by Codex. Otherwise it reports `credentialSource: "awsManaged"` for the external AWS credential path. This identifies the selected credential source; it does not validate that the AWS credential chain can resolve credentials. ### 2) Log in with an API key diff --git a/codex-rs/app-server/tests/suite/v2/account.rs b/codex-rs/app-server/tests/suite/v2/account.rs index aab0a357a..06874c423 100644 --- a/codex-rs/app-server/tests/suite/v2/account.rs +++ b/codex-rs/app-server/tests/suite/v2/account.rs @@ -37,6 +37,8 @@ use codex_login::AuthKeyringBackendKind; use codex_login::CLIENT_ID_OVERRIDE_ENV_VAR; use codex_login::REFRESH_TOKEN_URL_OVERRIDE_ENV_VAR; use codex_login::login_with_api_key; +use codex_login::login_with_bedrock_api_key; +use codex_protocol::account::AmazonBedrockCredentialSource; use codex_protocol::account::PlanType as AccountPlanType; use core_test_support::responses; use pretty_assertions::assert_eq; @@ -1725,13 +1727,60 @@ region = "us-west-2" let received: GetAccountResponse = to_response(resp)?; let expected = GetAccountResponse { - account: Some(Account::AmazonBedrock {}), + account: Some(Account::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::AwsManaged, + }), requires_openai_auth: false, }; assert_eq!(received, expected); Ok(()) } +#[tokio::test] +async fn get_account_with_managed_bedrock_provider() -> Result<()> { + let codex_home = TempDir::new()?; + create_config_toml( + codex_home.path(), + CreateConfigTomlParams { + model_provider_id: Some("amazon-bedrock".to_string()), + ..Default::default() + }, + )?; + login_with_bedrock_api_key( + codex_home.path(), + "managed-bedrock-api-key", + "us-west-2", + AuthCredentialsStoreMode::File, + AuthKeyringBackendKind::default(), + )?; + + let mut mcp = TestAppServer::new(codex_home.path()).await?; + timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??; + + let request_id = mcp + .send_get_account_request(GetAccountParams { + refresh_token: false, + }) + .await?; + let resp: JSONRPCResponse = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_response_message(RequestId::Integer(request_id)), + ) + .await??; + let received: GetAccountResponse = to_response(resp)?; + + assert_eq!( + received, + GetAccountResponse { + account: Some(Account::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::CodexManaged, + }), + requires_openai_auth: false, + } + ); + Ok(()) +} + #[tokio::test] async fn get_account_with_chatgpt() -> Result<()> { let codex_home = TempDir::new()?; diff --git a/codex-rs/model-provider/src/amazon_bedrock/mod.rs b/codex-rs/model-provider/src/amazon_bedrock/mod.rs index d44e7cbd0..68c94fe4d 100644 --- a/codex-rs/model-provider/src/amazon_bedrock/mod.rs +++ b/codex-rs/model-provider/src/amazon_bedrock/mod.rs @@ -15,6 +15,7 @@ use codex_model_provider_info::ModelProviderAwsAuthInfo; use codex_model_provider_info::ModelProviderInfo; use codex_models_manager::manager::SharedModelsManager; use codex_models_manager::manager::StaticModelsManager; +use codex_protocol::account::AmazonBedrockCredentialSource; use codex_protocol::account::ProviderAccount; use codex_protocol::error::Result; use codex_protocol::openai_models::ModelsResponse; @@ -130,8 +131,13 @@ impl ModelProvider for AmazonBedrockModelProvider { } fn account_state(&self) -> ProviderAccountResult { + let credential_source = if self.managed_auth().is_some() { + AmazonBedrockCredentialSource::CodexManaged + } else { + AmazonBedrockCredentialSource::AwsManaged + }; Ok(ProviderAccountState { - account: Some(ProviderAccount::AmazonBedrock), + account: Some(ProviderAccount::AmazonBedrock { credential_source }), requires_openai_auth: false, }) } @@ -209,6 +215,15 @@ mod tests { provider.auth().await, Some(CodexAuth::BedrockApiKey(managed_auth)) ); + assert_eq!( + provider.account_state(), + Ok(ProviderAccountState { + account: Some(ProviderAccount::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::CodexManaged, + }), + requires_openai_auth: false, + }) + ); assert_eq!( provider .runtime_base_url() @@ -238,6 +253,15 @@ mod tests { assert!(provider.auth_manager().is_none()); assert_eq!(provider.auth().await, None); + assert_eq!( + provider.account_state(), + Ok(ProviderAccountState { + account: Some(ProviderAccount::AmazonBedrock { + credential_source: AmazonBedrockCredentialSource::AwsManaged, + }), + requires_openai_auth: false, + }) + ); } #[test] diff --git a/codex-rs/model-provider/src/provider.rs b/codex-rs/model-provider/src/provider.rs index f794d49f4..91a1c06a5 100644 --- a/codex-rs/model-provider/src/provider.rs +++ b/codex-rs/model-provider/src/provider.rs @@ -577,7 +577,10 @@ mod tests { assert_eq!( provider.account_state(), Ok(ProviderAccountState { - account: Some(ProviderAccount::AmazonBedrock), + account: Some(ProviderAccount::AmazonBedrock { + credential_source: + codex_protocol::account::AmazonBedrockCredentialSource::AwsManaged, + }), requires_openai_auth: false, }) ); diff --git a/codex-rs/protocol/src/account.rs b/codex-rs/protocol/src/account.rs index bfbccbeb2..11286eddc 100644 --- a/codex-rs/protocol/src/account.rs +++ b/codex-rs/protocol/src/account.rs @@ -34,8 +34,21 @@ pub enum PlanType { #[derive(Debug, Clone, PartialEq, Eq)] pub enum ProviderAccount { ApiKey, - Chatgpt { email: String, plan_type: PlanType }, - AmazonBedrock, + Chatgpt { + email: String, + plan_type: PlanType, + }, + AmazonBedrock { + credential_source: AmazonBedrockCredentialSource, + }, +} + +#[derive(Serialize, Deserialize, Copy, Clone, Debug, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(rename_all = "camelCase")] +pub enum AmazonBedrockCredentialSource { + CodexManaged, + AwsManaged, } impl PlanType { diff --git a/codex-rs/tui/src/app_server_session.rs b/codex-rs/tui/src/app_server_session.rs index 846970bfc..5e36fb1c9 100644 --- a/codex-rs/tui/src/app_server_session.rs +++ b/codex-rs/tui/src/app_server_session.rs @@ -327,7 +327,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/lib.rs b/codex-rs/tui/src/lib.rs index 3ec8bef7c..002e64726 100644 --- a/codex-rs/tui/src/lib.rs +++ b/codex-rs/tui/src/lib.rs @@ -1874,7 +1874,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, }) }