mirror of
https://github.com/openai/codex.git
synced 2026-08-23 13:09:46 +00:00
Decouple recommended plugins from tool suggestions (#35839)
## What changed - Add the stable, disabled-by-default `recommended_plugins` feature flag. - Load recommended plugin candidates when apps and plugins are enabled and either `tool_suggest` or `recommended_plugins` is active. - Keep the `request_plugin_install` tool gated by `tool_suggest`. ## Testing - Cover the first turn after external login with `tool_suggest` both enabled and disabled, including the expected install-tool availability. GitOrigin-RevId: 06d9a1c2e8dd2498a47796d833eb6d25ba528351
This commit is contained in:
@@ -27,8 +27,31 @@ use wiremock::matchers::query_param;
|
||||
const DEFAULT_READ_TIMEOUT: Duration = Duration::from_secs(20);
|
||||
const WORKSPACE_ID: &str = "123e4567-e89b-42d3-a456-426614174010";
|
||||
|
||||
enum ToolSuggestFeature {
|
||||
Enabled,
|
||||
Disabled,
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn first_turn_after_external_login_waits_for_recommended_plugins() -> Result<()> {
|
||||
recommended_plugins_after_external_login(ToolSuggestFeature::Enabled).await
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn first_turn_after_external_login_waits_for_recommended_plugins_without_tool_suggest()
|
||||
-> Result<()> {
|
||||
recommended_plugins_after_external_login(ToolSuggestFeature::Disabled).await
|
||||
}
|
||||
|
||||
async fn recommended_plugins_after_external_login(
|
||||
tool_suggest_feature: ToolSuggestFeature,
|
||||
) -> Result<()> {
|
||||
let tool_suggest_enabled = matches!(tool_suggest_feature, ToolSuggestFeature::Enabled);
|
||||
let recommended_plugins_config = if tool_suggest_enabled {
|
||||
""
|
||||
} else {
|
||||
"recommended_plugins = true\n"
|
||||
};
|
||||
let server = responses::start_mock_server().await;
|
||||
let apps_server = AppsTestServer::mount(&server).await?;
|
||||
Mock::given(method("GET"))
|
||||
@@ -68,7 +91,9 @@ async fn first_turn_after_external_login_waits_for_recommended_plugins() -> Resu
|
||||
let config = std::fs::read_to_string(&config_path)?;
|
||||
std::fs::write(
|
||||
config_path,
|
||||
format!("{config}\n[features]\napps = true\nplugins = true\ntool_suggest = true\n"),
|
||||
format!(
|
||||
"{config}\n[features]\napps = true\nplugins = true\ntool_suggest = {tool_suggest_enabled}\n{recommended_plugins_config}"
|
||||
),
|
||||
)?;
|
||||
|
||||
let sqlite_home = codex_home.path().to_string_lossy();
|
||||
@@ -158,7 +183,10 @@ async fn first_turn_after_external_login_waits_for_recommended_plugins() -> Resu
|
||||
.flatten()
|
||||
.filter_map(|tool| tool.get("name").and_then(Value::as_str))
|
||||
.collect::<Vec<_>>();
|
||||
assert!(tool_names.contains(&"request_plugin_install"));
|
||||
assert_eq!(
|
||||
tool_names.contains(&"request_plugin_install"),
|
||||
tool_suggest_enabled
|
||||
);
|
||||
assert!(!tool_names.contains(&"list_available_plugins_to_install"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -633,6 +633,9 @@
|
||||
"realtime_conversation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"recommended_plugins": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remote_compaction_v2": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -5190,6 +5193,9 @@
|
||||
"realtime_conversation": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"recommended_plugins": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"remote_compaction_v2": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -3422,23 +3422,27 @@ impl Session {
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config.plugins_config_input())
|
||||
.await;
|
||||
let recommended_plugin_candidates =
|
||||
if crate::tools::spec_plan::tool_suggest_enabled(turn_context) {
|
||||
let auth = self.services.auth_manager.auth().await;
|
||||
let plugins_config = turn_context.config.plugins_config_input();
|
||||
self.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 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 auth = self.services.auth_manager.auth().await;
|
||||
let plugins_config = turn_context.config.plugins_config_input();
|
||||
self.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
|
||||
};
|
||||
if let Some(recommended_plugins) = recommended_plugin_candidates
|
||||
.as_deref()
|
||||
.and_then(RecommendedPluginsInstructions::from_plugins)
|
||||
|
||||
@@ -176,6 +176,8 @@ pub enum Feature {
|
||||
NonPrefixedMcpToolNames,
|
||||
/// Enable discoverable tool suggestions for apps.
|
||||
ToolSuggest,
|
||||
/// Include recommended plugins in model-visible context.
|
||||
RecommendedPlugins,
|
||||
/// Enable plugins.
|
||||
Plugins,
|
||||
/// Discover selected-root plugin and skill manifests through one high-level exec-server RPC.
|
||||
@@ -1161,6 +1163,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::Stable,
|
||||
default_enabled: true,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::RecommendedPlugins,
|
||||
key: "recommended_plugins",
|
||||
stage: Stage::Stable,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::Plugins,
|
||||
key: "plugins",
|
||||
|
||||
Reference in New Issue
Block a user