[plugins] Inject remote_plugin_id into install elicitations (#26409)

Summary
- Propagate cached remote plugin IDs through Codex plugin discovery.
- Inject `remote_plugin_id` and connector IDs into
`request_plugin_install` elicitation `_meta` from the resolved plugin.
- Keep the remote plugin ID out of the model-facing tool schema,
arguments, and result.

Validation
- `just test -p codex-tools`
- `just test -p codex-core-plugins`
- `just test -p codex-core
list_tool_suggest_discoverable_plugins_includes_cached_remote_global_plugins`
- `just fix -p codex-tools`
- `just fix -p codex-core-plugins`
- `just fix -p codex-core`
- `git diff --check`
- `just test -p codex-core` was also attempted: 2,581 passed, 55 failed,
and 1 timed out across unrelated sandbox/environment-sensitive
integration tests.
This commit is contained in:
Alex Daley
2026-06-10 12:01:03 -07:00
committed by GitHub
Unverified
parent 72667f4f41
commit 020bf49346
9 changed files with 58 additions and 18 deletions
@@ -68,6 +68,7 @@ pub struct ToolSuggestPluginDiscoveryInput {
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ToolSuggestDiscoverablePlugin {
pub id: String,
pub remote_plugin_id: Option<String>,
pub name: String,
pub description: Option<String>,
pub has_skills: bool,
@@ -162,6 +163,7 @@ impl PluginsManager {
discoverable_plugins.push(ToolSuggestDiscoverablePlugin {
id: plugin.config_name,
remote_plugin_id: None,
name: plugin.display_name,
description: plugin.description,
has_skills: plugin.has_skills,
@@ -217,6 +219,7 @@ impl PluginsManager {
discoverable_plugins.push(ToolSuggestDiscoverablePlugin {
id: plugin.config_id,
remote_plugin_id: Some(plugin.remote_plugin_id),
name: plugin.name,
description: plugin.description,
has_skills: plugin.has_skills,
@@ -41,6 +41,7 @@ pub(crate) async fn list_tool_suggest_discoverable_plugins(
.into_iter()
.map(|plugin| DiscoverablePluginInfo {
id: plugin.id,
remote_plugin_id: plugin.remote_plugin_id,
name: plugin.name,
description: plugin.description,
has_skills: plugin.has_skills,
@@ -393,6 +393,7 @@ remote_plugin = true
remote_plugins,
vec![DiscoverablePluginInfo {
id: "github@openai-curated-remote".to_string(),
remote_plugin_id: Some("plugins~Plugin_remote_github".to_string()),
name: "Remote GitHub".to_string(),
description: Some("Remote GitHub short".to_string()),
has_skills: true,
@@ -680,6 +681,7 @@ async fn list_tool_suggest_discoverable_plugins_normalizes_description() {
discoverable_plugins,
vec![DiscoverablePluginInfo {
id: "slack@openai-curated".to_string(),
remote_plugin_id: None,
name: "slack".to_string(),
description: Some("Plugin with extra spacing".to_string()),
has_skills: true,
@@ -816,6 +818,7 @@ discoverables = [{ type = "plugin", id = "sample@openai-curated" }]
discoverable_plugins,
vec![DiscoverablePluginInfo {
id: "sample@openai-curated".to_string(),
remote_plugin_id: None,
name: "sample".to_string(),
description: Some(
"Plugin that includes skills, MCP servers, and app connectors".to_string(),
@@ -130,6 +130,7 @@ async fn persist_disabled_install_request_writes_plugin_config() {
let codex_home = tempdir().expect("tempdir should succeed");
let tool = DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {
id: "slack@openai-curated".to_string(),
remote_plugin_id: None,
name: "Slack".to_string(),
description: None,
has_skills: true,
@@ -400,6 +400,7 @@ fn dynamic_tool(namespace: Option<&str>, name: &str, defer_loading: bool) -> Dyn
fn discoverable_plugin(id: &str, name: &str) -> DiscoverableTool {
DiscoverablePluginInfo {
id: id.to_string(),
remote_plugin_id: None,
name: name.to_string(),
description: Some(format!("{name} plugin")),
has_skills: false,
+18 -11
View File
@@ -47,6 +47,10 @@ pub struct RequestPluginInstallMeta<'a> {
pub tool_name: &'a str,
#[serde(skip_serializing_if = "Option::is_none")]
pub install_url: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub remote_plugin_id: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
pub app_connector_ids: Option<&'a [String]>,
}
pub fn build_request_plugin_install_elicitation_request(
@@ -57,8 +61,6 @@ pub fn build_request_plugin_install_elicitation_request(
suggest_reason: &str,
tool: &DiscoverableTool,
) -> McpServerElicitationRequestParams {
let tool_name = tool.name().to_string();
let install_url = tool.install_url().map(ToString::to_string);
let message = suggest_reason.to_string();
McpServerElicitationRequestParams {
@@ -70,9 +72,7 @@ pub fn build_request_plugin_install_elicitation_request(
args.tool_type,
args.action_type,
suggest_reason,
tool.id(),
tool_name.as_str(),
install_url.as_deref(),
tool,
))),
message,
requested_schema: McpElicitationSchema {
@@ -108,19 +108,26 @@ fn build_request_plugin_install_meta<'a>(
tool_type: DiscoverableToolType,
action_type: DiscoverableToolAction,
suggest_reason: &'a str,
tool_id: &'a str,
tool_name: &'a str,
install_url: Option<&'a str>,
tool: &'a DiscoverableTool,
) -> RequestPluginInstallMeta<'a> {
let (remote_plugin_id, app_connector_ids) = match tool {
DiscoverableTool::Connector(_) => (None, None),
DiscoverableTool::Plugin(plugin) => (
plugin.remote_plugin_id.as_deref(),
Some(plugin.app_connector_ids.as_slice()),
),
};
RequestPluginInstallMeta {
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
tool_type,
suggest_type: action_type,
suggest_reason,
tool_id,
tool_name,
install_url,
tool_id: tool.id(),
tool_name: tool.name(),
install_url: tool.install_url(),
remote_plugin_id,
app_connector_ids,
}
}
@@ -57,6 +57,8 @@ fn build_request_plugin_install_elicitation_request_uses_expected_shape() {
install_url: Some(
"https://chatgpt.com/apps/google-calendar/connector_2128aebfecb84f64a069897515042a44"
),
remote_plugin_id: None,
app_connector_ids: None,
})),
message: "Plan and reference events from your calendar".to_string(),
requested_schema: McpElicitationSchema {
@@ -71,15 +73,16 @@ fn build_request_plugin_install_elicitation_request_uses_expected_shape() {
}
#[test]
fn build_request_plugin_install_elicitation_request_for_plugin_omits_install_url() {
fn build_request_plugin_install_elicitation_request_injects_plugin_metadata() {
let args = RequestPluginInstallArgs {
tool_type: DiscoverableToolType::Plugin,
action_type: DiscoverableToolAction::Install,
tool_id: "sample@openai-curated".to_string(),
tool_id: "sample@openai-curated-remote".to_string(),
suggest_reason: "Use the sample plugin's skills and MCP server".to_string(),
};
let plugin = DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {
id: "sample@openai-curated".to_string(),
id: "sample@openai-curated-remote".to_string(),
remote_plugin_id: Some("plugins~Plugin_sample".to_string()),
name: "Sample Plugin".to_string(),
description: Some("Includes skills, MCP servers, and apps.".to_string()),
has_skills: true,
@@ -109,9 +112,11 @@ fn build_request_plugin_install_elicitation_request_for_plugin_omits_install_url
tool_type: DiscoverableToolType::Plugin,
suggest_type: DiscoverableToolAction::Install,
suggest_reason: "Use the sample plugin's skills and MCP server",
tool_id: "sample@openai-curated",
tool_id: "sample@openai-curated-remote",
tool_name: "Sample Plugin",
install_url: None,
remote_plugin_id: Some("plugins~Plugin_sample"),
app_connector_ids: Some(&["connector_calendar".to_string()]),
})),
message: "Use the sample plugin's skills and MCP server".to_string(),
requested_schema: McpElicitationSchema {
@@ -127,13 +132,28 @@ fn build_request_plugin_install_elicitation_request_for_plugin_omits_install_url
#[test]
fn build_request_plugin_install_meta_uses_expected_shape() {
let connector = DiscoverableTool::Connector(Box::new(AppInfo {
id: "connector_68df038e0ba48191908c8434991bbac2".to_string(),
name: "Gmail".to_string(),
description: None,
logo_url: None,
logo_url_dark: None,
distribution_channel: None,
branding: None,
app_metadata: None,
labels: None,
install_url: Some(
"https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2".to_string(),
),
is_accessible: false,
is_enabled: true,
plugin_display_names: Vec::new(),
}));
let meta = build_request_plugin_install_meta(
DiscoverableToolType::Connector,
DiscoverableToolAction::Install,
"Find and reference emails from your inbox",
"connector_68df038e0ba48191908c8434991bbac2",
"Gmail",
Some("https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2"),
&connector,
);
assert_eq!(
@@ -149,6 +169,8 @@ fn build_request_plugin_install_meta_uses_expected_shape() {
install_url: Some(
"https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2"
),
remote_plugin_id: None,
app_connector_ids: None,
},
);
}
+1
View File
@@ -93,6 +93,7 @@ pub fn filter_request_plugin_install_discoverable_tools_for_client(
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct DiscoverablePluginInfo {
pub id: String,
pub remote_plugin_id: Option<String>,
pub name: String,
pub description: Option<String>,
pub has_skills: bool,
@@ -37,6 +37,7 @@ fn filter_request_plugin_install_discoverable_tools_for_codex_tui_omits_plugins(
})),
DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {
id: "slack@openai-curated".to_string(),
remote_plugin_id: None,
name: "Slack".to_string(),
description: Some("Search Slack messages".to_string()),
has_skills: true,