From e929bb5c88545c80ec92b4a2d78dd079eb16756d Mon Sep 17 00:00:00 2001 From: Eric Ning Date: Fri, 29 May 2026 17:57:34 -0700 Subject: [PATCH] [codex] Update remote connector suggestions (#25172) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary - Use the session-loaded plugin app IDs as the source of connector suggestion candidates. - Remove the redundant plugin reload from `tool_suggest_connector_ids()`. - Add regression coverage for connectors declared by a loaded remote plugin, using the Databricks app case. ## Context Loaded remote plugins can declare app connector IDs in `.app.json`. The session-owned `PluginsManager` already loads those plugins and exposes their effective app IDs. The connector suggestion path was creating a separate `PluginsManager` and recomputing plugin app IDs. That new manager does not share the session manager’s remote installed plugin cache, so app IDs from loaded remote plugins were missing from connector suggestions. ## Fix Pass the already-loaded effective app IDs into connector suggestion generation and use them directly as the plugin-derived connector candidate set. Connector candidates are now built from: - App IDs declared by loaded plugins - Explicitly configured connector discoverables - Existing disabled-suggestion filtering This avoids a second plugin-manager lookup and keeps connector suggestions aligned with the plugins actually loaded for the turn. ## Behavior For example, when a plugin is loaded and its `.app.json` declares data apps, `list_available_plugins_to_install` can now return those data connectors. This does not create plugin suggestions from the plugin itself. Plugin suggestions still come from eligible uninstalled entries in the marketplace catalog and require existing matching/filtering rules. ## Validation - `just fmt` - Added regression coverage for a loaded-plugin connector ID appearing in discoverable tools - Attempted `just test -p codex-core`; the command exited unsuccessfully in the local test environment without useful failure detail captured in the run output --- codex-rs/core/src/connectors.rs | 16 +++++------ codex-rs/core/src/connectors_tests.rs | 40 +++++++++++++++++++++++++-- 2 files changed, 45 insertions(+), 11 deletions(-) diff --git a/codex-rs/core/src/connectors.rs b/codex-rs/core/src/connectors.rs index 70ccbc83d9..55a941e8c4 100644 --- a/codex-rs/core/src/connectors.rs +++ b/codex-rs/core/src/connectors.rs @@ -115,7 +115,7 @@ pub(crate) async fn list_tool_suggest_discoverable_tools_with_auth( accessible_connectors: &[AppInfo], loaded_plugin_app_connector_ids: &[String], ) -> anyhow::Result> { - let connector_ids = tool_suggest_connector_ids(config).await; + let connector_ids = tool_suggest_connector_ids(config, loaded_plugin_app_connector_ids); let directory_connectors = codex_connectors::merge::merge_plugin_connectors( cached_directory_connectors_for_tool_suggest_with_auth(config, auth).await, connector_ids.iter().cloned(), @@ -406,15 +406,13 @@ fn write_cached_accessible_connectors( }); } -async fn tool_suggest_connector_ids(config: &Config) -> HashSet { - let plugins_input = config.plugins_config_input(); - let mut connector_ids = PluginsManager::new(config.codex_home.to_path_buf()) - .plugins_for_config(&plugins_input) - .await - .capability_summaries() +fn tool_suggest_connector_ids( + config: &Config, + loaded_plugin_app_connector_ids: &[String], +) -> HashSet { + let mut connector_ids = loaded_plugin_app_connector_ids .iter() - .flat_map(|plugin| plugin.app_connector_ids.iter()) - .map(|connector_id| connector_id.0.clone()) + .cloned() .collect::>(); connector_ids.extend( config diff --git a/codex-rs/core/src/connectors_tests.rs b/codex-rs/core/src/connectors_tests.rs index 972743c6eb..285df9a29a 100644 --- a/codex-rs/core/src/connectors_tests.rs +++ b/codex-rs/core/src/connectors_tests.rs @@ -1180,7 +1180,7 @@ discoverables = [ .expect("config should load"); assert_eq!( - tool_suggest_connector_ids(&config).await, + tool_suggest_connector_ids(&config, &[]), HashSet::from(["connector_2128aebfecb84f64a069897515042a44".to_string()]) ); } @@ -1209,7 +1209,7 @@ disabled_tools = [ .expect("config should load"); assert_eq!( - tool_suggest_connector_ids(&config).await, + tool_suggest_connector_ids(&config, &[]), HashSet::from(["connector_gmail".to_string()]) ); } @@ -1249,3 +1249,39 @@ discoverables = [ ))] ); } + +#[tokio::test] +async fn tool_suggest_includes_connectors_from_loaded_plugin_apps() { + let codex_home = tempdir().expect("tempdir should succeed"); + std::fs::write( + codex_home.path().join(CONFIG_TOML_FILE), + r#" +[features] +apps = true +"#, + ) + .expect("write config"); + let config = ConfigBuilder::default() + .codex_home(codex_home.path().to_path_buf()) + .build() + .await + .expect("config should load"); + let auth = CodexAuth::create_dummy_chatgpt_auth_for_testing(); + let loaded_plugin_app_connector_ids = vec!["asdk_app_databricks_workspace".to_string()]; + + let discoverable_tools = list_tool_suggest_discoverable_tools_with_auth( + &config, + Some(&auth), + &[], + &loaded_plugin_app_connector_ids, + ) + .await + .expect("discoverable tools should load"); + + assert_eq!( + discoverable_tools, + vec![DiscoverableTool::from(plugin_connector_to_app_info( + "asdk_app_databricks_workspace".to_string(), + ))] + ); +}