Configure PSP routing through the feature system (#38056)

## What changed

- Add the under-development `psp` feature and expose it in the config schema.
- Use the feature to attach the PSP cookie to first-party ChatGPT clients.
- Remove the hidden `--psp` flag and its process-scoped configuration plumbing.
- Preserve configured ChatGPT cookies when creating the PSP client used for GET and POST requests.

## Testing

- Update the config manager service test to verify that enabling `features.psp` retains the setting in the effective config and configures the expected ChatGPT cookie.

GitOrigin-RevId: 53acb5495d2ff71e4ed25f674a0cff787aea474a
This commit is contained in:
stevenlee-oai authored and copyberry committed 2026-08-11 19:16:15 +00:00
1 parent b28aa476f4
commit 285abc0368
24 files changed
+45 -105

No files matched your search

@@ -36,7 +36,6 @@ pub(crate) struct ConfigManager {
cloud_config_bundle: Arc<RwLock<CloudConfigBundleLoader>>, cloud_config_bundle: Arc<RwLock<CloudConfigBundleLoader>>,
arg0_paths: Arg0DispatchPaths, arg0_paths: Arg0DispatchPaths,
thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>, thread_config_loader: Arc<RwLock<Arc<dyn ThreadConfigLoader>>>,
pub(crate) psp: bool,
} }
impl ConfigManager { impl ConfigManager {
@@ -58,7 +57,6 @@ impl ConfigManager {
cloud_config_bundle: Arc::new(RwLock::new(cloud_config_bundle)), cloud_config_bundle: Arc::new(RwLock::new(cloud_config_bundle)),
arg0_paths, arg0_paths,
thread_config_loader: Arc::new(RwLock::new(thread_config_loader)), thread_config_loader: Arc::new(RwLock::new(thread_config_loader)),
psp: false,
} }
} }
@@ -190,7 +188,6 @@ impl ConfigManager {
.cloud_config_bundle(CloudConfigBundleLoader::default()) .cloud_config_bundle(CloudConfigBundleLoader::default())
.build() .build()
.await?; .await?;
config.psp = self.psp;
self.apply_runtime_feature_enablement(&mut config); self.apply_runtime_feature_enablement(&mut config);
self.apply_arg0_paths(&mut config); self.apply_arg0_paths(&mut config);
Ok(config) Ok(config)
@@ -251,8 +248,6 @@ impl ConfigManager {
.map(|(key, value)| (key, json_to_toml(value))), .map(|(key, value)| (key, json_to_toml(value))),
) )
.collect::<Vec<_>>(); .collect::<Vec<_>>();
typesafe_overrides.psp = Some(self.psp);
let mut config = codex_core::config::ConfigBuilder::default() let mut config = codex_core::config::ConfigBuilder::default()
.codex_home(self.codex_home.clone()) .codex_home(self.codex_home.clone())
.cli_overrides(merged_cli_overrides) .cli_overrides(merged_cli_overrides)
@@ -1,5 +1,6 @@
use super::*; use super::*;
use anyhow::Result; use anyhow::Result;
use axum::http::HeaderValue;
use codex_app_server_protocol::AppConfig; use codex_app_server_protocol::AppConfig;
use codex_app_server_protocol::AppToolApproval; use codex_app_server_protocol::AppToolApproval;
use codex_app_server_protocol::AppsConfig; use codex_app_server_protocol::AppsConfig;
@@ -8,6 +9,8 @@ use codex_app_server_protocol::ConfigLayerSource as ApiConfigLayerSource;
use codex_config::CloudConfigBundleLoader; use codex_config::CloudConfigBundleLoader;
use codex_config::LoaderOverrides; use codex_config::LoaderOverrides;
use codex_config::test_support::CloudConfigBundleFixture; use codex_config::test_support::CloudConfigBundleFixture;
use codex_http_client::HttpClientFactory;
use codex_http_client::OutboundProxyPolicy;
use codex_utils_absolute_path::AbsolutePathBuf; use codex_utils_absolute_path::AbsolutePathBuf;
use pretty_assertions::assert_eq; use pretty_assertions::assert_eq;
use tempfile::tempdir; use tempfile::tempdir;
@@ -107,20 +110,22 @@ personality = true
} }
#[tokio::test] #[tokio::test]
async fn process_routing_does_not_enter_config_layers() -> Result<()> { async fn psp_feature_configures_first_party_routing() -> Result<()> {
let tmp = tempdir()?; let tmp = tempdir()?;
let mut service = ConfigManager::new_for_tests( let service = ConfigManager::new_for_tests(
tmp.path().to_path_buf(), tmp.path().to_path_buf(),
Vec::new(), Vec::new(),
LoaderOverrides::without_managed_config_for_tests(), LoaderOverrides::without_managed_config_for_tests(),
CloudConfigBundleLoader::default(), CloudConfigBundleLoader::default(),
); );
service.psp = true;
let config = service let config = service
.load_with_overrides( .load_with_overrides(
Some( Some(
[("features".to_string(), serde_json::json!({ "apps": true }))] [(
"features".to_string(),
serde_json::json!({ "apps": true, "psp": true }),
)]
.into_iter() .into_iter()
.collect(), .collect(),
), ),
@@ -128,15 +133,19 @@ async fn process_routing_does_not_enter_config_layers() -> Result<()> {
) )
.await?; .await?;
assert!(config.psp); assert!(config.features.enabled(codex_features::Feature::Psp));
assert!(config.http_client_factory().has_chatgpt_cookies()); assert_eq!(
assert!( config.http_client_factory(),
HttpClientFactory::new(OutboundProxyPolicy::ReqwestDefault)
.with_chatgpt_cookies([HeaderValue::from_static("oai-chat-psp=true")])
);
assert_eq!(
config config
.config_layer_stack .config_layer_stack
.effective_config() .effective_config()
.get("features") .get("features")
.and_then(|features| features.get("psp")) .and_then(|features| features.get("psp")),
.is_none() Some(&toml::Value::Boolean(true))
); );
Ok(()) Ok(())
} }
+1 -2
View File
@@ -446,7 +446,7 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
)); ));
let processor_outgoing = Arc::clone(&outgoing_message_sender); let processor_outgoing = Arc::clone(&outgoing_message_sender);
let mut config_manager = ConfigManager::new( let config_manager = ConfigManager::new(
args.config.codex_home.to_path_buf(), args.config.codex_home.to_path_buf(),
args.cli_overrides, args.cli_overrides,
args.loader_overrides, args.loader_overrides,
@@ -455,7 +455,6 @@ async fn start_uninitialized(args: InProcessStartArgs) -> IoResult<InProcessClie
args.arg0_paths.clone(), args.arg0_paths.clone(),
args.thread_config_loader, args.thread_config_loader,
); );
config_manager.psp = args.config.psp;
let (processor_tx, mut processor_rx) = mpsc::channel::<ProcessorCommand>(channel_capacity); let (processor_tx, mut processor_rx) = mpsc::channel::<ProcessorCommand>(channel_capacity);
let mut processor_handle = tokio::spawn(async move { let mut processor_handle = tokio::spawn(async move {
let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs { let processor = Arc::new(MessageProcessor::new(MessageProcessorArgs {
+1 -4
View File
@@ -438,7 +438,6 @@ pub struct AppServerRuntimeOptions {
pub plugin_startup_tasks: PluginStartupTasks, pub plugin_startup_tasks: PluginStartupTasks,
pub remote_control_startup_mode: RemoteControlStartupMode, pub remote_control_startup_mode: RemoteControlStartupMode,
pub install_shutdown_signal_handler: bool, pub install_shutdown_signal_handler: bool,
pub psp: bool,
} }
impl Default for AppServerRuntimeOptions { impl Default for AppServerRuntimeOptions {
@@ -448,7 +447,6 @@ impl Default for AppServerRuntimeOptions {
plugin_startup_tasks: PluginStartupTasks::Start, plugin_startup_tasks: PluginStartupTasks::Start,
remote_control_startup_mode: RemoteControlStartupMode::ResolvePersisted, remote_control_startup_mode: RemoteControlStartupMode::ResolvePersisted,
install_shutdown_signal_handler: true, install_shutdown_signal_handler: true,
psp: false,
} }
} }
} }
@@ -489,7 +487,7 @@ pub async fn run_main_with_transport_options(
arg0_paths.codex_linux_sandbox_exe.clone(), arg0_paths.codex_linux_sandbox_exe.clone(),
)?; )?;
let ignore_user_config = loader_overrides.ignore_user_config; let ignore_user_config = loader_overrides.ignore_user_config;
let mut config_manager = ConfigManager::new( let config_manager = ConfigManager::new(
codex_home.to_path_buf(), codex_home.to_path_buf(),
cli_kv_overrides.clone(), cli_kv_overrides.clone(),
loader_overrides, loader_overrides,
@@ -498,7 +496,6 @@ pub async fn run_main_with_transport_options(
arg0_paths.clone(), arg0_paths.clone(),
Arc::new(NoopThreadConfigLoader), Arc::new(NoopThreadConfigLoader),
); );
config_manager.psp = runtime_options.psp;
match config_manager match config_manager
.load_latest_config(/*fallback_cwd*/ None) .load_latest_config(/*fallback_cwd*/ None)
.await .await
-6
View File
@@ -60,10 +60,6 @@ struct AppServerArgs {
/// Enable remote control for this app-server process without changing persistence. /// Enable remote control for this app-server process without changing persistence.
#[arg(long = "remote-control", hide = true)] #[arg(long = "remote-control", hide = true)]
remote_control: bool, remote_control: bool,
/// Enable process-only PSP routing for first-party ChatGPT requests.
#[arg(long, hide = true)]
psp: bool,
} }
fn main() -> anyhow::Result<()> { fn main() -> anyhow::Result<()> {
@@ -79,7 +75,6 @@ fn main() -> anyhow::Result<()> {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
disable_plugin_startup_tasks_for_tests, disable_plugin_startup_tasks_for_tests,
remote_control, remote_control,
psp,
} = AppServerArgs::parse(); } = AppServerArgs::parse();
let loader_overrides = if disable_managed_config_from_debug_env() { let loader_overrides = if disable_managed_config_from_debug_env() {
LoaderOverrides::without_managed_config_for_tests() LoaderOverrides::without_managed_config_for_tests()
@@ -92,7 +87,6 @@ fn main() -> anyhow::Result<()> {
let auth = auth.try_into_settings()?; let auth = auth.try_into_settings()?;
let mut runtime_options = AppServerRuntimeOptions { let mut runtime_options = AppServerRuntimeOptions {
code_mode_host_transport: code_mode_host.into(), code_mode_host_transport: code_mode_host.into(),
psp,
..Default::default() ..Default::default()
}; };
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
-2
View File
@@ -46,7 +46,6 @@ fn app_server_accepts_process_scoped_code_mode_host() {
"wss://example.test/code-mode", "wss://example.test/code-mode",
"--listen", "--listen",
"off", "off",
"--psp",
]) ])
.expect("parse app-server args"); .expect("parse app-server args");
@@ -55,7 +54,6 @@ fn app_server_accepts_process_scoped_code_mode_host() {
Some(Url::parse("wss://example.test/code-mode").expect("test endpoint should parse")) Some(Url::parse("wss://example.test/code-mode").expect("test endpoint should parse"))
); );
assert_eq!(args.listen, AppServerTransport::Off); assert_eq!(args.listen, AppServerTransport::Off);
assert!(args.psp);
assert_eq!(args.config_overrides.raw_overrides, Vec::<String>::new()); assert_eq!(args.config_overrides.raw_overrides, Vec::<String>::new());
} }
+7 -6
View File
@@ -28,8 +28,7 @@ static PSP_CHATGPT_CLIENT: LazyLock<Mutex<Option<CachedChatGptClient>>> =
LazyLock::new(|| Mutex::new(None)); LazyLock::new(|| Mutex::new(None));
/// Reuse the default client while retaining its configured ChatGPT cookies. /// Reuse the default client while retaining its configured ChatGPT cookies.
fn psp_chatgpt_client(config: &Config) -> HttpClient { fn psp_chatgpt_client(factory: HttpClientFactory) -> HttpClient {
let factory = config.http_client_factory();
let residency = default_headers() let residency = default_headers()
.get(RESIDENCY_HEADER_NAME) .get(RESIDENCY_HEADER_NAME)
.map(|value| value.as_bytes().to_vec()); .map(|value| value.as_bytes().to_vec());
@@ -87,8 +86,9 @@ pub(crate) async fn chatgpt_get_request_with_timeout<T: DeserializeOwned>(
path.trim_start_matches('/') path.trim_start_matches('/')
); );
let client = if config.psp { let http_client_factory = config.http_client_factory();
psp_chatgpt_client(config) let client = if http_client_factory.has_chatgpt_cookies() {
psp_chatgpt_client(http_client_factory)
} else { } else {
create_client() create_client()
}; };
@@ -145,8 +145,9 @@ pub(crate) async fn chatgpt_post_request_with_timeout<
config.chatgpt_base_url.trim_end_matches('/'), config.chatgpt_base_url.trim_end_matches('/'),
path.trim_start_matches('/') path.trim_start_matches('/')
); );
let client = if config.psp { let http_client_factory = config.http_client_factory();
psp_chatgpt_client(config) let client = if http_client_factory.has_chatgpt_cookies() {
psp_chatgpt_client(http_client_factory)
} else { } else {
create_client() create_client()
}; };
-27
View File
@@ -105,10 +105,6 @@ use codex_terminal_detection::TerminalName;
override_usage = "codex [OPTIONS] [PROMPT]\n codex [OPTIONS] <COMMAND> [ARGS]" override_usage = "codex [OPTIONS] [PROMPT]\n codex [OPTIONS] <COMMAND> [ARGS]"
)] )]
struct MultitoolCli { struct MultitoolCli {
/// Enable process-only PSP routing for first-party ChatGPT requests.
#[arg(long, global = true, hide = true)]
psp: bool,
#[clap(flatten)] #[clap(flatten)]
pub config_overrides: CliConfigOverrides, pub config_overrides: CliConfigOverrides,
@@ -993,15 +989,12 @@ async fn cli_main(
remote_control_disabled: bool, remote_control_disabled: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let MultitoolCli { let MultitoolCli {
psp,
config_overrides: mut root_config_overrides, config_overrides: mut root_config_overrides,
feature_toggles, feature_toggles,
remote, remote,
mut interactive, mut interactive,
subcommand, subcommand,
} = MultitoolCli::parse(); } = MultitoolCli::parse();
interactive.psp = psp;
// Fold --enable/--disable into config overrides so they flow to all subcommands. // Fold --enable/--disable into config overrides so they flow to all subcommands.
let toggle_overrides = feature_toggles.to_overrides()?; let toggle_overrides = feature_toggles.to_overrides()?;
root_config_overrides.raw_overrides.extend(toggle_overrides); root_config_overrides.raw_overrides.extend(toggle_overrides);
@@ -1040,7 +1033,6 @@ async fn cli_main(
exec_cli exec_cli
.shared .shared
.inherit_exec_root_options(&interactive.shared); .inherit_exec_root_options(&interactive.shared);
exec_cli.psp = psp;
exec_cli.strict_config |= root_strict_config; exec_cli.strict_config |= root_strict_config;
prepend_config_flags( prepend_config_flags(
&mut exec_cli.config_overrides, &mut exec_cli.config_overrides,
@@ -1061,7 +1053,6 @@ async fn cli_main(
exec_cli exec_cli
.shared .shared
.inherit_exec_root_options(&interactive.shared); .inherit_exec_root_options(&interactive.shared);
exec_cli.psp = psp;
exec_cli.command = Some(ExecCommand::Review(review_args)); exec_cli.command = Some(ExecCommand::Review(review_args));
exec_cli.strict_config = strict_config || root_strict_config; exec_cli.strict_config = strict_config || root_strict_config;
prepend_config_flags( prepend_config_flags(
@@ -1171,7 +1162,6 @@ async fn cli_main(
codex_app_server::RemoteControlStartupMode::ResolvePersisted codex_app_server::RemoteControlStartupMode::ResolvePersisted
} }
}, },
psp,
..Default::default() ..Default::default()
}; };
codex_app_server::run_main_with_transport_options( codex_app_server::run_main_with_transport_options(
@@ -1274,7 +1264,6 @@ async fn cli_main(
remote_control_cli, remote_control_cli,
arg0_paths.clone(), arg0_paths.clone(),
root_config_overrides, root_config_overrides,
psp,
) )
.await?; .await?;
} }
@@ -2743,7 +2732,6 @@ mod tests {
fn finalize_resume_from_args(args: &[&str]) -> TuiCli { fn finalize_resume_from_args(args: &[&str]) -> TuiCli {
let cli = MultitoolCli::try_parse_from(args).expect("parse"); let cli = MultitoolCli::try_parse_from(args).expect("parse");
let MultitoolCli { let MultitoolCli {
psp: _,
mut interactive, mut interactive,
config_overrides: mut root_overrides, config_overrides: mut root_overrides,
subcommand, subcommand,
@@ -2781,7 +2769,6 @@ mod tests {
fn finalize_fork_from_args(args: &[&str]) -> TuiCli { fn finalize_fork_from_args(args: &[&str]) -> TuiCli {
let cli = MultitoolCli::try_parse_from(args).expect("parse"); let cli = MultitoolCli::try_parse_from(args).expect("parse");
let MultitoolCli { let MultitoolCli {
psp: _,
mut interactive, mut interactive,
config_overrides: mut root_overrides, config_overrides: mut root_overrides,
subcommand, subcommand,
@@ -2826,7 +2813,6 @@ mod tests {
fn finalize_archive_from_args(args: &[&str]) -> (String, TuiCli, InteractiveRemoteOptions) { fn finalize_archive_from_args(args: &[&str]) -> (String, TuiCli, InteractiveRemoteOptions) {
let cli = MultitoolCli::try_parse_from(args).expect("parse"); let cli = MultitoolCli::try_parse_from(args).expect("parse");
let MultitoolCli { let MultitoolCli {
psp: _,
interactive, interactive,
config_overrides: root_overrides, config_overrides: root_overrides,
subcommand, subcommand,
@@ -4055,19 +4041,6 @@ mod tests {
assert!(err.to_string().contains("is empty")); 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] #[test]
fn app_server_code_mode_host_url_parses_independently_of_listen_transport() { fn app_server_code_mode_host_url_parses_independently_of_listen_transport() {
let app_server = app_server_from_args( let app_server = app_server_from_args(
+1 -5
View File
@@ -65,7 +65,6 @@ pub(crate) async fn run(
command: RemoteControlCommand, command: RemoteControlCommand,
arg0_paths: Arg0DispatchPaths, arg0_paths: Arg0DispatchPaths,
root_config_overrides: CliConfigOverrides, root_config_overrides: CliConfigOverrides,
psp: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
match command.subcommand { match command.subcommand {
None => { None => {
@@ -73,8 +72,7 @@ pub(crate) async fn run(
command.json, command.json,
"Starting app-server with remote control enabled...", "Starting app-server with remote control enabled...",
)?; )?;
run_foreground_remote_control(command.json, arg0_paths, root_config_overrides, psp) run_foreground_remote_control(command.json, arg0_paths, root_config_overrides).await?;
.await?;
} }
Some(RemoteControlSubcommand::Start) => { Some(RemoteControlSubcommand::Start) => {
print_remote_control_progress( print_remote_control_progress(
@@ -113,7 +111,6 @@ async fn run_foreground_remote_control(
json: bool, json: bool,
arg0_paths: Arg0DispatchPaths, arg0_paths: Arg0DispatchPaths,
root_config_overrides: CliConfigOverrides, root_config_overrides: CliConfigOverrides,
psp: bool,
) -> anyhow::Result<()> { ) -> anyhow::Result<()> {
let socket_dir = tempfile::Builder::new() let socket_dir = tempfile::Builder::new()
.prefix("codex-rc-") .prefix("codex-rc-")
@@ -129,7 +126,6 @@ async fn run_foreground_remote_control(
let runtime_options = AppServerRuntimeOptions { let runtime_options = AppServerRuntimeOptions {
remote_control_startup_mode: codex_app_server::RemoteControlStartupMode::EnabledEphemeral, remote_control_startup_mode: codex_app_server::RemoteControlStartupMode::EnabledEphemeral,
install_shutdown_signal_handler: false, install_shutdown_signal_handler: false,
psp,
..Default::default() ..Default::default()
}; };
let (stop_rx, stop_signal_task) = foreground_stop_signal(); let (stop_rx, stop_signal_task) = foreground_stop_signal();
+6
View File
@@ -654,6 +654,9 @@
"prevent_idle_sleep": { "prevent_idle_sleep": {
"type": "boolean" "type": "boolean"
}, },
"psp": {
"type": "boolean"
},
"realtime_conversation": { "realtime_conversation": {
"type": "boolean" "type": "boolean"
}, },
@@ -5290,6 +5293,9 @@
"prevent_idle_sleep": { "prevent_idle_sleep": {
"type": "boolean" "type": "boolean"
}, },
"psp": {
"type": "boolean"
},
"realtime_conversation": { "realtime_conversation": {
"type": "boolean" "type": "boolean"
}, },
-1
View File
@@ -285,7 +285,6 @@ mod reload {
.flatten(), .flatten(),
model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()), model_provider: preserve_current_provider.then(|| config.model_provider_id.clone()),
service_tier: preserve_current_service_tier.then(|| config.service_tier.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(), codex_linux_sandbox_exe: config.codex_linux_sandbox_exe.clone(),
main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(), main_execve_wrapper_exe: config.main_execve_wrapper_exe.clone(),
..Default::default() ..Default::default()
-2
View File
@@ -162,7 +162,6 @@ async fn apply_role_preserves_unspecified_keys() {
.await; .await;
config.codex_linux_sandbox_exe = Some(PathBuf::from("/tmp/codex-linux-sandbox")); 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.main_execve_wrapper_exe = Some(PathBuf::from("/tmp/codex-execve-wrapper"));
config.psp = true;
let role_path = write_role_config( let role_path = write_role_config(
&home, &home,
"instructions-only.toml", "instructions-only.toml",
@@ -203,7 +202,6 @@ async fn apply_role_preserves_unspecified_keys() {
config.main_execve_wrapper_exe, config.main_execve_wrapper_exe,
Some(PathBuf::from("/tmp/codex-execve-wrapper")) Some(PathBuf::from("/tmp/codex-execve-wrapper"))
); );
assert!(config.psp);
assert_eq!(config.base_instructions, base_instructions); assert_eq!(config.base_instructions, base_instructions);
assert_eq!(config.base_instructions_provenance, provenance); assert_eq!(config.base_instructions_provenance, provenance);
} }
-3
View File
@@ -5034,7 +5034,6 @@ async fn rebuild_preserving_session_layers_refreshes_requirements() -> std::io::
refreshed_toml, refreshed_toml,
ConfigOverrides { ConfigOverrides {
cwd: Some(codex_home.path().to_path_buf()), cwd: Some(codex_home.path().to_path_buf()),
psp: Some(true),
..Default::default() ..Default::default()
}, },
codex_home.abs(), codex_home.abs(),
@@ -5112,8 +5111,6 @@ async fn rebuild_preserving_session_layers_refreshes_requirements() -> std::io::
.rebuild_preserving_session_layers(&refreshed_config) .rebuild_preserving_session_layers(&refreshed_config)
.await?; .await?;
assert!(config.psp);
assert!(config.http_client_factory().has_chatgpt_cookies());
assert_eq!( assert_eq!(
config.mcp_servers.get(), config.mcp_servers.get(),
&HashMap::from([ &HashMap::from([
+1 -8
View File
@@ -977,9 +977,6 @@ pub struct Config {
/// Whether Codex-owned clients should respect host system proxy settings. /// Whether Codex-owned clients should respect host system proxy settings.
pub respect_system_proxy: bool, 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. /// Optional product SKU forwarded to the host-owned apps MCP server.
pub apps_mcp_product_sku: Option<String>, pub apps_mcp_product_sku: Option<String>,
@@ -1623,7 +1620,7 @@ impl Config {
OutboundProxyPolicy::ReqwestDefault OutboundProxyPolicy::ReqwestDefault
}; };
let factory = HttpClientFactory::new(outbound_proxy_policy); let factory = HttpClientFactory::new(outbound_proxy_policy);
if self.psp { if self.features.enabled(Feature::Psp) {
factory.with_chatgpt_cookies([HeaderValue::from_static("oai-chat-psp=true")]) factory.with_chatgpt_cookies([HeaderValue::from_static("oai-chat-psp=true")])
} else { } else {
factory factory
@@ -1835,7 +1832,6 @@ impl Config {
ConfigOverrides { ConfigOverrides {
cwd: Some(self.cwd.to_path_buf()), cwd: Some(self.cwd.to_path_buf()),
default_zsh_path, default_zsh_path,
psp: Some(refreshed_config.psp),
..Default::default() ..Default::default()
}, },
refreshed_config.codex_home.clone(), refreshed_config.codex_home.clone(),
@@ -2579,7 +2575,6 @@ pub struct ConfigOverrides {
pub tools_web_search_request: Option<bool>, pub tools_web_search_request: Option<bool>,
pub ephemeral: Option<bool>, pub ephemeral: Option<bool>,
pub bypass_hook_trust: Option<bool>, pub bypass_hook_trust: Option<bool>,
pub psp: Option<bool>,
/// Additional directories that should be treated as writable roots for this session. /// Additional directories that should be treated as writable roots for this session.
pub additional_writable_roots: Vec<PathBuf>, pub additional_writable_roots: Vec<PathBuf>,
/// Explicit absolute runtime workspace roots for this session. When set, /// Explicit absolute runtime workspace roots for this session. When set,
@@ -3270,7 +3265,6 @@ impl Config {
tools_web_search_request: override_tools_web_search_request, tools_web_search_request: override_tools_web_search_request,
ephemeral, ephemeral,
bypass_hook_trust, bypass_hook_trust,
psp,
additional_writable_roots, additional_writable_roots,
workspace_roots: workspace_roots_override, workspace_roots: workspace_roots_override,
} = overrides; } = overrides;
@@ -4192,7 +4186,6 @@ impl Config {
.chatgpt_base_url .chatgpt_base_url
.unwrap_or("https://chatgpt.com/backend-api/".to_string()), .unwrap_or("https://chatgpt.com/backend-api/".to_string()),
respect_system_proxy, respect_system_proxy,
psp: psp.unwrap_or_default(),
apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(), apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(),
responses_api_metadata: cfg.responses_api_metadata.unwrap_or_default(), responses_api_metadata: cfg.responses_api_metadata.unwrap_or_default(),
realtime_audio: cfg realtime_audio: cfg
-4
View File
@@ -12,10 +12,6 @@ use std::path::PathBuf;
override_usage = "codex exec [OPTIONS] [PROMPT]\n codex exec [OPTIONS] <COMMAND> [ARGS]" override_usage = "codex exec [OPTIONS] [PROMPT]\n codex exec [OPTIONS] <COMMAND> [ARGS]"
)] )]
pub struct Cli { 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. /// Action to perform. If omitted, runs a new non-interactive session.
#[command(subcommand)] #[command(subcommand)]
pub command: Option<Command>, pub command: Option<Command>,
-2
View File
@@ -245,7 +245,6 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
} }
let Cli { let Cli {
psp,
command, command,
strict_config, strict_config,
shared, shared,
@@ -423,7 +422,6 @@ pub async fn run_main(cli: Cli, arg0_paths: Arg0DispatchPaths) -> anyhow::Result
tools_web_search_request: None, tools_web_search_request: None,
ephemeral: ephemeral.then_some(true), ephemeral: ephemeral.then_some(true),
bypass_hook_trust: bypass_hook_trust.then_some(true), bypass_hook_trust: bypass_hook_trust.then_some(true),
psp: Some(psp),
additional_writable_roots: add_dir, additional_writable_roots: add_dir,
}; };
-4
View File
@@ -18,9 +18,6 @@ use codex_utils_cli::CliConfigOverrides;
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct TopCli { struct TopCli {
#[arg(long, global = true, hide = true)]
psp: bool,
#[clap(flatten)] #[clap(flatten)]
config_overrides: CliConfigOverrides, config_overrides: CliConfigOverrides,
@@ -33,7 +30,6 @@ fn main() -> anyhow::Result<()> {
let top_cli = TopCli::parse(); let top_cli = TopCli::parse();
// Merge root-level overrides into inner CLI struct so downstream logic remains unchanged. // Merge root-level overrides into inner CLI struct so downstream logic remains unchanged.
let mut inner = top_cli.inner; let mut inner = top_cli.inner;
inner.psp = top_cli.psp;
inner inner
.config_overrides .config_overrides
.prepend_root_overrides(top_cli.config_overrides); .prepend_root_overrides(top_cli.config_overrides);
-2
View File
@@ -7,7 +7,6 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
let cli = TopCli::parse_from([ let cli = TopCli::parse_from([
"codex-exec", "codex-exec",
"resume", "resume",
"--psp",
"--strict-config", "--strict-config",
"--last", "--last",
"--json", "--json",
@@ -19,7 +18,6 @@ fn top_cli_parses_resume_prompt_after_config_flag() {
"--skip-git-repo-check", "--skip-git-repo-check",
PROMPT, PROMPT,
]); ]);
assert!(cli.psp);
let mut inner = cli.inner; let mut inner = cli.inner;
inner inner
.config_overrides .config_overrides
+8
View File
@@ -171,6 +171,8 @@ pub enum Feature {
SpawnCsv, SpawnCsv,
/// Enable apps. /// Enable apps.
Apps, Apps,
/// Route first-party ChatGPT requests through PSP.
Psp,
/// Enable MCP apps. /// Enable MCP apps.
EnableMcpApps, EnableMcpApps,
/// Enable MCP protocol version 2026-07-28 support. /// Enable MCP protocol version 2026-07-28 support.
@@ -1092,6 +1094,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::Stable, stage: Stage::Stable,
default_enabled: true, default_enabled: true,
}, },
FeatureSpec {
id: Feature::Psp,
key: "psp",
stage: Stage::UnderDevelopment,
default_enabled: false,
},
FeatureSpec { FeatureSpec {
id: Feature::EnableMcpApps, id: Feature::EnableMcpApps,
key: "enable_mcp_apps", key: "enable_mcp_apps",
@@ -187,7 +187,6 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
config_layer_stack: ConfigLayerStack::default(), config_layer_stack: ConfigLayerStack::default(),
startup_warnings: Vec::new(), startup_warnings: Vec::new(),
bypass_hook_trust: false, bypass_hook_trust: false,
psp: false,
model, model,
service_tier: None, service_tier: None,
review_model: None, review_model: None,
-4
View File
@@ -8,10 +8,6 @@ use codex_utils_cli::SharedCliOptions;
#[derive(Parser, Clone, Debug)] #[derive(Parser, Clone, Debug)]
#[command(version)] #[command(version)]
pub struct Cli { 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. /// Optional user prompt to start the session.
#[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)] #[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]
pub prompt: Option<String>, pub prompt: Option<String>,
+1 -2
View File
@@ -943,7 +943,7 @@ pub async fn run_main(
&cli_kv_overrides, &cli_kv_overrides,
&launch_loader_overrides, &launch_loader_overrides,
strict_config, strict_config,
cli.bypass_hook_trust || cli.psp, cli.bypass_hook_trust,
); );
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon { let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
maybe_probe_default_daemon_socket(&codex_home).await maybe_probe_default_daemon_socket(&codex_home).await
@@ -1077,7 +1077,6 @@ pub async fn run_main(
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(), main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(),
show_raw_agent_reasoning: cli.oss.then_some(true), show_raw_agent_reasoning: cli.oss.then_some(true),
bypass_hook_trust: cli.bypass_hook_trust.then_some(true), bypass_hook_trust: cli.bypass_hook_trust.then_some(true),
psp: Some(cli.psp),
additional_writable_roots: additional_dirs, additional_writable_roots: additional_dirs,
..Default::default() ..Default::default()
}; };
-4
View File
@@ -40,9 +40,6 @@ fn format_exit_messages(exit_info: AppExitInfo, color_enabled: bool) -> Vec<Stri
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
struct TopCli { struct TopCli {
#[arg(long, hide = true)]
psp: bool,
#[clap(flatten)] #[clap(flatten)]
config_overrides: CliConfigOverrides, config_overrides: CliConfigOverrides,
@@ -54,7 +51,6 @@ fn main() -> anyhow::Result<()> {
arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move { arg0_dispatch_or_else(|arg0_paths: Arg0DispatchPaths| async move {
let top_cli = TopCli::parse(); let top_cli = TopCli::parse();
let mut inner = top_cli.inner; let mut inner = top_cli.inner;
inner.psp = top_cli.psp;
inner inner
.config_overrides .config_overrides
.raw_overrides .raw_overrides
+1 -2
View File
@@ -282,7 +282,7 @@ async fn start_app_server_for_archive_command(
&cli_kv_overrides, &cli_kv_overrides,
&launch_loader_overrides, &launch_loader_overrides,
strict_config, strict_config,
cli.bypass_hook_trust || cli.psp, cli.bypass_hook_trust,
); );
let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon { let default_daemon = if explicit_remote_endpoint.is_none() && reuse_implicit_local_daemon {
super::maybe_probe_default_daemon_socket(codex_home.as_path()).await super::maybe_probe_default_daemon_socket(codex_home.as_path()).await
@@ -373,7 +373,6 @@ async fn start_app_server_for_archive_command(
main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(), main_execve_wrapper_exe: arg0_paths.main_execve_wrapper_exe.clone(),
show_raw_agent_reasoning: cli.oss.then_some(true), show_raw_agent_reasoning: cli.oss.then_some(true),
bypass_hook_trust: cli.bypass_hook_trust.then_some(true), bypass_hook_trust: cli.bypass_hook_trust.then_some(true),
psp: Some(cli.psp),
..Default::default() ..Default::default()
}) })
.loader_overrides(loader_overrides.clone()) .loader_overrides(loader_overrides.clone())