Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions codex-rs/app-server-protocol/src/protocol/v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use crate::RequestId;
use crate::protocol::common::AuthMode;
use codex_experimental_api_macros::ExperimentalApi;
use codex_protocol::account::PlanType;
use codex_protocol::account::ProviderAccount;
use codex_protocol::approvals::ElicitationRequest as CoreElicitationRequest;
use codex_protocol::approvals::ExecPolicyAmendment as CoreExecPolicyAmendment;
use codex_protocol::approvals::GuardianAssessmentAction as CoreGuardianAssessmentAction;
Expand Down Expand Up @@ -2015,6 +2016,20 @@ pub enum Account {
#[serde(rename = "chatgpt", rename_all = "camelCase")]
#[ts(rename = "chatgpt", rename_all = "camelCase")]
Chatgpt { email: String, plan_type: PlanType },

#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
AmazonBedrock {},
}

impl From<ProviderAccount> for Account {
fn from(account: ProviderAccount) -> Self {
match account {
ProviderAccount::ApiKey => Self::ApiKey {},
ProviderAccount::Chatgpt { email, plan_type } => Self::Chatgpt { email, plan_type },
ProviderAccount::AmazonBedrock => Self::AmazonBedrock {},
}
}
}

#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS, ExperimentalApi)]
Expand Down
62 changes: 20 additions & 42 deletions codex-rs/app-server/src/codex_message_processor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ use codex_app_server_protocol::AppsListParams;
use codex_app_server_protocol::AppsListResponse;
use codex_app_server_protocol::AskForApproval;
use codex_app_server_protocol::AuthMode;
use codex_app_server_protocol::AuthMode as CoreAuthMode;
use codex_app_server_protocol::CancelLoginAccountParams;
use codex_app_server_protocol::CancelLoginAccountResponse;
use codex_app_server_protocol::CancelLoginAccountStatus;
Expand Down Expand Up @@ -302,6 +301,8 @@ use codex_mcp::discover_supported_scopes;
use codex_mcp::effective_mcp_servers;
use codex_mcp::read_mcp_resource as read_mcp_resource_without_thread;
use codex_mcp::resolve_oauth_scopes;
use codex_model_provider::ProviderAccountError;
use codex_model_provider::create_model_provider;
use codex_models_manager::collaboration_mode_presets::CollaborationModesConfig;
use codex_protocol::ThreadId;
use codex_protocol::config_types::CollaborationMode;
Expand Down Expand Up @@ -1844,51 +1845,28 @@ impl CodexMessageProcessor {

self.refresh_token_if_requested(do_refresh).await;

// Whether auth is required for the active model provider.
let requires_openai_auth = self.config.model_provider.requires_openai_auth;

if !requires_openai_auth {
let response = GetAccountResponse {
account: None,
requires_openai_auth,
};
self.outgoing.send_response(request_id, response).await;
return;
}

let account = match self.auth_manager.auth_cached() {
Some(auth) => match auth.auth_mode() {
CoreAuthMode::ApiKey => Some(Account::ApiKey {}),
CoreAuthMode::Chatgpt
| CoreAuthMode::ChatgptAuthTokens
| CoreAuthMode::AgentIdentity => {
let email = auth.get_account_email();
let plan_type = auth.account_plan_type();

match (email, plan_type) {
(Some(email), Some(plan_type)) => {
Some(Account::Chatgpt { email, plan_type })
}
_ => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message:
"email and plan type are required for chatgpt authentication"
.to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
}
}
},
None => None,
let provider = create_model_provider(
self.config.model_provider.clone(),
Some(self.auth_manager.clone()),
);
let account_state = match provider.account_state() {
Ok(account_state) => account_state,
Err(ProviderAccountError::MissingChatgptAccountDetails) => {
let error = JSONRPCErrorError {
code: INVALID_REQUEST_ERROR_CODE,
message: "email and plan type are required for chatgpt authentication"
.to_string(),
data: None,
};
self.outgoing.send_error(request_id, error).await;
return;
}
};
let account = account_state.account.map(Account::from);

let response = GetAccountResponse {
account,
requires_openai_auth,
requires_openai_auth: account_state.requires_openai_auth,
};
self.outgoing.send_response(request_id, response).await;
}
Expand Down
70 changes: 62 additions & 8 deletions codex-rs/app-server/tests/suite/v2/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ struct CreateConfigTomlParams {
forced_workspace_id: Option<String>,
requires_openai_auth: Option<bool>,
base_url: Option<String>,
model_provider_id: Option<String>,
extra_provider_config: Option<String>,
}

fn create_config_toml(codex_home: &Path, params: CreateConfigTomlParams) -> std::io::Result<()> {
Expand All @@ -77,6 +79,23 @@ fn create_config_toml(codex_home: &Path, params: CreateConfigTomlParams) -> std:
Some(false) => String::new(),
None => String::new(),
};
let model_provider_id = params
.model_provider_id
.unwrap_or_else(|| "mock_provider".to_string());
let provider_section = if model_provider_id == "mock_provider" {
format!(
r#"[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "{base_url}"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
{requires_line}
"#
)
} else {
params.extra_provider_config.unwrap_or_default()
};
let contents = format!(
r#"
model = "mock-model"
Expand All @@ -85,18 +104,12 @@ sandbox_mode = "danger-full-access"
{forced_line}
{forced_workspace_line}

model_provider = "mock_provider"
model_provider = "{model_provider_id}"

[features]
shell_snapshot = false

[model_providers.mock_provider]
name = "Mock provider for test"
base_url = "{base_url}"
wire_api = "responses"
request_max_retries = 0
stream_max_retries = 0
{requires_line}
{provider_section}
"#
);
std::fs::write(config_toml, contents)
Expand Down Expand Up @@ -1545,6 +1558,47 @@ async fn get_account_when_auth_not_required() -> Result<()> {
Ok(())
}

#[tokio::test]
async fn get_account_with_aws_provider() -> 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]
profile = "codex-bedrock"
region = "us-west-2"
"#
.to_string(),
),
..Default::default()
},
)?;

let mut mcp = McpProcess::new(codex_home.path()).await?;
timeout(DEFAULT_READ_TIMEOUT, mcp.initialize()).await??;

let params = GetAccountParams {
refresh_token: false,
};
let request_id = mcp.send_get_account_request(params).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)?;

let expected = GetAccountResponse {
account: Some(Account::AmazonBedrock {}),
requires_openai_auth: false,
};
assert_eq!(received, expected);
Ok(())
}

#[tokio::test]
async fn get_account_with_chatgpt() -> Result<()> {
let codex_home = TempDir::new()?;
Expand Down
10 changes: 10 additions & 0 deletions codex-rs/model-provider/src/amazon_bedrock/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@ use codex_login::AuthManager;
use codex_login::CodexAuth;
use codex_model_provider_info::ModelProviderAwsAuthInfo;
use codex_model_provider_info::ModelProviderInfo;
use codex_protocol::account::ProviderAccount;
use codex_protocol::error::Result;

use crate::provider::ModelProvider;
use crate::provider::ProviderAccountResult;
use crate::provider::ProviderAccountState;
use auth::resolve_provider_auth;
use auth::resolve_region;
use mantle::base_url;
Expand All @@ -37,6 +40,13 @@ impl ModelProvider for AmazonBedrockModelProvider {
None
}

fn account_state(&self) -> ProviderAccountResult {
Ok(ProviderAccountState {
account: Some(ProviderAccount::AmazonBedrock),
requires_openai_auth: false,
})
}

async fn api_provider(&self) -> Result<Provider> {
let region = resolve_region(&self.aws).await?;
let mut api_provider_info = self.info.clone();
Expand Down
4 changes: 4 additions & 0 deletions codex-rs/model-provider/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ pub use auth::auth_provider_from_auth;
pub use auth::unauthenticated_auth_provider;
pub use bearer_auth_provider::BearerAuthProvider;
pub use bearer_auth_provider::BearerAuthProvider as CoreAuthProvider;
pub use codex_protocol::account::ProviderAccount;
pub use provider::ModelProvider;
pub use provider::ProviderAccountError;
pub use provider::ProviderAccountResult;
pub use provider::ProviderAccountState;
pub use provider::SharedModelProvider;
pub use provider::create_model_provider;
Loading
Loading