diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json index b65bb57424..37c14ab72d 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.schemas.json @@ -8192,6 +8192,20 @@ }, "type": "object" }, + "BrowserUseExtensionRequirements": { + "properties": { + "requestHeaders": { + "items": { + "$ref": "#/definitions/v2/RequestHeader" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "BrowserUseOriginPolicy": { "properties": { "access": { @@ -8344,6 +8358,16 @@ "null" ] }, + "extension": { + "anyOf": [ + { + "$ref": "#/definitions/v2/BrowserUseExtensionRequirements" + }, + { + "type": "null" + } + ] + }, "origins": { "additionalProperties": { "$ref": "#/definitions/v2/BrowserUseOriginPolicy" @@ -18521,6 +18545,21 @@ "title": "RemoteControlStatusChangedNotification", "type": "object" }, + "RequestHeader": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json index b328e713e8..9aac3eb2af 100644 --- a/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json +++ b/codex-rs/app-server-protocol/schema/json/codex_app_server_protocol.v2.schemas.json @@ -1471,6 +1471,20 @@ }, "type": "object" }, + "BrowserUseExtensionRequirements": { + "properties": { + "requestHeaders": { + "items": { + "$ref": "#/definitions/RequestHeader" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "BrowserUseOriginPolicy": { "properties": { "access": { @@ -1623,6 +1637,16 @@ "null" ] }, + "extension": { + "anyOf": [ + { + "$ref": "#/definitions/BrowserUseExtensionRequirements" + }, + { + "type": "null" + } + ] + }, "origins": { "additionalProperties": { "$ref": "#/definitions/BrowserUseOriginPolicy" @@ -14533,6 +14557,21 @@ "title": "RemoteControlStatusChangedNotification", "type": "object" }, + "RequestHeader": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, "RequestId": { "anyOf": [ { diff --git a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json index 71616ef3ce..8dbca4e59b 100644 --- a/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json +++ b/codex-rs/app-server-protocol/schema/json/v2/ConfigRequirementsReadResponse.json @@ -130,6 +130,20 @@ ], "type": "string" }, + "BrowserUseExtensionRequirements": { + "properties": { + "requestHeaders": { + "items": { + "$ref": "#/definitions/RequestHeader" + }, + "type": [ + "array", + "null" + ] + } + }, + "type": "object" + }, "BrowserUseOriginPolicy": { "properties": { "access": { @@ -237,6 +251,16 @@ "null" ] }, + "extension": { + "anyOf": [ + { + "$ref": "#/definitions/BrowserUseExtensionRequirements" + }, + { + "type": "null" + } + ] + }, "origins": { "additionalProperties": { "$ref": "#/definitions/BrowserUseOriginPolicy" @@ -1056,6 +1080,21 @@ "minLength": 1, "type": "string" }, + "RequestHeader": { + "properties": { + "name": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "name", + "value" + ], + "type": "object" + }, "ResidencyRequirement": { "enum": [ "us" diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst index 40f108461f..34523e9766 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-experimental.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst index 32ed7c1c69..61346229fb 100644 Binary files a/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst and b/codex-rs/app-server-protocol/schema/precomputed/app-server-exports-stable.json.zst differ diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseExtensionRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseExtensionRequirements.ts new file mode 100644 index 0000000000..5f279192be --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseExtensionRequirements.ts @@ -0,0 +1,6 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { RequestHeader } from "./RequestHeader"; + +export type BrowserUseExtensionRequirements = { requestHeaders: Array | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts b/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts index 1430f756e5..990274af49 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/BrowserUseRequirements.ts @@ -1,6 +1,7 @@ // GENERATED CODE! DO NOT MODIFY BY HAND! // This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. +import type { BrowserUseExtensionRequirements } from "./BrowserUseExtensionRequirements"; import type { BrowserUseOriginPolicy } from "./BrowserUseOriginPolicy"; -export type BrowserUseRequirements = { allowWebmcp: boolean | null, allowHistoryAccess: boolean | null, disableAutoReview: boolean | null, allowGlobalPersistentApproval: boolean | null, defaultOriginPolicy: BrowserUseOriginPolicy | null, origins: { [key in string]?: BrowserUseOriginPolicy } | null, }; +export type BrowserUseRequirements = { extension: BrowserUseExtensionRequirements | null, allowWebmcp: boolean | null, allowHistoryAccess: boolean | null, disableAutoReview: boolean | null, allowGlobalPersistentApproval: boolean | null, defaultOriginPolicy: BrowserUseOriginPolicy | null, origins: { [key in string]?: BrowserUseOriginPolicy } | null, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/RequestHeader.ts b/codex-rs/app-server-protocol/schema/typescript/v2/RequestHeader.ts new file mode 100644 index 0000000000..6b7b8ea7c2 --- /dev/null +++ b/codex-rs/app-server-protocol/schema/typescript/v2/RequestHeader.ts @@ -0,0 +1,5 @@ +// GENERATED CODE! DO NOT MODIFY BY HAND! + +// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually. + +export type RequestHeader = { name: string, value: string, }; diff --git a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts index 04d94e2c56..a90d0548a3 100644 --- a/codex-rs/app-server-protocol/schema/typescript/v2/index.ts +++ b/codex-rs/app-server-protocol/schema/typescript/v2/index.ts @@ -52,6 +52,7 @@ export type { AutoReviewDecisionSource } from "./AutoReviewDecisionSource"; export type { AutoReviewRequirements } from "./AutoReviewRequirements"; export type { BrowserUseAccessApprovalLifetime } from "./BrowserUseAccessApprovalLifetime"; export type { BrowserUseConfig } from "./BrowserUseConfig"; +export type { BrowserUseExtensionRequirements } from "./BrowserUseExtensionRequirements"; export type { BrowserUseOriginPolicy } from "./BrowserUseOriginPolicy"; export type { BrowserUseOriginPolicyConfig } from "./BrowserUseOriginPolicyConfig"; export type { BrowserUseRequirements } from "./BrowserUseRequirements"; @@ -427,6 +428,7 @@ export type { RemoteControlConnectionStatus } from "./RemoteControlConnectionSta export type { RemoteControlDisableParams } from "./RemoteControlDisableParams"; export type { RemoteControlEnableParams } from "./RemoteControlEnableParams"; export type { RemoteControlStatusChangedNotification } from "./RemoteControlStatusChangedNotification"; +export type { RequestHeader } from "./RequestHeader"; export type { RequestPermissionProfile } from "./RequestPermissionProfile"; export type { ResidencyRequirement } from "./ResidencyRequirement"; export type { ResponseUsageMetadata } from "./ResponseUsageMetadata"; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config.rs b/codex-rs/app-server-protocol/src/protocol/v2/config.rs index 42b7df7c45..3689fffd92 100644 --- a/codex-rs/app-server-protocol/src/protocol/v2/config.rs +++ b/codex-rs/app-server-protocol/src/protocol/v2/config.rs @@ -514,6 +514,7 @@ pub struct ComputerUseRequirements { #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] pub struct BrowserUseRequirements { + pub extension: Option, pub allow_webmcp: Option, pub allow_history_access: Option, pub disable_auto_review: Option, @@ -522,6 +523,30 @@ pub struct BrowserUseRequirements { pub origins: Option>, } +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct BrowserUseExtensionRequirements { + pub request_headers: Option>, +} + +#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, JsonSchema, TS)] +#[serde(rename_all = "camelCase")] +#[ts(export_to = "v2/")] +pub struct RequestHeader { + pub name: String, + pub value: String, +} + +impl std::fmt::Debug for RequestHeader { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RequestHeader") + .field("name", &self.name) + .field("value", &"[REDACTED]") + .finish() + } +} + #[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)] #[serde(rename_all = "camelCase")] #[ts(export_to = "v2/")] @@ -1141,3 +1166,7 @@ pub struct ConfigWarningNotification { #[ts(optional)] pub range: Option, } + +#[cfg(test)] +#[path = "config_tests.rs"] +mod tests; diff --git a/codex-rs/app-server-protocol/src/protocol/v2/config_tests.rs b/codex-rs/app-server-protocol/src/protocol/v2/config_tests.rs new file mode 100644 index 0000000000..cbe30b1b3a --- /dev/null +++ b/codex-rs/app-server-protocol/src/protocol/v2/config_tests.rs @@ -0,0 +1,28 @@ +use super::BrowserUseRequirements; +use super::RequestHeader; +use pretty_assertions::assert_eq; +use serde_json::json; + +#[test] +fn request_header_debug_redacts_values_without_changing_serialization() { + let wire = json!({ "name": "x-company-token", "value": "private-header-token" }); + let header: RequestHeader = serde_json::from_value(wire.clone()).unwrap(); + assert_eq!(serde_json::to_value(&header).unwrap(), wire); + assert_eq!( + format!("{header:?}"), + r#"RequestHeader { name: "x-company-token", value: "[REDACTED]" }"# + ); + let requirements: BrowserUseRequirements = serde_json::from_value(json!({ + "extension": { "requestHeaders": [wire] } + })) + .unwrap(); + for debug in [format!("{requirements:?}"), format!("{requirements:#?}")] { + assert!(!debug.contains("private-header-token")); + assert!(debug.contains("x-company-token")); + assert!(debug.contains("[REDACTED]")); + } + assert_eq!( + serde_json::to_value(&requirements).unwrap()["extension"]["requestHeaders"], + json!([wire]) + ); +} diff --git a/codex-rs/app-server/src/request_processors/config_processor.rs b/codex-rs/app-server/src/request_processors/config_processor.rs index 24c6711a7d..e03f96685a 100644 --- a/codex-rs/app-server/src/request_processors/config_processor.rs +++ b/codex-rs/app-server/src/request_processors/config_processor.rs @@ -626,6 +626,19 @@ fn map_browser_use_requirements_to_api( browser_use: codex_config::BrowserUseRequirementsToml, ) -> BrowserUseRequirements { BrowserUseRequirements { + extension: browser_use.extension.map(|extension| { + codex_app_server_protocol::BrowserUseExtensionRequirements { + request_headers: extension.request_headers.map(|headers| { + headers + .into_iter() + .map(|header| codex_app_server_protocol::RequestHeader { + name: header.name, + value: header.value, + }) + .collect() + }), + } + }), allow_webmcp: browser_use.allow_webmcp, allow_history_access: browser_use.allow_history_access, disable_auto_review: browser_use.disable_auto_review, @@ -1096,6 +1109,12 @@ client_id = "mcp-client" let mapped = map_test_requirements(ConfigRequirementsToml { allow_browser_and_computer_use: Some(false), browser_use: Some(BrowserUseRequirementsToml { + extension: Some(codex_config::BrowserUseExtensionRequirementsToml { + request_headers: Some(vec![codex_config::RequestHeaderToml { + name: "x-browser-agent".into(), + value: "ChatGPT/{{session_id}}".into(), + }]), + }), allow_webmcp: Some(true), allow_history_access: Some(false), disable_auto_review: Some(true), @@ -1154,6 +1173,12 @@ client_id = "mcp-client" assert_eq!( mapped.browser_use, Some(BrowserUseRequirements { + extension: Some(codex_app_server_protocol::BrowserUseExtensionRequirements { + request_headers: Some(vec![codex_app_server_protocol::RequestHeader { + name: "x-browser-agent".into(), + value: "ChatGPT/{{session_id}}".into() + }]), + }), allow_webmcp: Some(true), allow_history_access: Some(false), disable_auto_review: Some(true), diff --git a/codex-rs/app-server/tests/suite/v2/config_requirements_browser_use_tests.rs b/codex-rs/app-server/tests/suite/v2/config_requirements_browser_use_tests.rs index 99f4e8fb56..a690d72ae9 100644 --- a/codex-rs/app-server/tests/suite/v2/config_requirements_browser_use_tests.rs +++ b/codex-rs/app-server/tests/suite/v2/config_requirements_browser_use_tests.rs @@ -57,6 +57,7 @@ async fn config_requirements_read_preserves_webmcp_policy() -> Result<()> { json!({ "allowWebmcp": allow_webmcp, "allowHistoryAccess": allow_history_access, + "extension": null, "disableAutoReview": null, "allowGlobalPersistentApproval": null, "defaultOriginPolicy": null, diff --git a/codex-rs/app-server/tests/suite/v2/config_requirements_in_app_browser.rs b/codex-rs/app-server/tests/suite/v2/config_requirements_in_app_browser.rs index 007976c9bd..44f5d2fb35 100644 --- a/codex-rs/app-server/tests/suite/v2/config_requirements_in_app_browser.rs +++ b/codex-rs/app-server/tests/suite/v2/config_requirements_in_app_browser.rs @@ -106,6 +106,7 @@ async fn import_policy_is_separate_from_browser_feature_and_agent_policy() -> Re allow_external_browser_settings_import: Some(false), }), Some(BrowserUseRequirements { + extension: None, allow_webmcp: None, allow_history_access: None, disable_auto_review: Some(true), diff --git a/codex-rs/app-server/tests/suite/v2/config_rpc.rs b/codex-rs/app-server/tests/suite/v2/config_rpc.rs index 347db91b24..751324a173 100644 --- a/codex-rs/app-server/tests/suite/v2/config_rpc.rs +++ b/codex-rs/app-server/tests/suite/v2/config_rpc.rs @@ -245,6 +245,9 @@ allow_history_access = false disable_auto_review = true allow_global_persistent_approval = false +[browser_use.extension] +request_headers = [{ name = "x-browser-agent", value = "ChatGPT/{{session_id}}" }] + [browser_use.default_origin_policy] access = "deny" downloads = "allow" @@ -298,6 +301,12 @@ access = "deny" assert_eq!( requirements.browser_use, Some(BrowserUseRequirements { + extension: Some(codex_app_server_protocol::BrowserUseExtensionRequirements { + request_headers: Some(vec![codex_app_server_protocol::RequestHeader { + name: "x-browser-agent".to_string(), + value: "ChatGPT/{{session_id}}".to_string(), + }]), + }), allow_webmcp: None, allow_history_access: Some(false), disable_auto_review: Some(true), @@ -824,6 +833,7 @@ access = "deny" assert_eq!( requirements.browser_use, Some(BrowserUseRequirements { + extension: None, allow_webmcp: None, allow_history_access: Some(false), disable_auto_review: None, diff --git a/codex-rs/codex-mcp/src/rmcp_client.rs b/codex-rs/codex-mcp/src/rmcp_client.rs index 37089a380a..8e3a8d26b1 100644 --- a/codex-rs/codex-mcp/src/rmcp_client.rs +++ b/codex-rs/codex-mcp/src/rmcp_client.rs @@ -424,7 +424,11 @@ impl ManagedClientStartup { startup_complete.store(true, Ordering::Release); outcome }; - originator.scope(startup).in_current_span().boxed().shared() + originator + .scope(Box::pin(startup)) + .in_current_span() + .boxed() + .shared() } } diff --git a/codex-rs/config/src/browser_computer_use_requirements.rs b/codex-rs/config/src/browser_computer_use_requirements.rs index f0dba54095..88c18873d2 100644 --- a/codex-rs/config/src/browser_computer_use_requirements.rs +++ b/codex-rs/config/src/browser_computer_use_requirements.rs @@ -42,6 +42,7 @@ pub enum BrowserUseAccessApprovalLifetimeToml { #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] pub struct BrowserUseRequirementsToml { + pub extension: Option, pub allow_webmcp: Option, pub allow_history_access: Option, pub disable_auto_review: Option, @@ -52,7 +53,10 @@ pub struct BrowserUseRequirementsToml { impl BrowserUseRequirementsToml { pub fn is_empty(&self) -> bool { - self.allow_webmcp.is_none() + self.extension + .as_ref() + .is_none_or(|extension| extension.request_headers.is_none()) + && self.allow_webmcp.is_none() && self.allow_history_access.is_none() && self.disable_auto_review.is_none() && self.allow_global_persistent_approval.is_none() @@ -67,6 +71,27 @@ impl BrowserUseRequirementsToml { } } +#[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] +pub struct BrowserUseExtensionRequirementsToml { + pub request_headers: Option>, +} + +#[derive(Deserialize, Clone, PartialEq, Eq)] +#[serde(deny_unknown_fields)] +pub struct RequestHeaderToml { + pub name: String, + pub value: String, +} + +impl std::fmt::Debug for RequestHeaderToml { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("RequestHeaderToml") + .field("name", &self.name) + .field("value", &"[REDACTED]") + .finish() + } +} + #[derive(Deserialize, Debug, Clone, Default, PartialEq, Eq)] pub struct ComputerUseMacosRequirementsToml { pub bundle_ids: Option>, diff --git a/codex-rs/config/src/browser_computer_use_requirements_tests.rs b/codex-rs/config/src/browser_computer_use_requirements_tests.rs index 7635d3d24a..61484824eb 100644 --- a/codex-rs/config/src/browser_computer_use_requirements_tests.rs +++ b/codex-rs/config/src/browser_computer_use_requirements_tests.rs @@ -43,3 +43,94 @@ fn webmcp_requirements_reject_non_booleans() { assert!(error.to_string().contains("allow_webmcp")); } } + +#[test] +fn extension_headers_preserve_templates_and_explicit_disablement() { + for (value, expected) in [ + ("[]", vec![]), + ( + r#"[{name = "x-browser-agent", value = "ChatGPT/{{session_id}}"}]"#, + vec![crate::RequestHeaderToml { + name: "x-browser-agent".into(), + value: "ChatGPT/{{session_id}}".into(), + }], + ), + ] { + let requirements: ConfigRequirementsToml = toml::from_str(&format!( + "[browser_use.extension]\nrequest_headers = {value}" + )) + .unwrap(); + assert!(!requirements.is_empty()); + assert_eq!( + requirements + .browser_use + .unwrap() + .extension + .unwrap() + .request_headers, + Some(expected) + ); + } +} + +#[test] +fn extension_headers_reject_malformed_entries() { + for value in [ + "true", + r#"[{name = "x-test"}]"#, + r#"[{name = "x-test", value = 1}]"#, + r#"[{name = "x-test", value = "ok", typo = true}]"#, + ] { + assert!( + toml::from_str::(&format!( + "[browser_use.extension]\nrequest_headers = {value}" + )) + .is_err() + ); + } +} + +#[test] +fn extension_headers_remain_scoped_to_extension() { + let extension = r#"[browser_use.extension] +request_headers = [{ name = "x-extension", value = "{{session_id}}" }]"#; + let other = r#"[browser_use.iab] +request_headers = [{ name = "x-iab", value = "future" }]"#; + let expected: ConfigRequirementsToml = toml::from_str(extension).unwrap(); + let combined: ConfigRequirementsToml = + toml::from_str(&format!("{extension}\n{other}")).unwrap(); + assert_eq!(combined, expected); + for contents in [other, "[browser_use.extension]"] { + let requirements: ConfigRequirementsToml = toml::from_str(contents).unwrap(); + assert!(requirements.is_empty()); + } +} + +#[test] +fn request_header_debug_redacts_values_in_parent_requirements() { + let requirements: ConfigRequirementsToml = toml::from_str( + r#"[browser_use.extension] +request_headers = [{ name = "x-company-token", value = "private-header-token" }]"#, + ) + .unwrap(); + let header = &requirements + .browser_use + .as_ref() + .unwrap() + .extension + .as_ref() + .unwrap() + .request_headers + .as_ref() + .unwrap()[0]; + assert_eq!(header.value, "private-header-token"); + assert_eq!( + format!("{header:?}"), + r#"RequestHeaderToml { name: "x-company-token", value: "[REDACTED]" }"# + ); + for debug in [format!("{requirements:?}"), format!("{requirements:#?}")] { + assert!(!debug.contains("private-header-token")); + assert!(debug.contains("x-company-token")); + assert!(debug.contains("[REDACTED]")); + } +} diff --git a/codex-rs/config/src/config_requirements.rs b/codex-rs/config/src/config_requirements.rs index 52eefeb6cc..250b3b3c15 100644 --- a/codex-rs/config/src/config_requirements.rs +++ b/codex-rs/config/src/config_requirements.rs @@ -2577,6 +2577,7 @@ mod tests { assert_eq!( requirements.browser_use, Some(BrowserUseRequirementsToml { + extension: None, allow_webmcp: None, allow_history_access: Some(false), disable_auto_review: Some(true), @@ -2810,6 +2811,7 @@ mod tests { entries: BTreeMap::from([("personality".to_string(), true)]), }; let browser_use = BrowserUseRequirementsToml { + extension: None, allow_webmcp: None, allow_history_access: Some(false), disable_auto_review: Some(true), diff --git a/codex-rs/config/src/lib.rs b/codex-rs/config/src/lib.rs index abb0603ece..b51ad2f52f 100644 --- a/codex-rs/config/src/lib.rs +++ b/codex-rs/config/src/lib.rs @@ -56,12 +56,14 @@ pub use application_requirements::ApplicationRequirementsToml; pub use auth_policy::ManagedAuthPolicy; pub use browser_computer_use_requirements::AllowDenyRequirementToml; pub use browser_computer_use_requirements::BrowserUseAccessApprovalLifetimeToml; +pub use browser_computer_use_requirements::BrowserUseExtensionRequirementsToml; pub use browser_computer_use_requirements::BrowserUseOriginPolicyToml; pub use browser_computer_use_requirements::BrowserUseRequirementsToml; pub use browser_computer_use_requirements::ComputerUseMacosRequirementsToml; pub use browser_computer_use_requirements::ComputerUseRequirementsToml; pub use browser_computer_use_requirements::ComputerUseWindowsExeRequirementToml; pub use browser_computer_use_requirements::ComputerUseWindowsRequirementsToml; +pub use browser_computer_use_requirements::RequestHeaderToml; pub use browser_use::BrowserUseConfigToml; pub use browser_use::BrowserUseOriginPolicyConfigToml; pub use cloud_config_bundle::CloudConfigBundle; diff --git a/codex-rs/config/src/requirements_layers/stack_tests.rs b/codex-rs/config/src/requirements_layers/stack_tests.rs index 1453a09da2..0cf432ce78 100644 --- a/codex-rs/config/src/requirements_layers/stack_tests.rs +++ b/codex-rs/config/src/requirements_layers/stack_tests.rs @@ -1588,3 +1588,26 @@ path = "../plugins" ) ); } + +#[test] +fn extension_headers_replace_lower_priority_lists_including_explicit_empty() { + for higher in [ + r#"[browser_use.extension] +request_headers = []"#, + r#"[browser_use.extension] +request_headers = [{ name = "x-managed", value = "{{session_id}}" }]"#, + ] { + let composed = compose(vec![ + layer( + "low", + "Lower priority", + r#"[browser_use.extension] +request_headers = [{ name = "x-old", value = "old" }]"#, + ), + layer("high", "Higher priority", higher), + ]) + .expect("compose requirements") + .expect("requirements present"); + assert_eq!(composed, expected_requirements(higher)); + } +} diff --git a/sdk/python/src/openai_codex/generated/v2_all.py b/sdk/python/src/openai_codex/generated/v2_all.py index be2cbceea0..16dd0dcbf7 100644 --- a/sdk/python/src/openai_codex/generated/v2_all.py +++ b/sdk/python/src/openai_codex/generated/v2_all.py @@ -465,22 +465,6 @@ class BrowserUseOriginPolicyConfig(BaseModel): uploads: AllowDenyRequirement | None = None -class BrowserUseRequirements(BaseModel): - model_config = ConfigDict( - populate_by_name=True, - ) - allow_global_persistent_approval: Annotated[ - bool | None, Field(alias="allowGlobalPersistentApproval") - ] = None - allow_history_access: Annotated[bool | None, Field(alias="allowHistoryAccess")] = None - allow_webmcp: Annotated[bool | None, Field(alias="allowWebmcp")] = None - default_origin_policy: Annotated[ - BrowserUseOriginPolicy | None, Field(alias="defaultOriginPolicy") - ] = None - disable_auto_review: Annotated[bool | None, Field(alias="disableAutoReview")] = None - origins: dict[str, Any] | None = None - - class ByteRange(BaseModel): model_config = ConfigDict( populate_by_name=True, @@ -3872,6 +3856,14 @@ class RemoteControlStatusChangedNotification(BaseModel): status: RemoteControlConnectionStatus +class RequestHeader(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + name: str + value: str + + class RequestId(RootModel[str | int]): model_config = ConfigDict( populate_by_name=True, @@ -6860,6 +6852,30 @@ class BrowserUseConfig(BaseModel): origins: dict[str, Any] | None = None +class BrowserUseExtensionRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + request_headers: Annotated[list[RequestHeader] | None, Field(alias="requestHeaders")] = None + + +class BrowserUseRequirements(BaseModel): + model_config = ConfigDict( + populate_by_name=True, + ) + allow_global_persistent_approval: Annotated[ + bool | None, Field(alias="allowGlobalPersistentApproval") + ] = None + allow_history_access: Annotated[bool | None, Field(alias="allowHistoryAccess")] = None + allow_webmcp: Annotated[bool | None, Field(alias="allowWebmcp")] = None + default_origin_policy: Annotated[ + BrowserUseOriginPolicy | None, Field(alias="defaultOriginPolicy") + ] = None + disable_auto_review: Annotated[bool | None, Field(alias="disableAutoReview")] = None + extension: BrowserUseExtensionRequirements | None = None + origins: dict[str, Any] | None = None + + class CancelLoginAccountResponse(BaseModel): model_config = ConfigDict( populate_by_name=True,