diff --git a/codex-rs/config/src/types.rs b/codex-rs/config/src/types.rs index 55326030f0..8846fb3d95 100644 --- a/codex-rs/config/src/types.rs +++ b/codex-rs/config/src/types.rs @@ -1116,6 +1116,26 @@ pub struct PluginConfig { #[serde(default, skip_serializing_if = "Option::is_none")] pub enabled: Option, + /// Stable, unique identifier of a ChatGPT-hosted remote plugin. + /// Availability to the requesting user is determined by Plugin Service at + /// provisioning time. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub remote_plugin_id: Option, + + /// Release identifier used to pin the remote plugin to a specific release. + /// Omission leaves release selection unchanged. + /// Core stores this metadata without selecting or fetching a release. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub release_id: Option, + + /// Provisioning metadata. Omission does not request provisioning. + /// Only applies with `remote_plugin_id` and Plugin Service availability to the + /// requesting user at provisioning time. Effective enablement, trusted + /// provisioning policy, and authentication and authorization restrictions still apply. + /// This value is not trusted authorization and does not trigger provisioning. + #[serde(default, skip_serializing_if = "Option::is_none")] + pub auto_provision: Option, + /// Per-MCP-server policy overlays for MCP servers contributed by this plugin. #[serde(default, skip_serializing_if = "HashMap::is_empty")] pub mcp_servers: HashMap, diff --git a/codex-rs/config/src/types_tests.rs b/codex-rs/config/src/types_tests.rs index 5b5303b6d9..5a78075bfe 100644 --- a/codex-rs/config/src/types_tests.rs +++ b/codex-rs/config/src/types_tests.rs @@ -18,6 +18,10 @@ default_tools_approval_mode = "approve" enabled = true ["disabled@market"] enabled = false +["remote@market"] +remote_plugin_id = "remote-plugin-123" +release_id = "plugin-release-123" +auto_provision = true "# )) .expect("plugin activation config"); @@ -27,10 +31,11 @@ enabled = false "mcp@market", "approval@market", "enabled@market", - "disabled@market" + "disabled@market", + "remote@market" ] .map(|plugin_id| config.plugins[plugin_id].enabled), - [None, None, None, Some(true), Some(false)], + [None, None, None, Some(true), Some(false), None], ); assert_eq!( [ @@ -39,13 +44,14 @@ enabled = false "mcp@market", "approval@market", "enabled@market", - "disabled@market" + "disabled@market", + "remote@market" ] .map(|plugin_id| config.allows_plugin(plugin_id)), match default_enabled { - None => [true, true, true, true, true, true], - Some(false) => [false, false, false, false, true, false], - Some(true) => [true, true, true, true, true, false], + None => [true, true, true, true, true, true, true], + Some(false) => [false, false, false, false, true, false, false], + Some(true) => [true, true, true, true, true, false, true], }, ); let round_trip: PluginsConfigToml = diff --git a/codex-rs/core-plugins/src/loader.rs b/codex-rs/core-plugins/src/loader.rs index 03f7823cd9..f43575d57e 100644 --- a/codex-rs/core-plugins/src/loader.rs +++ b/codex-rs/core-plugins/src/loader.rs @@ -382,6 +382,9 @@ pub fn remote_installed_plugins_to_config( plugin_id.as_key(), PluginConfig { enabled: Some(plugin.enabled), + remote_plugin_id: None, + release_id: None, + auto_provision: None, mcp_servers: HashMap::new(), }, )) diff --git a/codex-rs/core-plugins/src/loader_tests.rs b/codex-rs/core-plugins/src/loader_tests.rs index 605879bea8..be59a1148a 100644 --- a/codex-rs/core-plugins/src/loader_tests.rs +++ b/codex-rs/core-plugins/src/loader_tests.rs @@ -377,6 +377,9 @@ fn configured_plugins_from_stack_merges_enabled_effective_layers() { "base".to_string(), PluginConfig { enabled: Some(true), + remote_plugin_id: None, + release_id: None, + auto_provision: None, mcp_servers: HashMap::new(), }, ), @@ -384,6 +387,9 @@ fn configured_plugins_from_stack_merges_enabled_effective_layers() { "profile".to_string(), PluginConfig { enabled: Some(true), + remote_plugin_id: None, + release_id: None, + auto_provision: None, mcp_servers: project_mcp_servers.clone(), }, ), @@ -391,6 +397,9 @@ fn configured_plugins_from_stack_merges_enabled_effective_layers() { "system".to_string(), PluginConfig { enabled: Some(true), + remote_plugin_id: None, + release_id: None, + auto_provision: None, mcp_servers: HashMap::new(), }, ), @@ -622,6 +631,16 @@ enabled = true false, HashMap::new(), ), + ( + "[plugins.\"valid@test\"]\nremote_plugin_id = 'remote-plugin-123'\nrelease_id = 'plugin-release-123'\nauto_provision = true", + true, + HashMap::new(), + ), + ( + "[plugins._default]\nenabled = false\n[plugins.\"valid@test\"]\nremote_plugin_id = 'remote-plugin-123'\nrelease_id = 'plugin-release-123'\nauto_provision = true", + false, + HashMap::new(), + ), ] { let stack = ConfigLayerStack::default() .with_user_config( diff --git a/codex-rs/core-plugins/src/manager_tests.rs b/codex-rs/core-plugins/src/manager_tests.rs index 6ba94a6738..55eca550cb 100644 --- a/codex-rs/core-plugins/src/manager_tests.rs +++ b/codex-rs/core-plugins/src/manager_tests.rs @@ -3294,6 +3294,9 @@ fn loaded_plugins_cache_evicts_least_recently_used_configuration() { format!("plugin-{index}@test"), PluginConfig { enabled: Some(true), + remote_plugin_id: None, + release_id: None, + auto_provision: None, mcp_servers: HashMap::new(), }, )]), diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index 2a31fc816f..8ddfa9b558 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -3684,6 +3684,10 @@ "PluginConfig": { "additionalProperties": false, "properties": { + "auto_provision": { + "description": "Provisioning metadata. Omission does not request provisioning. Only applies with `remote_plugin_id` and Plugin Service availability to the requesting user at provisioning time. Effective enablement, trusted provisioning policy, and authentication and authorization restrictions still apply. This value is not trusted authorization and does not trigger provisioning.", + "type": "boolean" + }, "enabled": { "description": "Explicit activation override. Omission inherits the installed-plugin default.", "type": "boolean" @@ -3694,6 +3698,14 @@ }, "description": "Per-MCP-server policy overlays for MCP servers contributed by this plugin.", "type": "object" + }, + "release_id": { + "description": "Release identifier used to pin the remote plugin to a specific release. Omission leaves release selection unchanged. Core stores this metadata without selecting or fetching a release.", + "type": "string" + }, + "remote_plugin_id": { + "description": "Stable, unique identifier of a ChatGPT-hosted remote plugin. Availability to the requesting user is determined by Plugin Service at provisioning time.", + "type": "string" } }, "type": "object"