mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Move Computer Use tool suggestion to core (#18219)
## Summary Move the Computer Use tool suggestion into core Codex plugin discovery. Also search `openai-bundled` when listing suggested plugins, with test coverage for overlap between baked-in suggestions and `tool_suggest.discoverables`. ## Test plan Tested locally: - `cargo test -p codex-core list_tool_suggest_discoverable_plugins`
This commit is contained in:
@@ -2,6 +2,7 @@ use anyhow::Context;
|
||||
use std::collections::HashSet;
|
||||
use tracing::warn;
|
||||
|
||||
use super::OPENAI_BUNDLED_MARKETPLACE_NAME;
|
||||
use super::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
use super::PluginCapabilitySummary;
|
||||
use super::PluginsManager;
|
||||
@@ -19,6 +20,12 @@ const TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST: &[&str] = &[
|
||||
"google-drive@openai-curated",
|
||||
"linear@openai-curated",
|
||||
"figma@openai-curated",
|
||||
"computer-use@openai-bundled",
|
||||
];
|
||||
|
||||
const TOOL_SUGGEST_DISCOVERABLE_MARKETPLACE_ALLOWLIST: &[&str] = &[
|
||||
OPENAI_BUNDLED_MARKETPLACE_NAME,
|
||||
OPENAI_CURATED_MARKETPLACE_NAME,
|
||||
];
|
||||
|
||||
pub(crate) async fn list_tool_suggest_discoverable_plugins(
|
||||
@@ -40,45 +47,46 @@ pub(crate) async fn list_tool_suggest_discoverable_plugins(
|
||||
.list_marketplaces_for_config(config, &[])
|
||||
.context("failed to list plugin marketplaces for tool suggestions")?
|
||||
.marketplaces;
|
||||
let Some(curated_marketplace) = marketplaces
|
||||
.into_iter()
|
||||
.find(|marketplace| marketplace.name == OPENAI_CURATED_MARKETPLACE_NAME)
|
||||
else {
|
||||
return Ok(Vec::new());
|
||||
};
|
||||
let curated_marketplace_name = curated_marketplace.name;
|
||||
|
||||
let mut discoverable_plugins = Vec::<DiscoverablePluginInfo>::new();
|
||||
for plugin in curated_marketplace.plugins {
|
||||
if plugin.installed
|
||||
|| (!TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST.contains(&plugin.id.as_str())
|
||||
&& !configured_plugin_ids.contains(plugin.id.as_str()))
|
||||
{
|
||||
for marketplace in marketplaces {
|
||||
let marketplace_name = marketplace.name;
|
||||
if !TOOL_SUGGEST_DISCOVERABLE_MARKETPLACE_ALLOWLIST.contains(&marketplace_name.as_str()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let plugin_id = plugin.id.clone();
|
||||
|
||||
match plugins_manager
|
||||
.read_plugin_detail_for_marketplace_plugin(config, &curated_marketplace_name, plugin)
|
||||
.await
|
||||
{
|
||||
Ok(plugin) => {
|
||||
let plugin: PluginCapabilitySummary = plugin.into();
|
||||
discoverable_plugins.push(DiscoverablePluginInfo {
|
||||
id: plugin.config_name,
|
||||
name: plugin.display_name,
|
||||
description: plugin.description,
|
||||
has_skills: plugin.has_skills,
|
||||
mcp_server_names: plugin.mcp_server_names,
|
||||
app_connector_ids: plugin
|
||||
.app_connector_ids
|
||||
.into_iter()
|
||||
.map(|connector_id| connector_id.0)
|
||||
.collect(),
|
||||
});
|
||||
for plugin in marketplace.plugins {
|
||||
if plugin.installed
|
||||
|| (!TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST.contains(&plugin.id.as_str())
|
||||
&& !configured_plugin_ids.contains(plugin.id.as_str()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
let plugin_id = plugin.id.clone();
|
||||
|
||||
match plugins_manager
|
||||
.read_plugin_detail_for_marketplace_plugin(config, &marketplace_name, plugin)
|
||||
.await
|
||||
{
|
||||
Ok(plugin) => {
|
||||
let plugin: PluginCapabilitySummary = plugin.into();
|
||||
discoverable_plugins.push(DiscoverablePluginInfo {
|
||||
id: plugin.config_name,
|
||||
name: plugin.display_name,
|
||||
description: plugin.description,
|
||||
has_skills: plugin.has_skills,
|
||||
mcp_server_names: plugin.mcp_server_names,
|
||||
app_connector_ids: plugin
|
||||
.app_connector_ids
|
||||
.into_iter()
|
||||
.map(|connector_id| connector_id.0)
|
||||
.collect(),
|
||||
});
|
||||
}
|
||||
Err(err) => {
|
||||
warn!("failed to load discoverable plugin suggestion {plugin_id}: {err:#}")
|
||||
}
|
||||
}
|
||||
Err(err) => warn!("failed to load discoverable plugin suggestion {plugin_id}: {err:#}"),
|
||||
}
|
||||
}
|
||||
discoverable_plugins.sort_by(|left, right| {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use super::*;
|
||||
use crate::plugins::PluginInstallRequest;
|
||||
use crate::plugins::test_support::load_plugins_config;
|
||||
use crate::plugins::test_support::write_curated_plugin;
|
||||
use crate::plugins::test_support::write_curated_plugin_sha;
|
||||
use crate::plugins::test_support::write_file;
|
||||
use crate::plugins::test_support::write_openai_curated_marketplace;
|
||||
@@ -40,6 +41,115 @@ async fn list_tool_suggest_discoverable_plugins_returns_uninstalled_curated_plug
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_tool_suggest_discoverable_plugins_deduplicates_allowlisted_configured_plugin() {
|
||||
let codex_home = tempdir().expect("tempdir should succeed");
|
||||
let plugin_id = TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|plugin_id| {
|
||||
plugin_id
|
||||
.rsplit_once('@')
|
||||
.is_some_and(|(_plugin_name, marketplace_name)| {
|
||||
marketplace_name == OPENAI_BUNDLED_MARKETPLACE_NAME
|
||||
})
|
||||
})
|
||||
.expect("allowlist should include a bundled plugin");
|
||||
let (plugin_name, marketplace_name) = plugin_id
|
||||
.rsplit_once('@')
|
||||
.expect("plugin id should include a marketplace");
|
||||
let marketplace_root = codex_home
|
||||
.path()
|
||||
.join(format!(".tmp/marketplaces/{marketplace_name}"));
|
||||
write_file(
|
||||
&marketplace_root.join(".agents/plugins/marketplace.json"),
|
||||
&format!(
|
||||
r#"{{
|
||||
"name": "{marketplace_name}",
|
||||
"plugins": [
|
||||
{{"name": "{plugin_name}", "source": {{"source": "local", "path": "./plugins/{plugin_name}"}}}}
|
||||
]
|
||||
}}
|
||||
"#
|
||||
),
|
||||
);
|
||||
write_curated_plugin(&marketplace_root, plugin_name);
|
||||
write_file(
|
||||
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
||||
&format!(
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[marketplaces.{marketplace_name}]
|
||||
source_type = "git"
|
||||
source = "/tmp/{marketplace_name}"
|
||||
|
||||
[tool_suggest]
|
||||
discoverables = [{{ type = "plugin", id = "{plugin_id}" }}]
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
let config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(discoverable_plugins.len(), 1);
|
||||
assert_eq!(discoverable_plugins[0].id, plugin_id);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_tool_suggest_discoverable_plugins_ignores_missing_allowlisted_plugin() {
|
||||
let codex_home = tempdir().expect("tempdir should succeed");
|
||||
let curated_root = crate::plugins::curated_plugins_repo_path(codex_home.path());
|
||||
write_openai_curated_marketplace(&curated_root, &["slack"]);
|
||||
let marketplace_name = TOOL_SUGGEST_DISCOVERABLE_PLUGIN_ALLOWLIST
|
||||
.iter()
|
||||
.copied()
|
||||
.filter_map(|plugin_id| plugin_id.rsplit_once('@'))
|
||||
.find(|(_plugin_name, marketplace_name)| {
|
||||
*marketplace_name == OPENAI_BUNDLED_MARKETPLACE_NAME
|
||||
})
|
||||
.map(|(_plugin_name, marketplace_name)| marketplace_name)
|
||||
.expect("allowlist should include a bundled plugin");
|
||||
let marketplace_root = codex_home
|
||||
.path()
|
||||
.join(format!(".tmp/marketplaces/{marketplace_name}"));
|
||||
write_file(
|
||||
&marketplace_root.join(".agents/plugins/marketplace.json"),
|
||||
&format!(
|
||||
r#"{{
|
||||
"name": "{marketplace_name}",
|
||||
"plugins": [
|
||||
{{"name": "sample", "source": {{"source": "local", "path": "./plugins/sample"}}}}
|
||||
]
|
||||
}}
|
||||
"#
|
||||
),
|
||||
);
|
||||
write_file(
|
||||
&codex_home.path().join(crate::config::CONFIG_TOML_FILE),
|
||||
&format!(
|
||||
r#"[features]
|
||||
plugins = true
|
||||
|
||||
[marketplaces.{marketplace_name}]
|
||||
source_type = "git"
|
||||
source = "/tmp/{marketplace_name}"
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
let config = load_plugins_config(codex_home.path()).await;
|
||||
let discoverable_plugins = list_tool_suggest_discoverable_plugins(&config)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(discoverable_plugins.len(), 1);
|
||||
assert_eq!(discoverable_plugins[0].id, "slack@openai-curated");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn list_tool_suggest_discoverable_plugins_returns_empty_when_plugins_feature_disabled() {
|
||||
let codex_home = tempdir().expect("tempdir should succeed");
|
||||
|
||||
@@ -75,6 +75,7 @@ use tracing::warn;
|
||||
|
||||
pub const OPENAI_CURATED_MARKETPLACE_NAME: &str = "openai-curated";
|
||||
pub const OPENAI_CURATED_MARKETPLACE_DISPLAY_NAME: &str = "OpenAI Curated";
|
||||
pub const OPENAI_BUNDLED_MARKETPLACE_NAME: &str = "openai-bundled";
|
||||
static CURATED_REPO_SYNC_STARTED: AtomicBool = AtomicBool::new(false);
|
||||
const FEATURED_PLUGIN_IDS_CACHE_TTL: std::time::Duration =
|
||||
std::time::Duration::from_secs(60 * 60 * 3);
|
||||
|
||||
@@ -32,6 +32,7 @@ pub use installed_marketplaces::marketplace_install_root;
|
||||
pub use manager::ConfiguredMarketplace;
|
||||
pub use manager::ConfiguredMarketplaceListOutcome;
|
||||
pub use manager::ConfiguredMarketplacePlugin;
|
||||
pub use manager::OPENAI_BUNDLED_MARKETPLACE_NAME;
|
||||
pub use manager::OPENAI_CURATED_MARKETPLACE_NAME;
|
||||
pub use manager::PluginDetail;
|
||||
pub use manager::PluginInstallError;
|
||||
|
||||
Reference in New Issue
Block a user