mirror of
https://github.com/openai/codex.git
synced 2026-10-19 16:17:49 +00:00
Add process-scoped PSP routing for ChatGPT requests (#36986)
## What changed - Add a hidden global `--psp` runtime flag and propagate it through TUI, exec, app-server, remote-control, and in-process startup paths. - Attach the `oai-chat-psp=true` cookie to first-party ChatGPT requests when enabled, using a cached cookie-aware client with sensitive request logging disabled. - Keep the routing selection out of persistent configuration layers while preserving it across config refreshes and agent role changes. ## Testing - Cover global flag parsing, app-server propagation, config-layer isolation, and preservation across config rebuilds and role changes. GitOrigin-RevId: 05cdc61ffd7162d8e48fc1e166f4732113e5a816
This commit is contained in:
Generated
+1
@@ -2342,6 +2342,7 @@ dependencies = [
|
||||
"codex-connectors",
|
||||
"codex-core",
|
||||
"codex-git-utils",
|
||||
"codex-http-client",
|
||||
"codex-login",
|
||||
"codex-model-provider",
|
||||
"codex-plugin",
|
||||
|
||||
@@ -35,6 +35,7 @@ pub(crate) struct ConfigManager {
|
||||
cloud_config_bundle: Arc<RwLock<CloudConfigBundleLoader>>,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>,
|
||||
pub(crate) psp: bool,
|
||||
}
|
||||
|
||||
impl ConfigManager {
|
||||
@@ -56,6 +57,7 @@ impl ConfigManager {
|
||||
cloud_config_bundle: Arc::new(RwLock::new(cloud_config_bundle)),
|
||||
arg0_paths,
|
||||
thread_config_loader: Arc::new(RwLock::new(thread_config_loader)),
|
||||
psp: false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,6 +186,7 @@ impl ConfigManager {
|
||||
TomlValue::Table(toml::map::Map::new()),
|
||||
)?;
|
||||
}
|
||||
config.psp = self.psp;
|
||||
self.apply_runtime_feature_enablement(&mut config);
|
||||
self.apply_arg0_paths(&mut config);
|
||||
Ok(config)
|
||||
@@ -244,6 +247,7 @@ impl ConfigManager {
|
||||
.map(|(key, value)| (key, json_to_toml(value))),
|
||||
)
|
||||
.collect::<Vec<_>>();
|
||||
typesafe_overrides.psp = Some(self.psp);
|
||||
|
||||
let mut config = codex_core::config::ConfigBuilder::default()
|
||||
.codex_home(self.codex_home.clone())
|
||||
|
||||
@@ -106,6 +106,41 @@ personality = true
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn process_routing_does_not_enter_config_layers() -> Result<()> {
|
||||
let tmp = tempdir()?;
|
||||
let mut service = ConfigManager::new_for_tests(
|
||||
tmp.path().to_path_buf(),
|
||||
Vec::new(),
|
||||
LoaderOverrides::without_managed_config_for_tests(),
|
||||
CloudConfigBundleLoader::default(),
|
||||
);
|
||||
service.psp = true;
|
||||
|
||||
let config = service
|
||||
.load_with_overrides(
|
||||
Some(
|
||||
[("features".to_string(), serde_json::json!({ "apps": true }))]
|
||||
.into_iter()
|
||||
.collect(),
|
||||
),
|
||||
Default::default(),
|
||||
)
|
||||
.await?;
|
||||
|
||||
assert!(config.psp);
|
||||
assert!(config.http_client_factory().has_chatgpt_cookies());
|
||||
assert!(
|
||||
config
|
||||
.config_layer_stack
|
||||
.effective_config()
|
||||
.get("features")
|
||||
.and_then(|features| features.get("psp"))
|
||||
.is_none()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_missing_nested_config_is_noop() -> Result<()> {
|
||||
let tmp = tempdir().expect("tempdir");
|
||||
|
||||
@@ -445,7 +445,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
|
||||
));
|
||||
|
||||
let processor_outgoing = Arc::clone(&outgoing_message_sender);
|
||||
let config_manager = ConfigManager::new(
|
||||
let mut config_manager = ConfigManager::new(
|
||||
args.config.codex_home.to_path_buf(),
|
||||
args.cli_overrides,
|
||||
args.loader_overrides,
|
||||
@@ -454,6 +454,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
|
||||
args.arg0_paths.clone(),
|
||||
args.thread_config_loader,
|
||||
);
|
||||
config_manager.psp = args.config.psp;
|
||||
let (processor_tx, mut processor_rx) = mpsc::channel::<ProcessorCommand>(channel_capacity);
|
||||
let mut processor_handle = tokio::spawn(async move {
|
||||
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
|
||||
|
||||
@@ -437,6 +437,7 @@ pub struct AppServerRuntimeOptions {
|
||||
pub plugin_startup_tasks: PluginStartupTasks,
|
||||
pub remote_control_startup_mode: RemoteControlStartupMode,
|
||||
pub install_shutdown_signal_handler: bool,
|
||||
pub psp: bool,
|
||||
}
|
||||
|
||||
impl Default for AppServerRuntimeOptions {
|
||||
@@ -446,6 +447,7 @@ impl Default for AppServerRuntimeOptions {
|
||||
plugin_startup_tasks: PluginStartupTasks::Start,
|
||||
remote_control_startup_mode: RemoteControlStartupMode::ResolvePersisted,
|
||||
install_shutdown_signal_handler: true,
|
||||
psp: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -486,7 +488,7 @@ pub async fn run_main_with_transport_options(
|
||||
arg0_paths.codex_linux_sandbox_exe.clone(),
|
||||
)?;
|
||||
let ignore_user_config = loader_overrides.ignore_user_config;
|
||||
let config_manager = ConfigManager::new(
|
||||
let mut config_manager = ConfigManager::new(
|
||||
codex_home.to_path_buf(),
|
||||
cli_kv_overrides.clone(),
|
||||
loader_overrides,
|
||||
@@ -495,6 +497,7 @@ pub async fn run_main_with_transport_options(
|
||||
arg0_paths.clone(),
|
||||
Arc::new(NoopThreadConfigLoader),
|
||||
);
|
||||
config_manager.psp = runtime_options.psp;
|
||||
match config_manager
|
||||
.load_latest_config(/*fallback_cwd*/ None)
|
||||
.await
|
||||
|
||||
@@ -60,6 +60,10 @@ struct AppServerArgs {
|
||||
/// Enable remote control for this app-server process without changing persistence.
|
||||
#[arg(long = "remote-control", hide = true)]
|
||||
remote_control: bool,
|
||||
|
||||
/// Enable process-only PSP routing for first-party ChatGPT requests.
|
||||
#[arg(long, hide = true)]
|
||||
psp: bool,
|
||||
}
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
@@ -75,6 +79,7 @@ fn main() -> anyhow::Result<()> {
|
||||
#[cfg(debug_assertions)]
|
||||
disable_plugin_startup_tasks_for_tests,
|
||||
remote_control,
|
||||
psp,
|
||||
} = AppServerArgs::parse();
|
||||
let loader_overrides = if disable_managed_config_from_debug_env() {
|
||||
LoaderOverrides::without_managed_config_for_tests()
|
||||
@@ -87,6 +92,7 @@ fn main() -> anyhow::Result<()> {
|
||||
let auth = auth.try_into_settings()?;
|
||||
let mut runtime_options = AppServerRuntimeOptions {
|
||||
code_mode_host_transport: code_mode_host.into(),
|
||||
psp,
|
||||
..Default::default()
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
|
||||
@@ -46,6 +46,7 @@ fn app_server_accepts_process_scoped_code_mode_host() {
|
||||
"wss://example.test/code-mode",
|
||||
"--listen",
|
||||
"off",
|
||||
"--psp",
|
||||
])
|
||||
.expect("parse app-server args");
|
||||
|
||||
@@ -54,6 +55,7 @@ fn app_server_accepts_process_scoped_code_mode_host() {
|
||||
Some(Url::parse("wss://example.test/code-mode").expect("test endpoint should parse"))
|
||||
);
|
||||
assert_eq!(args.listen, AppServerTransport::Off);
|
||||
assert!(args.psp);
|
||||
assert_eq!(args.config_overrides.raw_overrides, Vec::<String>::new());
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ clap = { workspace = true, features = ["derive"] }
|
||||
codex-connectors = { workspace = true }
|
||||
codex-core = { workspace = true }
|
||||
codex-git-utils = { workspace = true }
|
||||
codex-http-client = { workspace = true }
|
||||
codex-login = { workspace = true }
|
||||
codex-model-provider = { workspace = true }
|
||||
codex-plugin = { workspace = true }
|
||||
|
||||
@@ -1,16 +1,57 @@
|
||||
use codex_core::config::Config;
|
||||
use codex_http_client::HttpClient;
|
||||
use codex_http_client::HttpClientFactory;
|
||||
use codex_login::AuthManager;
|
||||
use codex_login::CodexAuth;
|
||||
use codex_login::default_client::RESIDENCY_HEADER_NAME;
|
||||
use codex_login::default_client::create_client;
|
||||
use codex_login::default_client::create_client_with_chatgpt_cookies;
|
||||
use codex_login::default_client::default_headers;
|
||||
|
||||
use anyhow::Context;
|
||||
use serde::Serialize;
|
||||
use serde::de::DeserializeOwned;
|
||||
use std::sync::LazyLock;
|
||||
use std::sync::Mutex;
|
||||
use std::time::Duration;
|
||||
|
||||
const OAI_PRODUCT_SKU_HEADER: &str = "OAI-Product-Sku";
|
||||
const CODEX_PRODUCT_SKU: &str = "codex";
|
||||
|
||||
struct CachedChatGptClient {
|
||||
factory: HttpClientFactory,
|
||||
residency: Option<Vec<u8>>,
|
||||
client: HttpClient,
|
||||
}
|
||||
|
||||
static PSP_CHATGPT_CLIENT: LazyLock<Mutex<Option<CachedChatGptClient>>> =
|
||||
LazyLock::new(|| Mutex::new(None));
|
||||
|
||||
/// Reuse the default client while retaining its configured ChatGPT cookies.
|
||||
fn psp_chatgpt_client(config: &Config) -> HttpClient {
|
||||
let factory = config.http_client_factory();
|
||||
let residency = default_headers()
|
||||
.get(RESIDENCY_HEADER_NAME)
|
||||
.map(|value| value.as_bytes().to_vec());
|
||||
let mut cached = PSP_CHATGPT_CLIENT
|
||||
.lock()
|
||||
.unwrap_or_else(std::sync::PoisonError::into_inner);
|
||||
if let Some(cached_client) = cached.as_ref()
|
||||
&& cached_client.factory == factory
|
||||
&& cached_client.residency == residency
|
||||
{
|
||||
return cached_client.client.clone();
|
||||
}
|
||||
|
||||
let client = create_client_with_chatgpt_cookies(&factory);
|
||||
*cached = Some(CachedChatGptClient {
|
||||
factory,
|
||||
residency,
|
||||
client: client.clone(),
|
||||
});
|
||||
client
|
||||
}
|
||||
|
||||
/// Make a GET request to the ChatGPT backend API.
|
||||
pub(crate) async fn chatgpt_get_request<T: DeserializeOwned>(
|
||||
config: &Config,
|
||||
@@ -40,24 +81,26 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
|
||||
"ChatGPT account ID not available, please re-run `codex login`"
|
||||
);
|
||||
|
||||
// Make direct HTTP request to ChatGPT backend API with the token
|
||||
let client = create_client();
|
||||
let url = format!(
|
||||
"{}/{}",
|
||||
chatgpt_base_url.trim_end_matches('/'),
|
||||
path.trim_start_matches('/')
|
||||
);
|
||||
|
||||
let client = if config.psp {
|
||||
psp_chatgpt_client(config)
|
||||
} else {
|
||||
create_client()
|
||||
};
|
||||
let mut request = client
|
||||
.get(&url)
|
||||
.headers(default_headers())
|
||||
.headers(codex_model_provider::auth_provider_from_auth(&auth).to_auth_headers())
|
||||
.header(OAI_PRODUCT_SKU_HEADER, CODEX_PRODUCT_SKU)
|
||||
.header("Content-Type", "application/json");
|
||||
|
||||
if let Some(timeout) = timeout {
|
||||
request = request.timeout(timeout);
|
||||
}
|
||||
|
||||
let response = request.send().await.context("Failed to send request")?;
|
||||
|
||||
if response.status().is_success() {
|
||||
@@ -102,8 +145,14 @@ pub(crate) async fn chatgpt_post_request_with_timeout<
|
||||
config.chatgpt_base_url.trim_end_matches('/'),
|
||||
path.trim_start_matches('/')
|
||||
);
|
||||
let response = create_client()
|
||||
let client = if config.psp {
|
||||
psp_chatgpt_client(config)
|
||||
} else {
|
||||
create_client()
|
||||
};
|
||||
let response = client
|
||||
.post(&url)
|
||||
.headers(default_headers())
|
||||
.headers(codex_model_provider::auth_provider_from_auth(auth).to_auth_headers())
|
||||
.header(OAI_PRODUCT_SKU_HEADER, product_sku)
|
||||
.header("Content-Type", "application/json")
|
||||
|
||||
@@ -104,6 +104,10 @@ use codex_terminal_detection::TerminalName;
|
||||
override_usage = "codex [OPTIONS] [PROMPT]\n codex [OPTIONS] <COMMAND> [ARGS]"
|
||||
)]
|
||||
struct MultitoolCli {
|
||||
/// Enable process-only PSP routing for first-party ChatGPT requests.
|
||||
#[arg(long, global = true, hide = true)]
|
||||
psp: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
|
||||
@@ -977,12 +981,14 @@ async fn cli_main(
|
||||
remote_control_disabled: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let MultitoolCli {
|
||||
psp,
|
||||
config_overrides: mut root_config_overrides,
|
||||
feature_toggles,
|
||||
remote,
|
||||
mut interactive,
|
||||
subcommand,
|
||||
} = MultitoolCli::parse();
|
||||
interactive.psp = psp;
|
||||
|
||||
// Fold --enable/--disable into config overrides so they flow to all subcommands.
|
||||
let toggle_overrides = feature_toggles.to_overrides()?;
|
||||
@@ -1022,6 +1028,7 @@ async fn cli_main(
|
||||
exec_cli
|
||||
.shared
|
||||
.inherit_exec_root_options(&interactive.shared);
|
||||
exec_cli.psp = psp;
|
||||
exec_cli.strict_config |= root_strict_config;
|
||||
prepend_config_flags(
|
||||
&mut exec_cli.config_overrides,
|
||||
@@ -1042,6 +1049,7 @@ async fn cli_main(
|
||||
exec_cli
|
||||
.shared
|
||||
.inherit_exec_root_options(&interactive.shared);
|
||||
exec_cli.psp = psp;
|
||||
exec_cli.command = Some(ExecCommand::Review(review_args));
|
||||
exec_cli.strict_config = strict_config || root_strict_config;
|
||||
prepend_config_flags(
|
||||
@@ -1151,6 +1159,7 @@ async fn cli_main(
|
||||
codex_app_server::RemoteControlStartupMode::ResolvePersisted
|
||||
}
|
||||
},
|
||||
psp,
|
||||
..Default::default()
|
||||
};
|
||||
codex_app_server::run_main_with_transport_options(
|
||||
@@ -1253,6 +1262,7 @@ async fn cli_main(
|
||||
remote_control_cli,
|
||||
arg0_paths.clone(),
|
||||
root_config_overrides,
|
||||
psp,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
@@ -2707,6 +2717,7 @@ mod tests {
|
||||
fn finalize_resume_from_args(args: &[&str]) -> TuiCli {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let MultitoolCli {
|
||||
psp: _,
|
||||
mut interactive,
|
||||
config_overrides: mut root_overrides,
|
||||
subcommand,
|
||||
@@ -2744,6 +2755,7 @@ mod tests {
|
||||
fn finalize_fork_from_args(args: &[&str]) -> TuiCli {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let MultitoolCli {
|
||||
psp: _,
|
||||
mut interactive,
|
||||
config_overrides: mut root_overrides,
|
||||
subcommand,
|
||||
@@ -2788,6 +2800,7 @@ mod tests {
|
||||
fn finalize_archive_from_args(args: &[&str]) -> (String, TuiCli, InteractiveRemoteOptions) {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse");
|
||||
let MultitoolCli {
|
||||
psp: _,
|
||||
interactive,
|
||||
config_overrides: root_overrides,
|
||||
subcommand,
|
||||
@@ -4016,6 +4029,19 @@ mod tests {
|
||||
assert!(err.to_string().contains("is empty"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn psp_is_a_global_runtime_argument() {
|
||||
for args in [
|
||||
["codex", "--psp"].as_slice(),
|
||||
["codex", "app-server", "--psp"].as_slice(),
|
||||
["codex", "remote-control", "--psp"].as_slice(),
|
||||
] {
|
||||
let cli = MultitoolCli::try_parse_from(args).expect("parse runtime PSP flag");
|
||||
assert!(cli.psp);
|
||||
assert!(cli.config_overrides.raw_overrides.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_code_mode_host_url_parses_independently_of_listen_transport() {
|
||||
let app_server = app_server_from_args(
|
||||
|
||||
@@ -65,6 +65,7 @@ pub(crate) async fn run(
|
||||
command: RemoteControlCommand,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
root_config_overrides: CliConfigOverrides,
|
||||
psp: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
match command.subcommand {
|
||||
None => {
|
||||
@@ -72,7 +73,8 @@ pub(crate) async fn run(
|
||||
command.json,
|
||||
"Starting app-server with remote control enabled...",
|
||||
)?;
|
||||
run_foreground_remote_control(command.json, arg0_paths, root_config_overrides).await?;
|
||||
run_foreground_remote_control(command.json, arg0_paths, root_config_overrides, psp)
|
||||
.await?;
|
||||
}
|
||||
Some(RemoteControlSubcommand::Start) => {
|
||||
print_remote_control_progress(
|
||||
@@ -111,6 +113,7 @@ async fn run_foreground_remote_control(
|
||||
json: bool,
|
||||
arg0_paths: Arg0DispatchPaths,
|
||||
root_config_overrides: CliConfigOverrides,
|
||||
psp: bool,
|
||||
) -> anyhow::Result<()> {
|
||||
let socket_dir = tempfile::Builder::new()
|
||||
.prefix("codex-rc-")
|
||||
@@ -126,6 +129,7 @@ async fn run_foreground_remote_control(
|
||||
let runtime_options = AppServerRuntimeOptions {
|
||||
remote_control_startup_mode: codex_app_server::RemoteControlStartupMode::EnabledEphemeral,
|
||||
install_shutdown_signal_handler: false,
|
||||
psp,
|
||||
..Default::default()
|
||||
};
|
||||
let (stop_rx, stop_signal_task) = foreground_stop_signal();
|
||||
|
||||
@@ -263,6 +263,7 @@ mod reload {
|
||||
.flatten(),
|
||||
model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()),
|
||||
service_tier: preserve_current_service_tier.then(|| config.service_tier.clone()),
|
||||
psp: Some(config.psp),
|
||||
codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
|
||||
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
|
||||
..Default::default()
|
||||
|
||||
@@ -161,6 +161,7 @@ async fn apply_role_preserves_unspecified_keys() {
|
||||
.await;
|
||||
config.codex_linux_sandbox_exe = Some(PathBuf::from("/tmp/codex-linux-sandbox"));
|
||||
config.main_execve_wrapper_exe = Some(PathBuf::from("/tmp/codex-execve-wrapper"));
|
||||
config.psp = true;
|
||||
let role_path = write_role_config(
|
||||
&home,
|
||||
"instructions-only.toml",
|
||||
@@ -195,6 +196,7 @@ async fn apply_role_preserves_unspecified_keys() {
|
||||
config.main_execve_wrapper_exe,
|
||||
Some(PathBuf::from("/tmp/codex-execve-wrapper"))
|
||||
);
|
||||
assert!(config.psp);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
|
||||
@@ -5009,6 +5009,7 @@ async fn rebuild_preserving_session_layers_refreshes_requirements() -> std::io::
|
||||
refreshed_toml,
|
||||
ConfigOverrides {
|
||||
cwd: Some(codex_home.path().to_path_buf()),
|
||||
psp: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
codex_home.abs(),
|
||||
@@ -5086,6 +5087,8 @@ async fn rebuild_preserving_session_layers_refreshes_requirements() -> std::io::
|
||||
.rebuild_preserving_session_layers(&refreshed_config)
|
||||
.await?;
|
||||
|
||||
assert!(config.psp);
|
||||
assert!(config.http_client_factory().has_chatgpt_cookies());
|
||||
assert_eq!(
|
||||
config.mcp_servers.get(),
|
||||
&HashMap::from([
|
||||
|
||||
@@ -117,6 +117,7 @@ pub use codex_thread_store::ExtraConfig;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
use codex_utils_absolute_path::AbsolutePathBufGuard;
|
||||
use codex_utils_path_uri::PathUri;
|
||||
use http::HeaderValue;
|
||||
use rmcp::model::ElicitationCapability;
|
||||
use rmcp::model::FormElicitationCapability;
|
||||
use rmcp::model::UrlElicitationCapability;
|
||||
@@ -975,6 +976,9 @@ pub struct Config {
|
||||
/// Whether Codex-owned clients should respect host system proxy settings.
|
||||
pub respect_system_proxy: bool,
|
||||
|
||||
/// Process-only ChatGPT routing selection supplied when Codex is launched.
|
||||
pub psp: bool,
|
||||
|
||||
/// Optional product SKU forwarded to the host-owned apps MCP server.
|
||||
pub apps_mcp_product_sku: Option<String>,
|
||||
|
||||
@@ -1621,7 +1625,12 @@ impl Config {
|
||||
} else {
|
||||
OutboundProxyPolicy::ReqwestDefault
|
||||
};
|
||||
HttpClientFactory::new(outbound_proxy_policy)
|
||||
let factory = HttpClientFactory::new(outbound_proxy_policy);
|
||||
if self.psp {
|
||||
factory.with_chatgpt_cookies([HeaderValue::from_static("oai-chat-psp=true")])
|
||||
} else {
|
||||
factory
|
||||
}
|
||||
}
|
||||
|
||||
/// Build the plugin-manager input from the effective config.
|
||||
@@ -1822,6 +1831,7 @@ impl Config {
|
||||
ConfigOverrides {
|
||||
cwd: Some(self.cwd.to_path_buf()),
|
||||
default_zsh_path,
|
||||
psp: Some(refreshed_config.psp),
|
||||
..Default::default()
|
||||
},
|
||||
refreshed_config.codex_home.clone(),
|
||||
@@ -2565,6 +2575,7 @@ pub struct ConfigOverrides {
|
||||
pub tools_web_search_request: Option<bool>,
|
||||
pub ephemeral: Option<bool>,
|
||||
pub bypass_hook_trust: Option<bool>,
|
||||
pub psp: Option<bool>,
|
||||
/// Additional directories that should be treated as writable roots for this session.
|
||||
pub additional_writable_roots: Vec<PathBuf>,
|
||||
/// Explicit absolute runtime workspace roots for this session. When set,
|
||||
@@ -3236,6 +3247,7 @@ impl Config {
|
||||
tools_web_search_request: override_tools_web_search_request,
|
||||
ephemeral,
|
||||
bypass_hook_trust,
|
||||
psp,
|
||||
additional_writable_roots,
|
||||
workspace_roots: workspace_roots_override,
|
||||
} = overrides;
|
||||
@@ -4152,6 +4164,7 @@ impl Config {
|
||||
.chatgpt_base_url
|
||||
.unwrap_or("https://chatgpt.com/backend-api/".to_string()),
|
||||
respect_system_proxy,
|
||||
psp: psp.unwrap_or_default(),
|
||||
apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(),
|
||||
realtime_audio: cfg
|
||||
.audio
|
||||
|
||||
@@ -12,6 +12,10 @@ use std::path::PathBuf;
|
||||
override_usage = "codex exec [OPTIONS] [PROMPT]\n codex exec [OPTIONS] <COMMAND> [ARGS]"
|
||||
)]
|
||||
pub struct Cli {
|
||||
/// Process-only PSP routing selected by the parent Codex CLI.
|
||||
#[clap(skip)]
|
||||
pub psp: bool,
|
||||
|
||||
/// Action to perform. If omitted, runs a new non-interactive session.
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
|
||||
@@ -244,6 +244,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
}
|
||||
|
||||
let Cli {
|
||||
psp,
|
||||
command,
|
||||
strict_config,
|
||||
shared,
|
||||
@@ -440,6 +441,7 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
|
||||
tools_web_search_request: None,
|
||||
ephemeral: ephemeral.then_some(true),
|
||||
bypass_hook_trust: bypass_hook_trust.then_some(true),
|
||||
psp: Some(psp),
|
||||
additional_writable_roots: add_dir,
|
||||
};
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ use codex_utils_cli::CliConfigOverrides;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct TopCli {
|
||||
#[arg(long, global = true, hide = true)]
|
||||
psp: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
config_overrides: CliConfigOverrides,
|
||||
|
||||
@@ -30,6 +33,7 @@ fn main() -> anyhow::Result<()> {
|
||||
let top_cli = TopCli::parse();
|
||||
// Merge root-level overrides into inner CLI struct so downstream logic remains unchanged.
|
||||
let mut inner = top_cli.inner;
|
||||
inner.psp = top_cli.psp;
|
||||
inner
|
||||
.config_overrides
|
||||
.prepend_root_overrides(top_cli.config_overrides);
|
||||
|
||||
@@ -7,6 +7,7 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
|
||||
let cli = TopCli::parse_from([
|
||||
"codex-exec",
|
||||
"resume",
|
||||
"--psp",
|
||||
"--strict-config",
|
||||
"--last",
|
||||
"--json",
|
||||
@@ -18,6 +19,7 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
|
||||
"--skip-git-repo-check",
|
||||
PROMPT,
|
||||
]);
|
||||
assert!(cli.psp);
|
||||
let mut inner = cli.inner;
|
||||
inner
|
||||
.config_overrides
|
||||
|
||||
@@ -222,6 +222,15 @@ pub fn create_client() -> HttpClient {
|
||||
build_default_client(default_http_client_builder())
|
||||
}
|
||||
|
||||
/// Creates the default client with configured ChatGPT cookies and no sensitive-response logging.
|
||||
pub fn create_client_with_chatgpt_cookies(http_client_factory: &HttpClientFactory) -> HttpClient {
|
||||
build_default_client(
|
||||
default_http_client_builder()
|
||||
.with_chatgpt_cookies(http_client_factory)
|
||||
.without_request_logging(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Create the default HTTP client without request URL or response-header diagnostics.
|
||||
///
|
||||
/// This preserves the default client's legacy custom-CA fallback and transport proxy behavior while
|
||||
|
||||
@@ -187,6 +187,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
|
||||
config_layer_stack: ConfigLayerStack::default(),
|
||||
startup_warnings: Vec::new(),
|
||||
bypass_hook_trust: false,
|
||||
psp: false,
|
||||
model,
|
||||
service_tier: None,
|
||||
review_model: None,
|
||||
|
||||
@@ -8,6 +8,10 @@ use codex_utils_cli::SharedCliOptions;
|
||||
#[derive(Parser, Clone, Debug)]
|
||||
#[command(version)]
|
||||
pub struct Cli {
|
||||
/// Process-only PSP routing selected by the parent Codex CLI.
|
||||
#[clap(skip)]
|
||||
pub psp: bool,
|
||||
|
||||
/// Optional user prompt to start the session.
|
||||
#[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
@@ -965,7 +965,7 @@ pub async fn run_main(
|
||||
&cli_kv_overrides,
|
||||
&launch_loader_overrides,
|
||||
strict_config,
|
||||
cli.bypass_hook_trust,
|
||||
cli.bypass_hook_trust || cli.psp,
|
||||
);
|
||||
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
|
||||
maybe_probe_default_daemon_socket(&codex_home).await
|
||||
@@ -1118,6 +1118,7 @@ pub async fn run_main(
|
||||
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
show_raw_agent_reasoning: cli.oss.then_some(true),
|
||||
bypass_hook_trust: cli.bypass_hook_trust.then_some(true),
|
||||
psp: Some(cli.psp),
|
||||
additional_writable_roots: additional_dirs,
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -40,6 +40,9 @@ fn format_exit_messages(exit_info: AppExitInfo, color_enabled: bool) -> Vec<Stri
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
struct TopCli {
|
||||
#[arg(long, hide = true)]
|
||||
psp: bool,
|
||||
|
||||
#[clap(flatten)]
|
||||
config_overrides: CliConfigOverrides,
|
||||
|
||||
@@ -51,6 +54,7 @@ fn main() -> anyhow::Result<()> {
|
||||
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
|
||||
let top_cli = TopCli::parse();
|
||||
let mut inner = top_cli.inner;
|
||||
inner.psp = top_cli.psp;
|
||||
inner
|
||||
.config_overrides
|
||||
.raw_overrides
|
||||
|
||||
@@ -340,7 +340,7 @@ async fn start_app_server_for_archive_command(
|
||||
&cli_kv_overrides,
|
||||
&launch_loader_overrides,
|
||||
strict_config,
|
||||
cli.bypass_hook_trust,
|
||||
cli.bypass_hook_trust || cli.psp,
|
||||
);
|
||||
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
|
||||
super::maybe_probe_default_daemon_socket(codex_home.as_path()).await
|
||||
@@ -406,11 +406,6 @@ async fn start_app_server_for_archive_command(
|
||||
.feature_requirements
|
||||
.as_ref(),
|
||||
)?;
|
||||
let environment_manager = Arc::new(
|
||||
prepared_environment_manager
|
||||
.build(Some(local_runtime_paths), http_client_factory.clone())
|
||||
.wrap_err("failed to initialize environment manager")?,
|
||||
);
|
||||
let auth_route_config = AuthRouteConfig::from_http_client_factory(http_client_factory);
|
||||
let cloud_config_bundle = cloud_config_bundle_loader_for_storage(
|
||||
codex_home.to_path_buf(),
|
||||
@@ -449,6 +444,7 @@ async fn start_app_server_for_archive_command(
|
||||
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(),
|
||||
show_raw_agent_reasoning: cli.oss.then_some(true),
|
||||
bypass_hook_trust: cli.bypass_hook_trust.then_some(true),
|
||||
psp: Some(cli.psp),
|
||||
..Default::default()
|
||||
})
|
||||
.loader_overrides(loader_overrides.clone())
|
||||
@@ -457,6 +453,11 @@ async fn start_app_server_for_archive_command(
|
||||
.build()
|
||||
.await
|
||||
.wrap_err("failed to load configuration")?;
|
||||
let environment_manager = Arc::new(
|
||||
prepared_environment_manager
|
||||
.build(Some(local_runtime_paths), config.http_client_factory())
|
||||
.wrap_err("failed to initialize environment manager")?,
|
||||
);
|
||||
let state_db = super::init_state_db_for_app_server_target(&config, &app_server_target)
|
||||
.await
|
||||
.wrap_err("failed to initialize state database")?;
|
||||
|
||||
Reference in New Issue
Block a user