Prepare MCP and plugin recommendations concurrently (#35675)

## Why

Turn preparation waited for MCP discovery before requesting endpoint plugin
recommendations, adding their latencies together.

## What changed

- Prepare the MCP runtime and endpoint plugin recommendations concurrently.
- Wait for both results before building tools and starting model sampling.
- Cancel the combined preparation when the turn is interrupted.

## Testing

- Add coverage that gates MCP initialization and verifies recommendation fetching
  overlaps it while the final request includes both results.
- Verify interrupting concurrent preparation prevents model sampling.

GitOrigin-RevId: 295ec268331bf05304e6b313925fd2b6c2ae4190
This commit is contained in:
Charlie Marsh
2026-07-27 21:43:27 +00:00
committed by copyberry
parent 294d813263
commit bbeee9a406
3 changed files with 281 additions and 30 deletions
+9 -4
View File
@@ -3044,16 +3044,21 @@ impl Session {
.await;
let extension_data = codex_extension_api::ExtensionData::new(turn_context.sub_id.clone());
extension_data.insert(selected_capability_roots.clone());
let mcp = self
.mcp_runtime_for_step(turn_context.as_ref(), &selected_capability_roots)
.or_cancel(cancellation_token)
.await?;
let (mcp, prepared_recommendations) = async {
tokio::join!(
self.mcp_runtime_for_step(turn_context.as_ref(), &selected_capability_roots),
turn::prepare_tool_recommendations(self.as_ref(), turn_context.as_ref()),
)
}
.or_cancel(cancellation_token)
.await?;
let (mcp_tools, tool_router) = turn::built_tools(
self.as_ref(),
turn_context.as_ref(),
&environments,
mcp.as_ref(),
&extension_data,
prepared_recommendations,
)
.or_cancel(cancellation_token)
.await?;
+51 -26
View File
@@ -82,6 +82,7 @@ use codex_extension_api::TurnInputEnvironment;
use codex_features::Feature;
use codex_file_system::FindUpErrorPolicy;
use codex_file_system::find_nearest_ancestor_with_markers;
use codex_login::CodexAuth;
use codex_mcp::ToolInfo;
use codex_protocol::ResponseItemId;
use codex_protocol::config_types::AutoCompactTokenLimitScope;
@@ -111,6 +112,7 @@ use codex_protocol::protocol::SafetyBufferingEvent;
use codex_protocol::protocol::TurnDiffEvent;
use codex_protocol::protocol::WarningEvent;
use codex_protocol::user_input::UserInput;
use codex_tools::DiscoverableTool;
use codex_tools::ToolName;
use codex_tools::filter_request_plugin_install_discoverable_tools_for_client;
use codex_utils_path_uri::PathUri;
@@ -1270,6 +1272,50 @@ async fn run_sampling_request(
}
}
pub(crate) struct PreparedToolRecommendations {
auth: Option<CodexAuth>,
endpoint_candidates: Option<Vec<DiscoverableTool>>,
}
#[instrument(level = "trace", skip_all)]
pub(crate) async fn prepare_tool_recommendations(
sess: &Session,
turn_context: &TurnContext,
) -> PreparedToolRecommendations {
let loaded_plugins = sess
.services
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.instrument(trace_span!("built_tools.load_plugins"))
.await;
let tool_suggest_is_enabled = tool_suggest_enabled(turn_context);
let auth = if tool_suggest_is_enabled {
sess.services.auth_manager.auth().await
} else {
None
};
let endpoint_candidates = if tool_suggest_is_enabled {
let plugins_config = turn_context.config.plugins_config_input();
sess.services
.plugins_manager
.recommended_plugin_candidates_for_config(RecommendedPluginCandidatesInput {
plugins_config: &plugins_config,
loaded_plugins: &loaded_plugins,
auth: auth.as_ref(),
disabled_tools: &turn_context.config.tool_suggest.disabled_tools,
app_server_client_name: turn_context.app_server_client_name.as_deref(),
})
.await
} else {
None
};
PreparedToolRecommendations {
auth,
endpoint_candidates,
}
}
#[instrument(level = "trace",
skip_all,
fields(
@@ -1284,14 +1330,9 @@ pub(crate) async fn built_tools(
environments: &TurnEnvironmentSnapshot,
mcp: &codex_mcp::McpBinding,
step_store: &ExtensionData,
prepared_recommendations: PreparedToolRecommendations,
) -> (Vec<ToolInfo>, Arc<ToolRouter>) {
let all_mcp_tools = mcp.tools().to_vec();
let loaded_plugins = sess
.services
.plugins_manager
.plugins_for_config(&turn_context.config.plugins_config_input())
.instrument(trace_span!("built_tools.load_plugins"))
.await;
let connector_snapshot = mcp.config().connector_snapshot.clone();
let apps_enabled = turn_context.apps_enabled();
@@ -1317,26 +1358,10 @@ pub(crate) async fn built_tools(
None
};
let tool_suggest_is_enabled = tool_suggest_enabled(turn_context);
let auth = if tool_suggest_is_enabled {
sess.services.auth_manager.auth().await
} else {
None
};
let endpoint_recommended_plugin_candidates = if tool_suggest_is_enabled {
let plugins_config = turn_context.config.plugins_config_input();
sess.services
.plugins_manager
.recommended_plugin_candidates_for_config(RecommendedPluginCandidatesInput {
plugins_config: &plugins_config,
loaded_plugins: &loaded_plugins,
auth: auth.as_ref(),
disabled_tools: &turn_context.config.tool_suggest.disabled_tools,
app_server_client_name: turn_context.app_server_client_name.as_deref(),
})
.await
} else {
None
};
let PreparedToolRecommendations {
auth,
endpoint_candidates: endpoint_recommended_plugin_candidates,
} = prepared_recommendations;
let tool_suggest_candidates =
if let Some(recommended_plugin_candidates) = endpoint_recommended_plugin_candidates {
Some(ToolSuggestCandidates {