mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Preload plugin recommendations during session startup (#41375)
## Why Fetching plugin recommendations while building the first-turn context delays that context when the recommendation catalog is not already cached. ## What changed - Start fetching recommendations during session initialization, in parallel with MCP and plugin setup, and reuse the in-flight request when building context. - Centralize the feature checks that enable plugin recommendations. - Prevent an invalidated recommendation request from repopulating the cache after a newer request has completed. ## Testing Added coverage for startup feature gating, reuse of the in-flight fetch, legacy behavior when recommendations are unavailable, and cache invalidation races. GitOrigin-RevId: 708774b9bfc472c9847973ae8b2774b66e7321e4
This commit is contained in:
1 parent
5ed294d49d
commit
8bcac28f93
7 files changed
+391
-14
No files matched your search
@@ -3731,11 +3731,10 @@ impl Session {
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config.plugins_config_input())
|
||||
.await;
|
||||
let features = turn_context.config.features.get();
|
||||
let recommended_plugin_candidates = if features.enabled(Feature::Apps)
|
||||
&& features.enabled(Feature::Plugins)
|
||||
&& (features.enabled(Feature::ToolSuggest)
|
||||
|| features.enabled(Feature::RecommendedPlugins))
|
||||
let recommended_plugin_candidates = if turn_context
|
||||
.config
|
||||
.features
|
||||
.plugin_recommendations_enabled()
|
||||
{
|
||||
let auth = self.services.auth_manager.auth().await;
|
||||
let plugins_config = turn_context.config.plugins_config_input();
|
||||
|
||||
@@ -928,6 +928,7 @@ impl Session {
|
||||
|
||||
let mut mcp_auth_changes = auth_manager.auth_change_receiver();
|
||||
let auth_manager_clone = Arc::clone(&auth_manager);
|
||||
let plugins_manager_for_prewarm = Arc::clone(&plugins_manager);
|
||||
let config_for_mcp = Arc::clone(&config);
|
||||
let mcp_manager_for_mcp = Arc::clone(&mcp_manager);
|
||||
let mcp_thread_init_for_startup = &mcp_thread_init;
|
||||
@@ -941,6 +942,20 @@ impl Session {
|
||||
.unwrap_or_else(|| session_configuration.cwd().to_path_buf());
|
||||
let auth_and_mcp_fut = async move {
|
||||
let auth = auth_manager_clone.auth().await;
|
||||
if config_for_mcp.features.plugin_recommendations_enabled() {
|
||||
let plugins_config = config_for_mcp.plugins_config_input();
|
||||
let auth_for_prewarm = auth.clone();
|
||||
// Fetch the catalog while MCP and plugin/skill initialization continue.
|
||||
// Context construction still handles filtering and prompt insertion.
|
||||
tokio::spawn(async move {
|
||||
plugins_manager_for_prewarm
|
||||
.recommended_plugins_mode_for_config(
|
||||
&plugins_config,
|
||||
auth_for_prewarm.as_ref(),
|
||||
)
|
||||
.await;
|
||||
});
|
||||
}
|
||||
let mcp_projection = mcp_manager_for_mcp
|
||||
.runtime_config_for_step(
|
||||
&config_for_mcp,
|
||||
|
||||
@@ -9726,6 +9726,86 @@ async fn record_context_updates_emits_realtime_end_when_session_stops_being_live
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_reuses_in_flight_recommendation_prewarm() {
|
||||
use wiremock::Mock;
|
||||
use wiremock::ResponseTemplate;
|
||||
use wiremock::matchers::method;
|
||||
use wiremock::matchers::path;
|
||||
use wiremock::matchers::query_param;
|
||||
|
||||
core_test_support::skip_if_no_network!();
|
||||
|
||||
let server = start_mock_server().await;
|
||||
Mock::given(method("GET"))
|
||||
.and(path("/ps/plugins/suggested/codex"))
|
||||
.and(query_param("scope", "GLOBAL"))
|
||||
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
|
||||
"enabled": true,
|
||||
"plugins": [{
|
||||
"id": "plugin_github",
|
||||
"name": "github",
|
||||
"display_name": "GitHub"
|
||||
}]
|
||||
})))
|
||||
.expect(1)
|
||||
.mount(&server)
|
||||
.await;
|
||||
let (session, turn_context, _rx_event) = make_session_and_context_with_auth_and_config_and_rx(
|
||||
CodexAuth::create_dummy_chatgpt_auth_for_testing(),
|
||||
/*dynamic_tools*/ Vec::new(),
|
||||
|config| {
|
||||
config.chatgpt_base_url = server.uri();
|
||||
config
|
||||
.features
|
||||
.disable(Feature::ToolSuggest)
|
||||
.expect("test config should allow feature update");
|
||||
for enabled_feature in [
|
||||
Feature::Apps,
|
||||
Feature::Plugins,
|
||||
Feature::RemotePlugin,
|
||||
Feature::RecommendedPlugins,
|
||||
] {
|
||||
config
|
||||
.features
|
||||
.enable(enabled_feature)
|
||||
.expect("test config should allow feature update");
|
||||
}
|
||||
},
|
||||
)
|
||||
.await;
|
||||
let plugins_manager = &session.services.plugins_manager;
|
||||
let plugins_config = turn_context.config.plugins_config_input();
|
||||
let auth = session.services.auth_manager.auth().await;
|
||||
// Cached plugin loading and auth let initial context reach the shared recommendation lookup
|
||||
// without awaiting unrelated I/O.
|
||||
plugins_manager.plugins_for_config(&plugins_config).await;
|
||||
let prewarm =
|
||||
plugins_manager.recommended_plugins_mode_for_config(&plugins_config, auth.as_ref());
|
||||
tokio::pin!(prewarm);
|
||||
assert!(futures::poll!(prewarm.as_mut()).is_pending());
|
||||
|
||||
// Keep the OnceCell initializer unpolled while first-thread context joins its in-flight fetch.
|
||||
// This does not depend on how quickly the HTTP server returns its response.
|
||||
let world_state = WorldState::default();
|
||||
let initial_context =
|
||||
session.build_initial_context_with_world_state(&turn_context, &world_state);
|
||||
tokio::pin!(initial_context);
|
||||
assert!(futures::poll!(initial_context.as_mut()).is_pending());
|
||||
|
||||
let (_, initial_context) = tokio::join!(prewarm, initial_context);
|
||||
assert_eq!(
|
||||
user_input_texts(&initial_context),
|
||||
vec![concat!(
|
||||
"<recommended_plugins>\n",
|
||||
"Here is a list of plugins that are available but not installed.\n\n",
|
||||
"- GitHub (github@openai-curated-remote)\n",
|
||||
"</recommended_plugins>",
|
||||
)]
|
||||
);
|
||||
server.verify().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn build_initial_context_describes_active_realtime_state() {
|
||||
let (session, mut turn_context) = make_session_and_context().await;
|
||||
|
||||
Reference in new issue
Block a user