mirror of
https://github.com/openai/codex.git
synced 2026-10-22 17:58:46 +00:00
feat: include remote plugin metadata in install suggestions
This commit is contained in:
@@ -64,6 +64,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,
|
||||
@@ -145,6 +146,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,
|
||||
@@ -200,6 +202,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,
|
||||
|
||||
@@ -406,6 +406,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,
|
||||
|
||||
@@ -46,9 +46,18 @@ pub struct RequestPluginInstallMeta<'a> {
|
||||
pub tool_id: &'a str,
|
||||
pub tool_name: &'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_ids: Option<&'a [String]>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub install_url: Option<&'a str>,
|
||||
}
|
||||
|
||||
struct RemotePluginInstallMeta<'a> {
|
||||
remote_plugin_id: &'a str,
|
||||
app_ids: &'a [String],
|
||||
}
|
||||
|
||||
pub fn build_request_plugin_install_elicitation_request(
|
||||
server_name: &str,
|
||||
thread_id: String,
|
||||
@@ -59,6 +68,18 @@ pub fn build_request_plugin_install_elicitation_request(
|
||||
) -> McpServerElicitationRequestParams {
|
||||
let tool_name = tool.name().to_string();
|
||||
let install_url = tool.install_url().map(ToString::to_string);
|
||||
let remote_plugin = match tool {
|
||||
DiscoverableTool::Connector(_) => None,
|
||||
DiscoverableTool::Plugin(plugin) => {
|
||||
plugin
|
||||
.remote_plugin_id
|
||||
.as_deref()
|
||||
.map(|remote_plugin_id| RemotePluginInstallMeta {
|
||||
remote_plugin_id,
|
||||
app_ids: plugin.app_connector_ids.as_slice(),
|
||||
})
|
||||
}
|
||||
};
|
||||
let message = suggest_reason.to_string();
|
||||
|
||||
McpServerElicitationRequestParams {
|
||||
@@ -72,6 +93,7 @@ pub fn build_request_plugin_install_elicitation_request(
|
||||
suggest_reason,
|
||||
tool.id(),
|
||||
tool_name.as_str(),
|
||||
remote_plugin,
|
||||
install_url.as_deref(),
|
||||
))),
|
||||
message,
|
||||
@@ -110,8 +132,17 @@ fn build_request_plugin_install_meta<'a>(
|
||||
suggest_reason: &'a str,
|
||||
tool_id: &'a str,
|
||||
tool_name: &'a str,
|
||||
remote_plugin: Option<RemotePluginInstallMeta<'a>>,
|
||||
install_url: Option<&'a str>,
|
||||
) -> RequestPluginInstallMeta<'a> {
|
||||
let (remote_plugin_id, app_ids) = match remote_plugin {
|
||||
Some(remote_plugin) => (
|
||||
Some(remote_plugin.remote_plugin_id),
|
||||
Some(remote_plugin.app_ids),
|
||||
),
|
||||
None => (None, None),
|
||||
};
|
||||
|
||||
RequestPluginInstallMeta {
|
||||
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
|
||||
persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
|
||||
@@ -120,6 +151,8 @@ fn build_request_plugin_install_meta<'a>(
|
||||
suggest_reason,
|
||||
tool_id,
|
||||
tool_name,
|
||||
remote_plugin_id,
|
||||
app_ids,
|
||||
install_url,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,8 @@ fn build_request_plugin_install_elicitation_request_uses_expected_shape() {
|
||||
suggest_reason: "Plan and reference events from your calendar",
|
||||
tool_id: "connector_2128aebfecb84f64a069897515042a44",
|
||||
tool_name: "Google Calendar",
|
||||
remote_plugin_id: None,
|
||||
app_ids: None,
|
||||
install_url: Some(
|
||||
"https://chatgpt.com/apps/google-calendar/connector_2128aebfecb84f64a069897515042a44"
|
||||
),
|
||||
@@ -80,6 +82,7 @@ fn build_request_plugin_install_elicitation_request_for_plugin_omits_install_url
|
||||
};
|
||||
let plugin = DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {
|
||||
id: "sample@openai-curated".to_string(),
|
||||
remote_plugin_id: None,
|
||||
name: "Sample Plugin".to_string(),
|
||||
description: Some("Includes skills, MCP servers, and apps.".to_string()),
|
||||
has_skills: true,
|
||||
@@ -111,6 +114,8 @@ fn build_request_plugin_install_elicitation_request_for_plugin_omits_install_url
|
||||
suggest_reason: "Use the sample plugin's skills and MCP server",
|
||||
tool_id: "sample@openai-curated",
|
||||
tool_name: "Sample Plugin",
|
||||
remote_plugin_id: None,
|
||||
app_ids: None,
|
||||
install_url: None,
|
||||
})),
|
||||
message: "Use the sample plugin's skills and MCP server".to_string(),
|
||||
@@ -133,6 +138,7 @@ fn build_request_plugin_install_meta_uses_expected_shape() {
|
||||
"Find and reference emails from your inbox",
|
||||
"connector_68df038e0ba48191908c8434991bbac2",
|
||||
"Gmail",
|
||||
/*remote_plugin*/ None,
|
||||
Some("https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2"),
|
||||
);
|
||||
|
||||
@@ -146,6 +152,8 @@ fn build_request_plugin_install_meta_uses_expected_shape() {
|
||||
suggest_reason: "Find and reference emails from your inbox",
|
||||
tool_id: "connector_68df038e0ba48191908c8434991bbac2",
|
||||
tool_name: "Gmail",
|
||||
remote_plugin_id: None,
|
||||
app_ids: None,
|
||||
install_url: Some(
|
||||
"https://chatgpt.com/apps/gmail/connector_68df038e0ba48191908c8434991bbac2"
|
||||
),
|
||||
@@ -153,6 +161,65 @@ fn build_request_plugin_install_meta_uses_expected_shape() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn build_request_plugin_install_elicitation_request_for_remote_plugin_includes_directory_metadata()
|
||||
{
|
||||
let args = RequestPluginInstallArgs {
|
||||
tool_type: DiscoverableToolType::Plugin,
|
||||
action_type: DiscoverableToolAction::Install,
|
||||
tool_id: "slack@openai-curated-remote".to_string(),
|
||||
suggest_reason: "Search and reference Slack messages".to_string(),
|
||||
};
|
||||
let plugin = DiscoverableTool::Plugin(Box::new(DiscoverablePluginInfo {
|
||||
id: "slack@openai-curated-remote".to_string(),
|
||||
remote_plugin_id: Some("plugins~Plugin_slack".to_string()),
|
||||
name: "Slack".to_string(),
|
||||
description: Some("Search Slack messages".to_string()),
|
||||
has_skills: true,
|
||||
mcp_server_names: Vec::new(),
|
||||
app_connector_ids: vec!["connector_slack".to_string()],
|
||||
}));
|
||||
|
||||
let request = build_request_plugin_install_elicitation_request(
|
||||
"codex-apps",
|
||||
"thread-1".to_string(),
|
||||
"turn-1".to_string(),
|
||||
&args,
|
||||
"Search and reference Slack messages",
|
||||
&plugin,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
request,
|
||||
McpServerElicitationRequestParams {
|
||||
thread_id: "thread-1".to_string(),
|
||||
turn_id: Some("turn-1".to_string()),
|
||||
server_name: "codex-apps".to_string(),
|
||||
request: McpServerElicitationRequest::Form {
|
||||
meta: Some(json!(RequestPluginInstallMeta {
|
||||
codex_approval_kind: REQUEST_PLUGIN_INSTALL_APPROVAL_KIND_VALUE,
|
||||
persist: REQUEST_PLUGIN_INSTALL_PERSIST_ALWAYS_VALUE,
|
||||
tool_type: DiscoverableToolType::Plugin,
|
||||
suggest_type: DiscoverableToolAction::Install,
|
||||
suggest_reason: "Search and reference Slack messages",
|
||||
tool_id: "slack@openai-curated-remote",
|
||||
tool_name: "Slack",
|
||||
remote_plugin_id: Some("plugins~Plugin_slack"),
|
||||
app_ids: Some(&["connector_slack".to_string()]),
|
||||
install_url: None,
|
||||
})),
|
||||
message: "Search and reference Slack messages".to_string(),
|
||||
requested_schema: McpElicitationSchema {
|
||||
schema_uri: None,
|
||||
type_: McpElicitationObjectType::Object,
|
||||
properties: BTreeMap::new(),
|
||||
required: None,
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn verified_connector_install_completed_requires_accessible_connector() {
|
||||
let accessible_connectors = vec![AppInfo {
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user