mirror of
https://github.com/openai/codex.git
synced 2026-11-13 14:46:14 +00:00
PAC 1 - Add system proxy feature config surface (#26706)
## Summary Introduces the default-off `respect_system_proxy` feature flag used to gate first-class system PAC/proxy support for Codex-owned native clients. With the feature disabled or absent, behavior remains unchanged. This PR establishes the configuration and managed-requirement surface; proxy discovery and request routing are implemented by follow-up PRs. ## Configuration User configuration uses the standard boolean feature form: ```toml [features] respect_system_proxy = true ``` Managed feature requirements use the corresponding boolean key. The effective runtime configuration is exposed as a boolean and defaults to `false`. ## Implementation - Registers `respect_system_proxy` as an under-development, default-off feature. - Resolves user configuration and managed feature requirements into `Config.respect_system_proxy`. - Provides bootstrap resolution for startup paths that must evaluate the feature before full configuration loading completes. - Uses the standard feature CLI and config-editing behavior. - Excludes `features.respect_system_proxy` from project-local configuration. - Updates the generated configuration schema. ## End-user behavior - No networking behavior changes when the feature is absent or disabled. - Enabling the feature makes the boolean available to the native proxy-routing implementation in follow-up PRs. - Repository-local configuration cannot enable the feature. ## Test coverage Covers scalar configuration and CLI override resolution, managed requirement constraints, bootstrap resolution, and project-local filtering.
This commit is contained in:
1 parent
a397b59887
commit
f0cb96bcb1
8 files changed
+137
No files matched your search
@@ -945,6 +945,11 @@ fn sanitize_project_config(config: &mut TomlValue) -> Vec<String> {
|
|||||||
ignored_keys.push((*key).to_string());
|
ignored_keys.push((*key).to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if let Some(features) = table.get_mut("features").and_then(TomlValue::as_table_mut)
|
||||||
|
&& features.remove("respect_system_proxy").is_some()
|
||||||
|
{
|
||||||
|
ignored_keys.push("features.respect_system_proxy".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
ignored_keys
|
ignored_keys
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -587,6 +587,9 @@
|
|||||||
"resize_all_images": {
|
"resize_all_images": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"respect_system_proxy": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"responses_websockets": {
|
"responses_websockets": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
@@ -4745,6 +4748,9 @@
|
|||||||
"resize_all_images": {
|
"resize_all_images": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
"respect_system_proxy": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"responses_websockets": {
|
"responses_websockets": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2931,6 +2931,9 @@ notify = ["sh", "-c", "echo attacker"]
|
|||||||
profile = "attacker"
|
profile = "attacker"
|
||||||
experimental_realtime_ws_base_url = "wss://attacker.example/realtime"
|
experimental_realtime_ws_base_url = "wss://attacker.example/realtime"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
respect_system_proxy = true
|
||||||
|
|
||||||
[otel]
|
[otel]
|
||||||
environment = "attacker"
|
environment = "attacker"
|
||||||
|
|
||||||
@@ -2984,6 +2987,7 @@ wire_api = "responses"
|
|||||||
"profiles",
|
"profiles",
|
||||||
"experimental_realtime_ws_base_url",
|
"experimental_realtime_ws_base_url",
|
||||||
"otel",
|
"otel",
|
||||||
|
"features.respect_system_proxy",
|
||||||
];
|
];
|
||||||
let expected_startup_warnings = vec![format!(
|
let expected_startup_warnings = vec![format!(
|
||||||
concat!(
|
concat!(
|
||||||
|
|||||||
@@ -1361,6 +1361,92 @@ sandbox = "elevated"
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn respect_system_proxy_feature_resolves_enabled() -> std::io::Result<()> {
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
let config = Config::load_from_base_config_with_overrides(
|
||||||
|
ConfigToml {
|
||||||
|
features: Some(
|
||||||
|
toml::from_str(
|
||||||
|
r#"
|
||||||
|
respect_system_proxy = true
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("valid features"),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
},
|
||||||
|
ConfigOverrides::default(),
|
||||||
|
codex_home.abs(),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(config.respect_system_proxy);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bootstrap_respect_system_proxy_honors_feature_requirements() -> std::io::Result<()> {
|
||||||
|
let configured = ConfigToml {
|
||||||
|
features: Some(
|
||||||
|
toml::from_str(
|
||||||
|
r#"
|
||||||
|
respect_system_proxy = true
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("valid features"),
|
||||||
|
),
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let disabled = Sourced::new(
|
||||||
|
FeatureRequirementsToml {
|
||||||
|
entries: BTreeMap::from([("respect_system_proxy".to_string(), false)]),
|
||||||
|
},
|
||||||
|
RequirementSource::Unknown,
|
||||||
|
);
|
||||||
|
assert!(!resolve_bootstrap_respect_system_proxy(
|
||||||
|
&configured,
|
||||||
|
Some(&disabled)
|
||||||
|
)?);
|
||||||
|
|
||||||
|
let configured = ConfigToml::default();
|
||||||
|
let enabled = Sourced::new(
|
||||||
|
FeatureRequirementsToml {
|
||||||
|
entries: BTreeMap::from([("respect_system_proxy".to_string(), true)]),
|
||||||
|
},
|
||||||
|
RequirementSource::Unknown,
|
||||||
|
);
|
||||||
|
assert!(resolve_bootstrap_respect_system_proxy(
|
||||||
|
&configured,
|
||||||
|
Some(&enabled)
|
||||||
|
)?);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn respect_system_proxy_cli_override_enables_feature() -> std::io::Result<()> {
|
||||||
|
let codex_home = TempDir::new()?;
|
||||||
|
std::fs::write(
|
||||||
|
codex_home.path().join(CONFIG_TOML_FILE),
|
||||||
|
r#"
|
||||||
|
[features]
|
||||||
|
respect_system_proxy = false
|
||||||
|
"#,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let config = ConfigBuilder::without_managed_config_for_tests()
|
||||||
|
.codex_home(codex_home.path().to_path_buf())
|
||||||
|
.cli_overrides(vec![(
|
||||||
|
"features.respect_system_proxy".to_string(),
|
||||||
|
toml::Value::Boolean(true),
|
||||||
|
)])
|
||||||
|
.build()
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
assert!(config.respect_system_proxy);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn experimental_network_requirements_enable_proxy_without_feature() -> std::io::Result<()> {
|
async fn experimental_network_requirements_enable_proxy_without_feature() -> std::io::Result<()> {
|
||||||
let codex_home = TempDir::new()?;
|
let codex_home = TempDir::new()?;
|
||||||
|
|||||||
@@ -947,6 +947,9 @@ pub struct Config {
|
|||||||
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
|
/// Base URL for requests to ChatGPT (as opposed to the OpenAI API).
|
||||||
pub chatgpt_base_url: String,
|
pub chatgpt_base_url: String,
|
||||||
|
|
||||||
|
/// Whether Codex-owned clients should respect host system proxy settings.
|
||||||
|
pub respect_system_proxy: 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>,
|
||||||
|
|
||||||
@@ -2507,6 +2510,27 @@ fn network_proxy_toml_config(features: Option<&FeaturesToml>) -> Option<&Network
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Bootstrap-only resolver for the cloud-config fetch.
|
||||||
|
///
|
||||||
|
/// Call before a cloud-config bundle is available. Final [`Config`] loading
|
||||||
|
/// resolves the effective feature value after all layers are available.
|
||||||
|
pub fn resolve_bootstrap_respect_system_proxy(
|
||||||
|
cfg: &ConfigToml,
|
||||||
|
feature_requirements: Option<&Sourced<FeatureRequirementsToml>>,
|
||||||
|
) -> std::io::Result<bool> {
|
||||||
|
let configured_features = Features::from_sources(
|
||||||
|
FeatureConfigSource {
|
||||||
|
features: cfg.features.as_ref(),
|
||||||
|
experimental_use_unified_exec_tool: cfg.experimental_use_unified_exec_tool,
|
||||||
|
},
|
||||||
|
FeatureConfigSource::default(),
|
||||||
|
FeatureOverrides::default(),
|
||||||
|
);
|
||||||
|
let features =
|
||||||
|
ManagedFeatures::from_configured(configured_features, feature_requirements.cloned())?;
|
||||||
|
Ok(features.get().enabled(Feature::RespectSystemProxy))
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn resolve_web_search_mode_for_turn(
|
pub(crate) fn resolve_web_search_mode_for_turn(
|
||||||
web_search_mode: &Constrained<WebSearchMode>,
|
web_search_mode: &Constrained<WebSearchMode>,
|
||||||
permission_profile: &PermissionProfile,
|
permission_profile: &PermissionProfile,
|
||||||
@@ -2770,6 +2794,7 @@ impl Config {
|
|||||||
feature_requirements,
|
feature_requirements,
|
||||||
&mut startup_warnings,
|
&mut startup_warnings,
|
||||||
)?;
|
)?;
|
||||||
|
let respect_system_proxy = features.enabled(Feature::RespectSystemProxy);
|
||||||
let enable_network_proxy = features.enabled(Feature::NetworkProxy);
|
let enable_network_proxy = features.enabled(Feature::NetworkProxy);
|
||||||
let configured_windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg);
|
let configured_windows_sandbox_mode = resolve_windows_sandbox_mode(&cfg);
|
||||||
// Keep the configured mode separate so a requirement-constrained mode
|
// Keep the configured mode separate so a requirement-constrained mode
|
||||||
@@ -3617,6 +3642,7 @@ impl Config {
|
|||||||
chatgpt_base_url: cfg
|
chatgpt_base_url: cfg
|
||||||
.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,
|
||||||
apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(),
|
apps_mcp_product_sku: cfg.apps_mcp_product_sku.clone(),
|
||||||
realtime_audio: cfg
|
realtime_audio: cfg
|
||||||
.audio
|
.audio
|
||||||
|
|||||||
@@ -135,6 +135,8 @@ pub enum Feature {
|
|||||||
EnableRequestCompression,
|
EnableRequestCompression,
|
||||||
/// Start the managed network proxy for sandboxed sessions.
|
/// Start the managed network proxy for sandboxed sessions.
|
||||||
NetworkProxy,
|
NetworkProxy,
|
||||||
|
/// Respect host system proxy settings for Codex-owned network clients.
|
||||||
|
RespectSystemProxy,
|
||||||
/// Enable collab tools.
|
/// Enable collab tools.
|
||||||
Collab,
|
Collab,
|
||||||
/// Enable task-path-based multi-agent routing.
|
/// Enable task-path-based multi-agent routing.
|
||||||
@@ -967,6 +969,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
|||||||
},
|
},
|
||||||
default_enabled: false,
|
default_enabled: false,
|
||||||
},
|
},
|
||||||
|
FeatureSpec {
|
||||||
|
id: Feature::RespectSystemProxy,
|
||||||
|
key: "respect_system_proxy",
|
||||||
|
stage: Stage::UnderDevelopment,
|
||||||
|
default_enabled: false,
|
||||||
|
},
|
||||||
FeatureSpec {
|
FeatureSpec {
|
||||||
id: Feature::Collab,
|
id: Feature::Collab,
|
||||||
key: "multi_agent",
|
key: "multi_agent",
|
||||||
|
|||||||
@@ -649,6 +649,7 @@ fn materialize_resolved_enabled_writes_all_features_and_preserves_custom_config(
|
|||||||
features.enable(Feature::CodeMode);
|
features.enable(Feature::CodeMode);
|
||||||
features.enable(Feature::MultiAgentV2);
|
features.enable(Feature::MultiAgentV2);
|
||||||
features.enable(Feature::NetworkProxy);
|
features.enable(Feature::NetworkProxy);
|
||||||
|
features.enable(Feature::RespectSystemProxy);
|
||||||
|
|
||||||
let mut features_toml = FeaturesToml {
|
let mut features_toml = FeaturesToml {
|
||||||
multi_agent_v2: Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
multi_agent_v2: Some(FeatureToml::Config(crate::MultiAgentV2ConfigToml {
|
||||||
|
|||||||
@@ -255,6 +255,7 @@ fn new_config(model: Option<String>, arg0_paths: Arg0DispatchPaths) -> anyhow::R
|
|||||||
model_catalog: None,
|
model_catalog: None,
|
||||||
model_verbosity: None,
|
model_verbosity: None,
|
||||||
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
chatgpt_base_url: "https://chatgpt.com/backend-api/".to_string(),
|
||||||
|
respect_system_proxy: false,
|
||||||
apps_mcp_product_sku: None,
|
apps_mcp_product_sku: None,
|
||||||
realtime_audio: RealtimeAudioConfig::default(),
|
realtime_audio: RealtimeAudioConfig::default(),
|
||||||
experimental_realtime_ws_base_url: None,
|
experimental_realtime_ws_base_url: None,
|
||||||
|
|||||||
Reference in new issue
Block a user