mirror of
https://github.com/openai/codex.git
synced 2026-10-30 19:18:49 +00:00
## What changed - Rename ChatGPT-specific bundle loaders to `chatgpt_config_bundle_loader*` and use managed-config terminology for shared loader plumbing. - Pass `Config` directly when updating the app-server's managed configuration loader. - Share configuration loading, logging setup, and result handling between browser and device-code login. - Expose the CLI cloud-config helpers and `provider_uses_first_party_auth_path` through their library APIs. ## Testing Extend CLI login coverage to verify that `forced_login_method="api"` rejects both `login` and `login --device-auth` while preserving saved API-key credentials. GitOrigin-RevId: 9e8fa6110dc80882ba365e550b48ac77da604940
191 lines
7.5 KiB
Rust
191 lines
7.5 KiB
Rust
//! One-shot configuration discovery. Never initializes environments, thread storage, or plugins.
|
|
|
|
use crate::config_manager::ConfigManager;
|
|
use crate::request_processors::account_processor::clear_account_configuration;
|
|
use crate::request_processors::account_processor::logout_configured_account;
|
|
use crate::request_processors::config_errors::config_load_error;
|
|
use crate::request_processors::config_processor::map_requirements_to_api;
|
|
use codex_app_server_protocol::ClientInfo;
|
|
use codex_app_server_protocol::ConfigRequirementsReadResponse;
|
|
use codex_app_server_protocol::JSONRPCErrorError;
|
|
use codex_arg0::Arg0DispatchPaths;
|
|
use codex_config::LoaderOverrides;
|
|
use codex_config::NoopThreadConfigLoader;
|
|
use codex_core::config::find_codex_home;
|
|
use codex_login::AuthManager;
|
|
use codex_login::default_client::SetOriginatorError;
|
|
use codex_login::default_client::USER_AGENT_SUFFIX;
|
|
use codex_login::default_client::set_default_client_residency_requirement;
|
|
use codex_login::default_client::set_default_originator;
|
|
use codex_login::is_workload_identity_selected;
|
|
use codex_login::read_codex_access_token_from_env;
|
|
use codex_utils_cli::CliConfigOverrides;
|
|
use serde::Serialize;
|
|
use std::io;
|
|
use std::io::Write;
|
|
use std::sync::Arc;
|
|
|
|
#[derive(Debug, clap::Args)]
|
|
pub struct StartupConfigArgs {
|
|
/// Sign out using the managed auth path before loading fresh startup requirements.
|
|
#[arg(long, requires = "print_startup_config")]
|
|
pub startup_sign_out: bool,
|
|
|
|
/// Validate configuration and print requirements as JSON without starting the app server.
|
|
#[arg(long, conflicts_with_all = ["listen", "stdio", "remote_control", "managed_daemon"])]
|
|
pub print_startup_config: bool,
|
|
|
|
/// ClientInfo JSON used for configuration/auth request identity.
|
|
#[arg(long, requires = "print_startup_config", value_parser = |value: &str| serde_json::from_str::<ClientInfo>(value))]
|
|
pub startup_client_info: Option<ClientInfo>,
|
|
}
|
|
|
|
#[derive(Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
struct StartupConfig {
|
|
app_server_version: &'static str,
|
|
config_requirements: ConfigRequirementsReadResponse,
|
|
}
|
|
|
|
/// Writes exactly one JSON value, including structured errors on failure.
|
|
pub async fn print_startup_config(
|
|
arg0_paths: Arg0DispatchPaths,
|
|
cli_config_overrides: CliConfigOverrides,
|
|
loader_overrides: LoaderOverrides,
|
|
strict_config: bool,
|
|
startup: StartupConfigArgs,
|
|
) -> io::Result<()> {
|
|
let result = read_startup_config(
|
|
arg0_paths,
|
|
cli_config_overrides,
|
|
loader_overrides,
|
|
strict_config,
|
|
startup,
|
|
)
|
|
.await;
|
|
let output = match &result {
|
|
Ok(config) => serde_json::to_vec(config)?,
|
|
Err(error) => serde_json::to_vec(&serde_json::json!({ "error": error }))?,
|
|
};
|
|
let mut stdout = io::stdout().lock();
|
|
stdout.write_all(&output)?;
|
|
stdout.write_all(b"\n")?;
|
|
stdout.flush()?;
|
|
result
|
|
.map(|_| ())
|
|
.map_err(|_| io::Error::other("startup configuration unavailable"))
|
|
}
|
|
|
|
async fn read_startup_config(
|
|
arg0_paths: Arg0DispatchPaths,
|
|
cli_config_overrides: CliConfigOverrides,
|
|
loader_overrides: LoaderOverrides,
|
|
strict_config: bool,
|
|
startup: StartupConfigArgs,
|
|
) -> Result<StartupConfig, JSONRPCErrorError> {
|
|
if startup.startup_sign_out
|
|
&& (is_workload_identity_selected() || read_codex_access_token_from_env().is_some())
|
|
{
|
|
return Err(crate::error_code::invalid_request(
|
|
"Configured external authentication is owned by the app-server host and cannot be changed during startup recovery.",
|
|
));
|
|
}
|
|
if let Some(client) = startup.startup_client_info {
|
|
match set_default_originator(client.name.clone()) {
|
|
Ok(()) | Err(SetOriginatorError::AlreadyInitialized) => {}
|
|
Err(SetOriginatorError::InvalidHeaderValue) => {
|
|
return Err(crate::error_code::invalid_params("invalid client name"));
|
|
}
|
|
}
|
|
if let Ok(mut suffix) = USER_AGENT_SUFFIX.lock() {
|
|
*suffix = Some(format!("{}; {}", client.name, client.version));
|
|
}
|
|
}
|
|
let loader_overrides = crate::loader_overrides_with_test_user_config_file(
|
|
loader_overrides,
|
|
crate::test_user_config_file_from_env(),
|
|
)
|
|
.map_err(startup_error)?;
|
|
let overrides = cli_config_overrides
|
|
.parse_overrides()
|
|
.map_err(|error| startup_error(io::Error::new(io::ErrorKind::InvalidInput, error)))?;
|
|
let manager = ConfigManager::new(
|
|
find_codex_home().map_err(startup_error)?.to_path_buf(),
|
|
overrides,
|
|
loader_overrides,
|
|
strict_config,
|
|
Default::default(),
|
|
arg0_paths,
|
|
Arc::new(NoopThreadConfigLoader),
|
|
);
|
|
let cwd = std::env::current_dir().ok();
|
|
let loaded = manager
|
|
.load_managed_startup_config(cwd.clone())
|
|
.await
|
|
.map_err(startup_error)?;
|
|
let config = match loaded.config {
|
|
Ok(config) => config,
|
|
// Recovery retains local credential settings when cloud policy is unavailable.
|
|
Err(_) if startup.startup_sign_out => loaded.bootstrap.clone(),
|
|
Err(error) => return Err(startup_error(error)),
|
|
};
|
|
let config = if startup.startup_sign_out {
|
|
// Preserve local credential authority while using the effective HTTP route.
|
|
let mut auth_config = loaded.bootstrap.auth_config();
|
|
auth_config.auth_route_config = config.auth_route_config();
|
|
auth_config.chatgpt_base_url = Some(config.chatgpt_base_url.clone());
|
|
let auth = AuthManager::shared_from_auth_config(
|
|
auth_config,
|
|
/*enable_codex_api_key_env*/ false,
|
|
)
|
|
.await
|
|
.map_err(|error| startup_error(io::Error::other(error)))?;
|
|
set_default_client_residency_requirement(config.enforce_residency.value());
|
|
let config = logout_configured_account(&manager, &auth, &config).await?;
|
|
clear_account_configuration(&manager, &config).await?;
|
|
manager.update_managed_config_loader(Arc::clone(&auth), &config);
|
|
manager
|
|
.load_latest_config(cwd)
|
|
.await
|
|
.map_err(startup_error)?
|
|
} else {
|
|
config
|
|
};
|
|
// Validate and emit the fetched snapshot, or the fresh signed-out snapshot.
|
|
config
|
|
.validate_windows_mxc_requirement()
|
|
.map_err(startup_error)?;
|
|
let auth_config = config.auth_config();
|
|
auth_config.validate().map_err(startup_error)?;
|
|
let allowed_login_methods = auth_config.managed_auth_policy.allowed_login_methods(
|
|
auth_config.forced_login_method,
|
|
auth_config.forced_chatgpt_workspace_id.as_deref(),
|
|
);
|
|
let requirements = config.config_layer_stack.requirements_toml();
|
|
Ok(StartupConfig {
|
|
app_server_version: env!("CARGO_PKG_VERSION"),
|
|
config_requirements: ConfigRequirementsReadResponse {
|
|
supports_independent_speed_modes: Some(true),
|
|
requirements: map_requirements_to_api(
|
|
(!requirements.is_empty()).then(|| requirements.clone()),
|
|
allowed_login_methods,
|
|
),
|
|
},
|
|
})
|
|
}
|
|
|
|
fn startup_error(error: io::Error) -> JSONRPCErrorError {
|
|
let mut response = config_load_error(&error);
|
|
if response.data.is_none() {
|
|
let warning = crate::config_warning_from_error("Invalid configuration", &error);
|
|
response.data = Some(serde_json::json!({
|
|
"reason": "configLoad",
|
|
"filePath": warning.path,
|
|
"line": warning.range.as_ref().map(|range| range.start.line),
|
|
"column": warning.range.as_ref().map(|range| range.start.column),
|
|
"detail": error.to_string(),
|
|
}));
|
|
}
|
|
response
|
|
}
|