mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
changes
This commit is contained in:
Generated
+1
@@ -3307,6 +3307,7 @@ dependencies = [
|
||||
"codex-response-debug-context",
|
||||
"http 1.4.0",
|
||||
"pretty_assertions",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
||||
@@ -1331,6 +1331,30 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensLoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockLoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"region",
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockLoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
+40
@@ -10635,6 +10635,30 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"region",
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountParams"
|
||||
@@ -10728,6 +10752,22 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountResponseType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountResponse"
|
||||
|
||||
+40
@@ -7157,6 +7157,30 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"region",
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountParams"
|
||||
@@ -7250,6 +7274,22 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountResponseType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountResponse"
|
||||
|
||||
@@ -89,6 +89,30 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"key": {
|
||||
"type": "string"
|
||||
},
|
||||
"region": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountParamsType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"key",
|
||||
"region",
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountParams",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountParams"
|
||||
|
||||
@@ -87,6 +87,22 @@
|
||||
],
|
||||
"title": "ChatgptAuthTokensv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
},
|
||||
{
|
||||
"properties": {
|
||||
"type": {
|
||||
"enum": [
|
||||
"amazonBedrock"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountResponseType",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"type"
|
||||
],
|
||||
"title": "AmazonBedrockv2::LoginAccountResponse",
|
||||
"type": "object"
|
||||
}
|
||||
],
|
||||
"title": "LoginAccountResponse"
|
||||
|
||||
@@ -18,4 +18,4 @@ chatgptAccountId: string,
|
||||
* When `null`, Codex attempts to derive the plan type from access-token
|
||||
* claims. If unavailable, the plan defaults to `unknown`.
|
||||
*/
|
||||
chatgptPlanType?: string | null, };
|
||||
chatgptPlanType?: string | null, } | { "type": "amazonBedrock", key: string, region: string, };
|
||||
|
||||
@@ -14,4 +14,4 @@ verificationUrl: string,
|
||||
/**
|
||||
* One-time code the user must enter after signing in.
|
||||
*/
|
||||
userCode: string, } | { "type": "chatgptAuthTokens", };
|
||||
userCode: string, } | { "type": "chatgptAuthTokens", } | { "type": "amazonBedrock", };
|
||||
|
||||
@@ -80,6 +80,9 @@ pub enum LoginAccountParams {
|
||||
#[ts(optional = nullable)]
|
||||
chatgpt_plan_type: Option<String>,
|
||||
},
|
||||
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
AmazonBedrock { key: String, region: String },
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
@@ -113,6 +116,9 @@ pub enum LoginAccountResponse {
|
||||
#[serde(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
||||
#[ts(rename = "chatgptAuthTokens", rename_all = "camelCase")]
|
||||
ChatgptAuthTokens {},
|
||||
#[serde(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
#[ts(rename = "amazonBedrock", rename_all = "camelCase")]
|
||||
AmazonBedrock {},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
|
||||
|
||||
@@ -47,6 +47,8 @@ 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;
|
||||
@@ -100,6 +102,7 @@ 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;
|
||||
@@ -354,8 +357,12 @@ use codex_mcp::discover_supported_scopes;
|
||||
use codex_mcp::read_mcp_resource as read_mcp_resource_without_thread;
|
||||
use codex_mcp::resolve_oauth_scopes;
|
||||
use codex_memories_write::clear_memory_roots_contents;
|
||||
use codex_model_provider::AMAZON_BEDROCK_PROVIDER_ID;
|
||||
use codex_model_provider::ProviderAccountError;
|
||||
use codex_model_provider::create_model_provider;
|
||||
use codex_model_provider::delete_amazon_bedrock_auth;
|
||||
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::config_types::CollaborationMode;
|
||||
|
||||
@@ -238,6 +238,9 @@ impl AccountRequestProcessor {
|
||||
)
|
||||
.await;
|
||||
}
|
||||
LoginAccountParams::AmazonBedrock { key, region } => {
|
||||
self.login_amazon_bedrock_v2(request_id, key, region).await;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
@@ -300,6 +303,123 @@ impl AccountRequestProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
async fn login_amazon_bedrock_common(
|
||||
&self,
|
||||
key: &str,
|
||||
region: &str,
|
||||
) -> std::result::Result<(), JSONRPCErrorError> {
|
||||
let key = key.trim();
|
||||
if key.is_empty() {
|
||||
return Err(invalid_request("Amazon Bedrock key must not be empty."));
|
||||
}
|
||||
|
||||
let region = region.trim();
|
||||
if !is_supported_amazon_bedrock_region(region) {
|
||||
return Err(invalid_request(format!(
|
||||
"Amazon Bedrock Mantle does not support region `{region}`"
|
||||
)));
|
||||
}
|
||||
|
||||
// Cancel any active login attempt.
|
||||
{
|
||||
let mut guard = self.active_login.lock().await;
|
||||
if let Some(active) = guard.take() {
|
||||
drop(active);
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
.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(),
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn login_amazon_bedrock_v2(
|
||||
&self,
|
||||
request_id: ConnectionRequestId,
|
||||
key: String,
|
||||
region: String,
|
||||
) {
|
||||
let result = self
|
||||
.login_amazon_bedrock_common(&key, ®ion)
|
||||
.await
|
||||
.map(|()| LoginAccountResponse::AmazonBedrock {});
|
||||
let logged_in = result.is_ok();
|
||||
self.outgoing.send_result(request_id, result).await;
|
||||
|
||||
if logged_in {
|
||||
self.send_amazon_bedrock_login_success_notifications().await;
|
||||
}
|
||||
}
|
||||
|
||||
async fn send_amazon_bedrock_login_success_notifications(&self) {
|
||||
Self::maybe_refresh_remote_installed_plugins_cache_for_current_config(
|
||||
&self.config_manager,
|
||||
&self.thread_manager,
|
||||
self.auth_manager.auth_cached(),
|
||||
)
|
||||
.await;
|
||||
|
||||
self.outgoing
|
||||
.send_server_notification(ServerNotification::AccountLoginCompleted(
|
||||
AccountLoginCompletedNotification {
|
||||
login_id: None,
|
||||
success: true,
|
||||
error: None,
|
||||
},
|
||||
))
|
||||
.await;
|
||||
self.outgoing
|
||||
.send_server_notification(ServerNotification::AccountUpdated(
|
||||
AccountUpdatedNotification {
|
||||
auth_mode: None,
|
||||
plan_type: None,
|
||||
},
|
||||
))
|
||||
.await;
|
||||
}
|
||||
|
||||
// Build options for a ChatGPT login attempt; performs validation.
|
||||
async fn login_chatgpt_common(
|
||||
&self,
|
||||
@@ -682,6 +802,9 @@ impl AccountRequestProcessor {
|
||||
return Err(internal_error(format!("logout failed: {err}")));
|
||||
}
|
||||
}
|
||||
delete_amazon_bedrock_auth(&self.config.codex_home).map_err(|err| {
|
||||
internal_error(format!("failed to remove Amazon Bedrock auth: {err}"))
|
||||
})?;
|
||||
|
||||
Self::maybe_refresh_remote_installed_plugins_cache_for_current_config(
|
||||
&self.config_manager,
|
||||
@@ -810,9 +933,15 @@ impl AccountRequestProcessor {
|
||||
|
||||
self.refresh_token_if_requested(do_refresh).await;
|
||||
|
||||
let config = self
|
||||
.config_manager
|
||||
.load_latest_config(/*fallback_cwd*/ None)
|
||||
.await
|
||||
.map_err(|err| internal_error(format!("failed to reload config: {err}")))?;
|
||||
let provider = create_model_provider(
|
||||
self.config.model_provider.clone(),
|
||||
config.model_provider.clone(),
|
||||
Some(self.auth_manager.clone()),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let account_state = match provider.account_state() {
|
||||
Ok(account_state) => account_state,
|
||||
|
||||
@@ -172,7 +172,11 @@ impl ConfigRequestProcessor {
|
||||
&self,
|
||||
) -> Result<ModelProviderCapabilitiesReadResponse, JSONRPCErrorError> {
|
||||
let config = self.load_latest_config(/*fallback_cwd*/ None).await?;
|
||||
let provider = create_model_provider(config.model_provider, /*auth_manager*/ None);
|
||||
let provider = create_model_provider(
|
||||
config.model_provider,
|
||||
/*auth_manager*/ None,
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let capabilities = provider.capabilities();
|
||||
Ok(ModelProviderCapabilitiesReadResponse {
|
||||
namespace_tools: capabilities.namespace_tools,
|
||||
|
||||
@@ -2260,7 +2260,11 @@ async fn websocket_reachability_check(
|
||||
provider.websocket_connect_timeout().as_millis()
|
||||
));
|
||||
|
||||
let runtime_provider = create_model_provider(provider.clone(), auth_manager);
|
||||
let runtime_provider = create_model_provider(
|
||||
provider.clone(),
|
||||
auth_manager,
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let auth = runtime_provider.auth().await;
|
||||
details.push(format!(
|
||||
"auth mode: {}",
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//! fails, normal stream retry/fallback logic handles recovery on the same turn.
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
use std::sync::Mutex as StdMutex;
|
||||
use std::sync::OnceLock;
|
||||
@@ -321,6 +322,7 @@ impl ModelClient {
|
||||
thread_id: ThreadId,
|
||||
installation_id: String,
|
||||
provider_info: ModelProviderInfo,
|
||||
codex_home: PathBuf,
|
||||
session_source: SessionSource,
|
||||
parent_thread_id: Option<ThreadId>,
|
||||
model_verbosity: Option<VerbosityConfig>,
|
||||
@@ -329,7 +331,7 @@ impl ModelClient {
|
||||
beta_features_header: Option<String>,
|
||||
attestation_provider: Option<Arc<dyn AttestationProvider>>,
|
||||
) -> Self {
|
||||
let model_provider = create_model_provider(provider_info, auth_manager);
|
||||
let model_provider = create_model_provider(provider_info, auth_manager, Some(codex_home));
|
||||
let codex_api_key_env_enabled = model_provider
|
||||
.auth_manager()
|
||||
.as_ref()
|
||||
|
||||
@@ -76,6 +76,7 @@ fn test_model_client_with_parent(
|
||||
thread_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider,
|
||||
std::env::temp_dir(),
|
||||
session_source,
|
||||
parent_thread_id,
|
||||
/*model_verbosity*/ None,
|
||||
@@ -530,6 +531,7 @@ fn model_client_with_counting_attestation(
|
||||
ThreadId::new(),
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider,
|
||||
std::env::temp_dir(),
|
||||
SessionSource::Exec,
|
||||
/*parent_thread_id*/ None,
|
||||
/*model_verbosity*/ None,
|
||||
|
||||
@@ -188,7 +188,11 @@ async fn guardian_test_session_and_turn_with_base_url(
|
||||
);
|
||||
session.services.models_manager = models_manager;
|
||||
turn.config = Arc::clone(&config);
|
||||
turn.provider = create_model_provider(config.model_provider.clone(), turn.auth_manager.clone());
|
||||
turn.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
turn.user_instructions = None;
|
||||
|
||||
(Arc::new(session), Arc::new(turn))
|
||||
@@ -1428,7 +1432,11 @@ async fn guardian_review_request_layout_matches_model_visible_request_snapshot()
|
||||
);
|
||||
session.services.models_manager = models_manager;
|
||||
turn.config = Arc::clone(&config);
|
||||
turn.provider = create_model_provider(config.model_provider.clone(), turn.auth_manager.clone());
|
||||
turn.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let session = Arc::new(session);
|
||||
let turn = Arc::new(turn);
|
||||
seed_guardian_parent_history(&session, &turn).await;
|
||||
@@ -2007,8 +2015,11 @@ async fn guardian_review_surfaces_responses_api_errors_in_rejection_reason() ->
|
||||
.models_manager = models_manager;
|
||||
let turn_mut = Arc::get_mut(&mut turn).expect("turn should be uniquely owned");
|
||||
turn_mut.config = Arc::clone(&config);
|
||||
turn_mut.provider =
|
||||
create_model_provider(config.model_provider.clone(), turn_mut.auth_manager.clone());
|
||||
turn_mut.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_mut.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
turn_mut.user_instructions = None;
|
||||
|
||||
seed_guardian_parent_history(&session, &turn).await;
|
||||
|
||||
@@ -2339,6 +2339,7 @@ async fn guardian_mode_skips_auto_when_annotations_do_not_require_approval() {
|
||||
turn_context.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
|
||||
let session = Arc::new(session);
|
||||
@@ -2623,6 +2624,7 @@ async fn guardian_mode_mcp_denial_returns_rationale_message() {
|
||||
turn_context.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
|
||||
let session = Arc::new(session);
|
||||
@@ -2843,6 +2845,7 @@ async fn approve_mode_skips_guardian_in_every_permission_mode() {
|
||||
turn_context.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
|
||||
let session = Arc::new(session);
|
||||
|
||||
@@ -1037,6 +1037,7 @@ impl Session {
|
||||
thread_id,
|
||||
installation_id.clone(),
|
||||
session_configuration.provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.parent_thread_id,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -437,6 +437,7 @@ fn test_model_client_session() -> crate::client::ModelClientSession {
|
||||
thread_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
ModelProviderInfo::create_openai_provider(/* base_url */ /*base_url*/ None),
|
||||
std::env::temp_dir(),
|
||||
codex_protocol::protocol::SessionSource::Exec,
|
||||
/*parent_thread_id*/ None,
|
||||
/*model_verbosity*/ None,
|
||||
@@ -4770,6 +4771,7 @@ pub(crate) async fn make_session_and_context() -> (Session, TurnContext) {
|
||||
thread_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
session_configuration.provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.parent_thread_id,
|
||||
config.model_verbosity,
|
||||
@@ -6860,6 +6862,7 @@ where
|
||||
thread_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
session_configuration.provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_configuration.session_source.clone(),
|
||||
session_configuration.parent_thread_id,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -114,6 +114,7 @@ async fn request_permissions_routes_to_guardian_when_reviewer_is_enabled() {
|
||||
turn_context_raw.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context_raw.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
@@ -205,6 +206,7 @@ async fn request_permissions_guardian_review_stops_when_cancelled() {
|
||||
turn_context_raw.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context_raw.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
|
||||
let requested_permissions = RequestPermissionProfile {
|
||||
@@ -320,6 +322,7 @@ async fn guardian_allows_shell_command_additional_permissions_requests_past_poli
|
||||
turn_context_raw.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context_raw.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
@@ -425,6 +428,7 @@ async fn strict_auto_review_turn_grant_forces_guardian_for_shell_command_policy_
|
||||
turn_context_raw.provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
turn_context_raw.auth_manager.clone(),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
let session = Arc::new(session);
|
||||
let turn_context = Arc::new(turn_context_raw);
|
||||
|
||||
@@ -489,7 +489,11 @@ impl Session {
|
||||
);
|
||||
let session_source = session_configuration.session_source.clone();
|
||||
let auth_manager_for_context = auth_manager.clone();
|
||||
let provider_for_context = create_model_provider(provider, auth_manager);
|
||||
let provider_for_context = create_model_provider(
|
||||
provider,
|
||||
auth_manager,
|
||||
Some(per_turn_config.codex_home.to_path_buf()),
|
||||
);
|
||||
let session_telemetry_for_context = session_telemetry;
|
||||
let available_models = models_manager.try_list_models().unwrap_or_default();
|
||||
let unified_exec_shell_mode = UnifiedExecShellMode::for_session(
|
||||
|
||||
@@ -121,7 +121,7 @@ pub fn models_manager_with_provider(
|
||||
auth_manager: Arc<AuthManager>,
|
||||
provider: ModelProviderInfo,
|
||||
) -> SharedModelsManager {
|
||||
let provider = create_model_provider(provider, Some(auth_manager));
|
||||
let provider = create_model_provider(provider, Some(auth_manager), Some(codex_home.clone()));
|
||||
provider.models_manager(codex_home, /*config_model_catalog*/ None)
|
||||
}
|
||||
|
||||
|
||||
@@ -221,7 +221,11 @@ pub fn build_models_manager(
|
||||
config: &Config,
|
||||
auth_manager: Arc<AuthManager>,
|
||||
) -> SharedModelsManager {
|
||||
let provider = create_model_provider(config.model_provider.clone(), Some(auth_manager));
|
||||
let provider = create_model_provider(
|
||||
config.model_provider.clone(),
|
||||
Some(auth_manager),
|
||||
Some(config.codex_home.to_path_buf()),
|
||||
);
|
||||
provider.models_manager(
|
||||
config.codex_home.to_path_buf(),
|
||||
config.model_catalog.clone(),
|
||||
@@ -380,8 +384,12 @@ impl ThreadManager {
|
||||
state: Arc::new(ThreadManagerState {
|
||||
threads: Arc::new(RwLock::new(HashMap::new())),
|
||||
thread_created_tx,
|
||||
models_manager: create_model_provider(provider, Some(auth_manager.clone()))
|
||||
.models_manager(codex_home, /*config_model_catalog*/ None),
|
||||
models_manager: create_model_provider(
|
||||
provider,
|
||||
Some(auth_manager.clone()),
|
||||
Some(codex_home.clone()),
|
||||
)
|
||||
.models_manager(codex_home, /*config_model_catalog*/ None),
|
||||
environment_manager,
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
|
||||
@@ -264,8 +264,12 @@ async fn spawn_agent_uses_explorer_role_and_preserves_approval_policy() {
|
||||
turn.approval_policy
|
||||
.set(AskForApproval::OnRequest)
|
||||
.expect("approval policy should be set");
|
||||
turn.provider = create_model_provider(provider_info, turn.auth_manager.clone());
|
||||
turn.config = Arc::new(config);
|
||||
turn.provider = create_model_provider(
|
||||
provider_info,
|
||||
turn.auth_manager.clone(),
|
||||
Some(turn.config.codex_home.to_path_buf()),
|
||||
);
|
||||
|
||||
let invocation = invocation(
|
||||
Arc::new(session),
|
||||
|
||||
@@ -271,6 +271,7 @@ fn use_chatgpt_auth(turn: &mut TurnContext) {
|
||||
turn.provider = create_model_provider(
|
||||
turn.config.model_provider.clone(),
|
||||
turn.auth_manager.clone(),
|
||||
Some(turn.config.codex_home.to_path_buf()),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -280,7 +281,11 @@ fn use_bedrock_provider(turn: &mut TurnContext) {
|
||||
config.model_provider_id = AMAZON_BEDROCK_PROVIDER_ID.to_string();
|
||||
config.model_provider = provider_info.clone();
|
||||
});
|
||||
turn.provider = create_model_provider(provider_info, turn.auth_manager.clone());
|
||||
turn.provider = create_model_provider(
|
||||
provider_info,
|
||||
turn.auth_manager.clone(),
|
||||
Some(turn.config.codex_home.to_path_buf()),
|
||||
);
|
||||
}
|
||||
|
||||
struct WebRunExtensionTool;
|
||||
|
||||
@@ -104,6 +104,7 @@ async fn responses_stream_includes_subagent_header_on_review() {
|
||||
thread_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_source,
|
||||
/*parent_thread_id*/ None,
|
||||
config.model_verbosity,
|
||||
@@ -233,6 +234,7 @@ async fn responses_stream_includes_subagent_header_on_other() {
|
||||
thread_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_source,
|
||||
/*parent_thread_id*/ None,
|
||||
config.model_verbosity,
|
||||
@@ -351,6 +353,7 @@ async fn responses_respects_model_info_overrides_from_config() {
|
||||
thread_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_source,
|
||||
/*parent_thread_id*/ None,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -903,6 +903,7 @@ async fn send_provider_auth_request(server: &MockServer, auth: ModelProviderAuth
|
||||
thread_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider,
|
||||
config.codex_home.to_path_buf(),
|
||||
SessionSource::Exec,
|
||||
/*parent_thread_id*/ None,
|
||||
config.model_verbosity,
|
||||
@@ -2360,6 +2361,7 @@ async fn azure_responses_request_includes_store_and_reasoning_ids() {
|
||||
thread_id,
|
||||
/*installation_id*/ "11111111-1111-4111-8111-111111111111".to_string(),
|
||||
provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
SessionSource::Exec,
|
||||
/*parent_thread_id*/ None,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -2155,6 +2155,7 @@ async fn websocket_harness_with_provider_options(
|
||||
thread_id,
|
||||
/*installation_id*/ TEST_INSTALLATION_ID.to_string(),
|
||||
provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
SessionSource::Exec,
|
||||
/*parent_thread_id*/ None,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_core::config::Config;
|
||||
@@ -26,6 +27,7 @@ struct ImageGenerationExtension {
|
||||
struct ImageGenerationExtensionConfig {
|
||||
enabled: bool,
|
||||
provider: ModelProviderInfo,
|
||||
codex_home: PathBuf,
|
||||
}
|
||||
|
||||
impl From<&Config> for ImageGenerationExtensionConfig {
|
||||
@@ -35,6 +37,7 @@ impl From<&Config> for ImageGenerationExtensionConfig {
|
||||
enabled: config.features.enabled(Feature::ImageGenExt)
|
||||
&& config.model_provider.is_openai(),
|
||||
provider: config.model_provider.clone(),
|
||||
codex_home: config.codex_home.to_path_buf(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -77,7 +80,11 @@ impl ToolContributor for ImageGenerationExtension {
|
||||
}
|
||||
|
||||
vec![Arc::new(ImageGenerationTool::new(CodexImagesBackend::new(
|
||||
create_model_provider(config.provider.clone(), Some(self.auth_manager.clone())),
|
||||
create_model_provider(
|
||||
config.provider.clone(),
|
||||
Some(self.auth_manager.clone()),
|
||||
Some(config.codex_home.clone()),
|
||||
),
|
||||
)))]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use codex_api::AllowedCaller;
|
||||
@@ -31,6 +32,7 @@ struct WebSearchExtension {
|
||||
struct WebSearchExtensionConfig {
|
||||
enabled: bool,
|
||||
provider: ModelProviderInfo,
|
||||
codex_home: PathBuf,
|
||||
settings: SearchSettings,
|
||||
}
|
||||
|
||||
@@ -42,6 +44,7 @@ impl From<&Config> for WebSearchExtensionConfig {
|
||||
&& config.model_provider.is_openai()
|
||||
&& web_search_mode != WebSearchMode::Disabled,
|
||||
provider: config.model_provider.clone(),
|
||||
codex_home: config.codex_home.to_path_buf(),
|
||||
settings: search_settings(config, web_search_mode),
|
||||
}
|
||||
}
|
||||
@@ -120,6 +123,7 @@ impl ToolContributor for WebSearchExtension {
|
||||
provider: create_model_provider(
|
||||
config.provider.clone(),
|
||||
Some(self.auth_manager.clone()),
|
||||
Some(config.codex_home.clone()),
|
||||
),
|
||||
settings: config.settings.clone(),
|
||||
})]
|
||||
|
||||
@@ -179,6 +179,7 @@ impl MemoryStartupContext {
|
||||
self.thread_id,
|
||||
installation_id,
|
||||
config.model_provider.clone(),
|
||||
config.codex_home.to_path_buf(),
|
||||
session_source,
|
||||
config_snapshot.parent_thread_id,
|
||||
config.model_verbosity,
|
||||
|
||||
@@ -26,11 +26,12 @@ codex-otel = { workspace = true }
|
||||
codex-protocol = { workspace = true }
|
||||
codex-response-debug-context = { workspace = true }
|
||||
http = { workspace = true }
|
||||
serde = { workspace = true, features = ["derive"] }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["sync", "time"] }
|
||||
tracing = { workspace = true, features = ["log"] }
|
||||
|
||||
[dev-dependencies]
|
||||
pretty_assertions = { workspace = true }
|
||||
serde_json = { workspace = true }
|
||||
tokio = { workspace = true, features = ["macros", "rt"] }
|
||||
wiremock = { workspace = true }
|
||||
|
||||
@@ -18,19 +18,29 @@ use crate::BearerAuthProvider;
|
||||
|
||||
use super::mantle::aws_auth_config;
|
||||
use super::mantle::region_from_config;
|
||||
use super::provider_auth::AmazonBedrockAuth;
|
||||
|
||||
const AWS_BEARER_TOKEN_BEDROCK_ENV_VAR: &str = "AWS_BEARER_TOKEN_BEDROCK";
|
||||
const AWS_REGION_ENV_VAR: &str = "AWS_REGION";
|
||||
const AWS_DEFAULT_REGION_ENV_VAR: &str = "AWS_DEFAULT_REGION";
|
||||
|
||||
pub(super) enum BedrockAuthMethod {
|
||||
StoredBearerToken { token: String, region: String },
|
||||
EnvBearerToken { token: String, region: String },
|
||||
AwsSdkAuth { context: AwsAuthContext },
|
||||
}
|
||||
|
||||
pub(super) async fn resolve_auth_method(
|
||||
stored_auth: Option<&AmazonBedrockAuth>,
|
||||
aws: &ModelProviderAwsAuthInfo,
|
||||
) -> Result<BedrockAuthMethod> {
|
||||
if let Some(auth) = stored_auth {
|
||||
return Ok(BedrockAuthMethod::StoredBearerToken {
|
||||
token: auth.bearer_token.clone(),
|
||||
region: auth.region.clone(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(token) = non_empty_env_var_from(AWS_BEARER_TOKEN_BEDROCK_ENV_VAR, std::env::var) {
|
||||
let region = bearer_token_region(aws, std::env::var)?;
|
||||
return Ok(BedrockAuthMethod::EnvBearerToken { token, region });
|
||||
@@ -44,10 +54,12 @@ pub(super) async fn resolve_auth_method(
|
||||
}
|
||||
|
||||
pub(super) async fn resolve_provider_auth(
|
||||
stored_auth: Option<&AmazonBedrockAuth>,
|
||||
aws: &ModelProviderAwsAuthInfo,
|
||||
) -> Result<SharedAuthProvider> {
|
||||
match resolve_auth_method(aws).await? {
|
||||
BedrockAuthMethod::EnvBearerToken { token, .. } => Ok(Arc::new(BearerAuthProvider {
|
||||
match resolve_auth_method(stored_auth, aws).await? {
|
||||
BedrockAuthMethod::StoredBearerToken { token, .. }
|
||||
| BedrockAuthMethod::EnvBearerToken { token, .. } => Ok(Arc::new(BearerAuthProvider {
|
||||
token: Some(token),
|
||||
account_id: None,
|
||||
is_fedramp_account: false,
|
||||
@@ -194,6 +206,36 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn stored_bedrock_bearer_auth_takes_precedence() {
|
||||
let auth = AmazonBedrockAuth {
|
||||
bearer_token: "stored-bedrock-key".to_string(),
|
||||
region: "eu-west-1".to_string(),
|
||||
};
|
||||
|
||||
let method = resolve_auth_method(
|
||||
Some(&auth),
|
||||
&ModelProviderAwsAuthInfo {
|
||||
profile: None,
|
||||
region: Some("us-east-1".to_string()),
|
||||
},
|
||||
)
|
||||
.await
|
||||
.expect("stored auth should resolve");
|
||||
|
||||
match method {
|
||||
BedrockAuthMethod::StoredBearerToken { token, region } => {
|
||||
assert_eq!(
|
||||
(token, region),
|
||||
("stored-bedrock-key".to_string(), "eu-west-1".to_string())
|
||||
);
|
||||
}
|
||||
BedrockAuthMethod::EnvBearerToken { .. } | BedrockAuthMethod::AwsSdkAuth { .. } => {
|
||||
panic!("stored auth should take precedence")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bedrock_bearer_auth_uses_aws_region_env() {
|
||||
let region = bearer_token_region(
|
||||
|
||||
@@ -5,6 +5,7 @@ use codex_protocol::error::Result;
|
||||
|
||||
use super::auth::BedrockAuthMethod;
|
||||
use super::auth::resolve_auth_method;
|
||||
use super::provider_auth::AmazonBedrockAuth;
|
||||
|
||||
const BEDROCK_MANTLE_SERVICE_NAME: &str = "bedrock-mantle";
|
||||
const BEDROCK_MANTLE_SUPPORTED_REGIONS: [&str; 12] = [
|
||||
@@ -39,7 +40,7 @@ pub(super) fn region_from_config(aws: &ModelProviderAwsAuthInfo) -> Option<Strin
|
||||
}
|
||||
|
||||
pub(super) fn base_url(region: &str) -> Result<String> {
|
||||
if BEDROCK_MANTLE_SUPPORTED_REGIONS.contains(®ion) {
|
||||
if is_supported_region(region) {
|
||||
Ok(format!("https://bedrock-mantle.{region}.api.aws/openai/v1"))
|
||||
} else {
|
||||
Err(CodexErr::Fatal(format!(
|
||||
@@ -48,14 +49,25 @@ pub(super) fn base_url(region: &str) -> Result<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn runtime_base_url(aws: &ModelProviderAwsAuthInfo) -> Result<String> {
|
||||
let region = resolve_region(aws).await?;
|
||||
pub fn is_supported_region(region: &str) -> bool {
|
||||
BEDROCK_MANTLE_SUPPORTED_REGIONS.contains(®ion)
|
||||
}
|
||||
|
||||
pub(super) async fn runtime_base_url(
|
||||
stored_auth: Option<&AmazonBedrockAuth>,
|
||||
aws: &ModelProviderAwsAuthInfo,
|
||||
) -> Result<String> {
|
||||
let region = resolve_region(stored_auth, aws).await?;
|
||||
base_url(®ion)
|
||||
}
|
||||
|
||||
async fn resolve_region(aws: &ModelProviderAwsAuthInfo) -> Result<String> {
|
||||
match resolve_auth_method(aws).await? {
|
||||
BedrockAuthMethod::EnvBearerToken { region, .. } => Ok(region),
|
||||
async fn resolve_region(
|
||||
stored_auth: Option<&AmazonBedrockAuth>,
|
||||
aws: &ModelProviderAwsAuthInfo,
|
||||
) -> Result<String> {
|
||||
match resolve_auth_method(stored_auth, aws).await? {
|
||||
BedrockAuthMethod::StoredBearerToken { region, .. }
|
||||
| BedrockAuthMethod::EnvBearerToken { region, .. } => Ok(region),
|
||||
BedrockAuthMethod::AwsSdkAuth { context } => Ok(context.region().to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
mod auth;
|
||||
mod catalog;
|
||||
mod mantle;
|
||||
mod provider_auth;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
@@ -25,17 +26,23 @@ use crate::provider::ProviderCapabilities;
|
||||
use auth::resolve_provider_auth;
|
||||
pub(crate) use catalog::static_model_catalog;
|
||||
use catalog::with_default_only_service_tier;
|
||||
pub use mantle::is_supported_region;
|
||||
use mantle::runtime_base_url;
|
||||
pub use provider_auth::AmazonBedrockAuth;
|
||||
pub use provider_auth::delete_amazon_bedrock_auth;
|
||||
pub use provider_auth::load_amazon_bedrock_auth;
|
||||
pub use provider_auth::save_amazon_bedrock_auth;
|
||||
|
||||
/// Runtime provider for Amazon Bedrock's OpenAI-compatible Mantle endpoint.
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct AmazonBedrockModelProvider {
|
||||
pub(crate) info: ModelProviderInfo,
|
||||
pub(crate) aws: ModelProviderAwsAuthInfo,
|
||||
codex_home: Option<PathBuf>,
|
||||
}
|
||||
|
||||
impl AmazonBedrockModelProvider {
|
||||
pub(crate) fn new(provider_info: ModelProviderInfo) -> Self {
|
||||
pub(crate) fn new(provider_info: ModelProviderInfo, codex_home: Option<PathBuf>) -> Self {
|
||||
let aws = provider_info
|
||||
.aws
|
||||
.clone()
|
||||
@@ -46,8 +53,20 @@ impl AmazonBedrockModelProvider {
|
||||
Self {
|
||||
info: provider_info,
|
||||
aws,
|
||||
codex_home,
|
||||
}
|
||||
}
|
||||
|
||||
fn stored_auth(&self) -> Result<Option<AmazonBedrockAuth>> {
|
||||
let Some(codex_home) = self.codex_home.as_deref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
load_amazon_bedrock_auth(codex_home).map_err(|err| {
|
||||
codex_protocol::error::CodexErr::Fatal(format!(
|
||||
"failed to load Amazon Bedrock auth: {err}"
|
||||
))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
@@ -85,16 +104,21 @@ impl ModelProvider for AmazonBedrockModelProvider {
|
||||
|
||||
async fn api_provider(&self) -> Result<Provider> {
|
||||
let mut api_provider_info = self.info.clone();
|
||||
api_provider_info.base_url = Some(runtime_base_url(&self.aws).await?);
|
||||
let stored_auth = self.stored_auth()?;
|
||||
api_provider_info.base_url = Some(runtime_base_url(stored_auth.as_ref(), &self.aws).await?);
|
||||
api_provider_info.to_api_provider(/*auth_mode*/ None)
|
||||
}
|
||||
|
||||
async fn runtime_base_url(&self) -> Result<Option<String>> {
|
||||
Ok(Some(runtime_base_url(&self.aws).await?))
|
||||
let stored_auth = self.stored_auth()?;
|
||||
Ok(Some(
|
||||
runtime_base_url(stored_auth.as_ref(), &self.aws).await?,
|
||||
))
|
||||
}
|
||||
|
||||
async fn api_auth(&self) -> Result<SharedAuthProvider> {
|
||||
resolve_provider_auth(&self.aws).await
|
||||
let stored_auth = self.stored_auth()?;
|
||||
resolve_provider_auth(stored_auth.as_ref(), &self.aws).await
|
||||
}
|
||||
|
||||
fn models_manager(
|
||||
@@ -135,6 +159,7 @@ mod tests {
|
||||
fn capabilities_disable_unsupported_hosted_tools() {
|
||||
let provider = AmazonBedrockModelProvider::new(
|
||||
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -151,6 +176,7 @@ mod tests {
|
||||
fn approval_review_preferred_model_uses_bedrock_gpt_5_4() {
|
||||
let provider = AmazonBedrockModelProvider::new(
|
||||
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::Path;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct AmazonBedrockAuth {
|
||||
pub bearer_token: String,
|
||||
pub region: String,
|
||||
}
|
||||
|
||||
pub fn save_amazon_bedrock_auth(
|
||||
codex_home: &Path,
|
||||
bearer_token: &str,
|
||||
region: &str,
|
||||
) -> io::Result<()> {
|
||||
let auth = AmazonBedrockAuth {
|
||||
bearer_token: bearer_token.to_string(),
|
||||
region: region.to_string(),
|
||||
};
|
||||
let auth_path = amazon_bedrock_auth_file(codex_home);
|
||||
if let Some(parent) = auth_path.parent() {
|
||||
fs::create_dir_all(parent)?;
|
||||
}
|
||||
let contents = serde_json::to_vec_pretty(&auth)?;
|
||||
fs::write(&auth_path, contents)?;
|
||||
restrict_file_permissions(&auth_path)
|
||||
}
|
||||
|
||||
pub fn load_amazon_bedrock_auth(codex_home: &Path) -> io::Result<Option<AmazonBedrockAuth>> {
|
||||
let auth_path = amazon_bedrock_auth_file(codex_home);
|
||||
let contents = match fs::read_to_string(&auth_path) {
|
||||
Ok(contents) => contents,
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => return Ok(None),
|
||||
Err(err) => return Err(err),
|
||||
};
|
||||
let auth = serde_json::from_str(&contents)?;
|
||||
Ok(Some(auth))
|
||||
}
|
||||
|
||||
pub fn delete_amazon_bedrock_auth(codex_home: &Path) -> io::Result<bool> {
|
||||
let auth_path = amazon_bedrock_auth_file(codex_home);
|
||||
match fs::remove_file(auth_path) {
|
||||
Ok(()) => Ok(true),
|
||||
Err(err) if err.kind() == io::ErrorKind::NotFound => Ok(false),
|
||||
Err(err) => Err(err),
|
||||
}
|
||||
}
|
||||
|
||||
fn amazon_bedrock_auth_file(codex_home: &Path) -> PathBuf {
|
||||
codex_home
|
||||
.join("model-providers")
|
||||
.join("amazon-bedrock")
|
||||
.join("auth.json")
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
fn restrict_file_permissions(auth_path: &Path) -> io::Result<()> {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
|
||||
fs::set_permissions(auth_path, fs::Permissions::from_mode(0o600))
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
fn restrict_file_permissions(_auth_path: &Path) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::time::SystemTime;
|
||||
use std::time::UNIX_EPOCH;
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn save_load_and_delete_amazon_bedrock_auth() {
|
||||
let codex_home = std::env::temp_dir().join(format!(
|
||||
"codex-bedrock-auth-test-{}-{}",
|
||||
std::process::id(),
|
||||
SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.expect("time")
|
||||
.as_nanos()
|
||||
));
|
||||
|
||||
save_amazon_bedrock_auth(&codex_home, "bedrock-key", "us-east-1").expect("save auth");
|
||||
|
||||
assert_eq!(
|
||||
load_amazon_bedrock_auth(&codex_home).expect("load auth"),
|
||||
Some(AmazonBedrockAuth {
|
||||
bearer_token: "bedrock-key".to_string(),
|
||||
region: "us-east-1".to_string(),
|
||||
})
|
||||
);
|
||||
assert!(delete_amazon_bedrock_auth(&codex_home).expect("delete auth"));
|
||||
assert_eq!(
|
||||
load_amazon_bedrock_auth(&codex_home).expect("load missing auth"),
|
||||
None
|
||||
);
|
||||
assert!(!delete_amazon_bedrock_auth(&codex_home).expect("delete missing auth"));
|
||||
let _ = fs::remove_dir_all(&codex_home);
|
||||
}
|
||||
}
|
||||
@@ -4,10 +4,15 @@ mod bearer_auth_provider;
|
||||
mod models_endpoint;
|
||||
mod provider;
|
||||
|
||||
pub use amazon_bedrock::delete_amazon_bedrock_auth;
|
||||
pub use amazon_bedrock::is_supported_region as is_supported_amazon_bedrock_region;
|
||||
pub use amazon_bedrock::load_amazon_bedrock_auth;
|
||||
pub use amazon_bedrock::save_amazon_bedrock_auth;
|
||||
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_model_provider_info::AMAZON_BEDROCK_PROVIDER_ID;
|
||||
pub use codex_protocol::account::ProviderAccount;
|
||||
pub use provider::ModelProvider;
|
||||
pub use provider::ProviderAccountError;
|
||||
|
||||
@@ -148,9 +148,10 @@ pub type SharedModelProvider = Arc<dyn ModelProvider>;
|
||||
pub fn create_model_provider(
|
||||
provider_info: ModelProviderInfo,
|
||||
auth_manager: Option<Arc<AuthManager>>,
|
||||
codex_home: Option<PathBuf>,
|
||||
) -> SharedModelProvider {
|
||||
if provider_info.is_amazon_bedrock() {
|
||||
Arc::new(AmazonBedrockModelProvider::new(provider_info))
|
||||
Arc::new(AmazonBedrockModelProvider::new(provider_info, codex_home))
|
||||
} else {
|
||||
Arc::new(ConfiguredModelProvider::new(provider_info, auth_manager))
|
||||
}
|
||||
@@ -356,6 +357,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
ModelProviderInfo::create_openai_provider(/*base_url*/ None),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(provider.capabilities(), ProviderCapabilities::default());
|
||||
@@ -366,6 +368,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
ModelProviderInfo::create_openai_provider(/*base_url*/ None),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -379,6 +382,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
provider_for("https://example.test/v1".to_string()),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -395,6 +399,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
provider_info_with_command_auth(),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
let auth_manager = provider
|
||||
@@ -414,6 +419,7 @@ mod tests {
|
||||
Some(AuthManager::from_auth_for_testing(CodexAuth::from_api_key(
|
||||
"openai-api-key",
|
||||
))),
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert!(provider.auth_manager().is_none());
|
||||
@@ -424,6 +430,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
ModelProviderInfo::create_openai_provider(/*base_url*/ None),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -442,6 +449,7 @@ mod tests {
|
||||
Some(AuthManager::from_auth_for_testing(CodexAuth::from_api_key(
|
||||
"openai-api-key",
|
||||
))),
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -464,6 +472,7 @@ mod tests {
|
||||
..Default::default()
|
||||
},
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -480,6 +489,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -496,6 +506,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
let manager =
|
||||
provider.models_manager(test_codex_home(), /*config_model_catalog*/ None);
|
||||
@@ -533,6 +544,7 @@ mod tests {
|
||||
let provider = create_model_provider(
|
||||
ModelProviderInfo::create_amazon_bedrock_provider(/*aws*/ None),
|
||||
/*auth_manager*/ None,
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
let manager = provider.models_manager(
|
||||
test_codex_home(),
|
||||
@@ -579,6 +591,7 @@ mod tests {
|
||||
Some(AuthManager::from_auth_for_testing(
|
||||
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
|
||||
)),
|
||||
/*codex_home*/ None,
|
||||
);
|
||||
|
||||
let manager =
|
||||
|
||||
+11
-3
@@ -589,8 +589,15 @@ fn active_turn_not_steerable_turn_error(error: &TypedRequestError) -> Option<App
|
||||
.then_some(turn_error)
|
||||
}
|
||||
|
||||
async fn resolve_runtime_model_provider_base_url(provider: &ModelProviderInfo) -> Option<String> {
|
||||
let provider = create_model_provider(provider.clone(), /*auth_manager*/ None);
|
||||
async fn resolve_runtime_model_provider_base_url(
|
||||
provider: &ModelProviderInfo,
|
||||
codex_home: &Path,
|
||||
) -> Option<String> {
|
||||
let provider = create_model_provider(
|
||||
provider.clone(),
|
||||
/*auth_manager*/ None,
|
||||
Some(codex_home.to_path_buf()),
|
||||
);
|
||||
match provider.runtime_base_url().await {
|
||||
Ok(base_url) => base_url,
|
||||
Err(err) => {
|
||||
@@ -839,7 +846,8 @@ impl App {
|
||||
);
|
||||
let runtime_model_provider_started_at = Instant::now();
|
||||
let runtime_model_provider_base_url =
|
||||
resolve_runtime_model_provider_base_url(&config.model_provider).await;
|
||||
resolve_runtime_model_provider_base_url(&config.model_provider, &config.codex_home)
|
||||
.await;
|
||||
let runtime_model_provider_ms = runtime_model_provider_started_at.elapsed().as_millis();
|
||||
|
||||
let enhanced_keys_supported = tui.enhanced_keys_supported();
|
||||
|
||||
Reference in New Issue
Block a user