Match plugin suggestions against resolved connector candidates

This commit is contained in:
Noah MacCallum
2026-05-29 15:32:11 -07:00
parent 4cd3ab4a3d
commit c243abfc81
4 changed files with 90 additions and 14 deletions
+11 -6
View File
@@ -22,7 +22,7 @@ use tracing::warn;
use crate::config::Config;
use crate::mcp::McpManager;
use crate::plugins::list_tool_suggest_discoverable_plugins;
use crate::plugins::list_tool_suggest_discoverable_plugins_with_connector_candidates;
use crate::session::INITIAL_SUBMIT_ID;
use codex_config::AppsRequirementsToml;
use codex_config::types::AppToolApproval;
@@ -148,11 +148,16 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth(
)
.into_iter()
.map(DiscoverableTool::from);
let discoverable_plugins =
list_tool_suggest_discoverable_plugins(config, auth, loaded_plugin_app_connector_ids)
.await?
.into_iter()
.map(DiscoverableTool::from);
let candidate_app_connector_ids = connector_ids.iter().cloned().collect::<Vec<_>>();
let discoverable_plugins = list_tool_suggest_discoverable_plugins_with_connector_candidates(
config,
auth,
loaded_plugin_app_connector_ids,
&candidate_app_connector_ids,
)
.await?
.into_iter()
.map(DiscoverableTool::from);
Ok(discoverable_connectors
.chain(discoverable_plugins)
.collect())
+22
View File
@@ -21,10 +21,26 @@ const TOOL_SUGGEST_DISCOVERABLE_MARKETPLACE_ALLOWLIST: &[&str] = &[
OPENAI_CURATED_MARKETPLACE_NAME,
];
#[cfg(test)]
pub(crate) async fn list_tool_suggest_discoverable_plugins(
config: &Config,
auth: Option<&CodexAuth>,
loaded_plugin_app_connector_ids: &[String],
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
list_tool_suggest_discoverable_plugins_with_connector_candidates(
config,
auth,
loaded_plugin_app_connector_ids,
&[],
)
.await
}
pub(crate) async fn list_tool_suggest_discoverable_plugins_with_connector_candidates(
config: &Config,
auth: Option<&CodexAuth>,
loaded_plugin_app_connector_ids: &[String],
candidate_app_connector_ids: &[String],
) -> anyhow::Result<Vec<DiscoverablePluginInfo>> {
if !config.features.enabled(Feature::Plugins) {
return Ok(Vec::new());
@@ -63,6 +79,12 @@ pub(crate) async fn list_tool_suggest_discoverable_plugins(
.cloned()
.map(AppConnectorId),
)
.chain(
candidate_app_connector_ids
.iter()
.cloned()
.map(AppConnectorId),
)
.collect::<Vec<_>>();
let remote_plugin_service_config = RemotePluginServiceConfig {
chatgpt_base_url: config.chatgpt_base_url.clone(),
@@ -102,15 +102,12 @@ async fn list_tool_suggest_discoverable_plugins_matches_on_resolved_template_app
let databricks_app_id = "asdk_app_databricks_workspace";
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path(
"/backend-api/ps/connectors/by_template_id/templated_apps_Databricks",
))
.and(path("/backend-api/connectors/directory/list_workspace"))
.and(header("authorization", "Bearer Access Token"))
.and(header("chatgpt-account-id", "account_id"))
.respond_with(
ResponseTemplate::new(200)
.set_body_string(format!(r#"{{"connector_ids":["{databricks_app_id}"]}}"#)),
)
.respond_with(ResponseTemplate::new(200).set_body_string(format!(
r#"{{"apps":[{{"id":"{databricks_app_id}","template_id":"templated_apps_Databricks"}}]}}"#
)))
.mount(&server)
.await;
@@ -151,6 +148,58 @@ async fn list_tool_suggest_discoverable_plugins_matches_on_resolved_template_app
);
}
#[tokio::test]
async fn list_tool_suggest_discoverable_plugins_matches_on_resolved_connector_candidate_apps() {
let databricks_app_id = "asdk_app_databricks_workspace";
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/backend-api/connectors/directory/list_workspace"))
.and(header("authorization", "Bearer Access Token"))
.and(header("chatgpt-account-id", "account_id"))
.respond_with(ResponseTemplate::new(200).set_body_string(format!(
r#"{{"apps":[{{"id":"{databricks_app_id}","template_id":"templated_apps_Databricks"}}]}}"#
)))
.mount(&server)
.await;
let codex_home = tempdir().expect("tempdir should succeed");
let curated_root = curated_plugins_repo_path(codex_home.path());
write_openai_curated_marketplace(&curated_root, &["databricks-source"]);
write_plugin_app(
&curated_root,
"databricks-source",
"databricks",
"templated_apps_Databricks",
);
write_plugins_feature_config(codex_home.path());
let mut config = load_plugins_config(codex_home.path()).await;
config.chatgpt_base_url = format!("{}/backend-api", server.uri());
let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing();
let discoverable_plugins = list_tool_suggest_discoverable_plugins_with_connector_candidates(
&config,
Some(&auth),
&[],
&[databricks_app_id.to_string()],
)
.await
.unwrap();
assert_eq!(
discoverable_plugins,
vec![DiscoverablePluginInfo {
id: "databricks-source@openai-curated".to_string(),
name: "databricks-source".to_string(),
description: Some(
"Plugin that includes skills, MCP servers, and app connectors".to_string(),
),
has_skills: true,
mcp_server_names: vec!["sample-docs".to_string()],
app_connector_ids: vec![databricks_app_id.to_string()],
}]
);
}
#[tokio::test]
async fn list_tool_suggest_discoverable_plugins_filters_microsoft_by_installed_apps() {
let codex_home = tempdir().expect("tempdir should succeed");
+1 -1
View File
@@ -7,7 +7,7 @@ pub(crate) mod test_support;
pub(crate) use codex_plugin::PluginCapabilitySummary;
pub(crate) use discoverable::list_tool_suggest_discoverable_plugins;
pub(crate) use discoverable::list_tool_suggest_discoverable_plugins_with_connector_candidates;
pub(crate) use injection::build_plugin_injections;
pub(crate) use render::render_explicit_plugin_instructions;