mirror of
https://github.com/pchuan98/codex.git
synced 2026-07-01 00:31:56 +08:00
[codex] [3/4] Activate endpoint plugin recommendations (#27704)
Summary\n- Await endpoint recommendation selection while constructing each authenticated turn, removing the first-turn cache race.\n- Snapshot and filter endpoint candidates once per turn, then use that same set for the bounded contextual user fragment, tool exposure, and exact install validation.\n- Keep recommendation selection ephemeral: do not persist recommendation state in or gate resumed threads on prior context.\n- Hide the legacy list tool in endpoint mode and preserve legacy discovery unchanged when the endpoint is disabled or unavailable.\n- Keep remote plugin and connector app identities out of model-visible context and attach them only to Codex-owned elicitation metadata.\n\nStack\n- 3/4, based on #28400.\n- Endpoint client and cache: #28399.\n- Generalized suggestion presentation: #28400.\n- Install-schema follow-up: #28403.\n\nValidation\n- \n- \n- \n- \n- Full : 2,649 passed and 88 environment-dependent tests failed because this sandbox cannot write , nest Seatbelt, or locate auxiliary test binaries.
This commit is contained in:
@@ -10,6 +10,7 @@ use super::InternalModelContextFragment;
|
||||
use super::LegacyApplyPatchExecCommandWarning;
|
||||
use super::LegacyModelMismatchWarning;
|
||||
use super::LegacyUnifiedExecProcessLimitWarning;
|
||||
use super::RecommendedPluginsInstructions;
|
||||
use super::SkillInstructions;
|
||||
use super::SubagentNotification;
|
||||
use super::TurnAborted;
|
||||
@@ -33,6 +34,8 @@ static SUBAGENT_NOTIFICATION_REGISTRATION: FragmentRegistrationProxy<SubagentNot
|
||||
static INTERNAL_MODEL_CONTEXT_REGISTRATION: FragmentRegistrationProxy<
|
||||
InternalModelContextFragment,
|
||||
> = FragmentRegistrationProxy::new();
|
||||
static RECOMMENDED_PLUGINS_REGISTRATION: FragmentRegistrationProxy<RecommendedPluginsInstructions> =
|
||||
FragmentRegistrationProxy::new();
|
||||
static LEGACY_UNIFIED_EXEC_PROCESS_LIMIT_WARNING_REGISTRATION: FragmentRegistrationProxy<
|
||||
LegacyUnifiedExecProcessLimitWarning,
|
||||
> = FragmentRegistrationProxy::new();
|
||||
@@ -52,6 +55,7 @@ static CONTEXTUAL_USER_FRAGMENTS: &[&dyn FragmentRegistration] = &[
|
||||
&TURN_ABORTED_REGISTRATION,
|
||||
&SUBAGENT_NOTIFICATION_REGISTRATION,
|
||||
&INTERNAL_MODEL_CONTEXT_REGISTRATION,
|
||||
&RECOMMENDED_PLUGINS_REGISTRATION,
|
||||
&LEGACY_UNIFIED_EXEC_PROCESS_LIMIT_WARNING_REGISTRATION,
|
||||
&LEGACY_APPLY_PATCH_EXEC_COMMAND_WARNING_REGISTRATION,
|
||||
&LEGACY_MODEL_MISMATCH_WARNING_REGISTRATION,
|
||||
|
||||
@@ -75,6 +75,14 @@ fn detects_internal_model_context_fragment() {
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_recommended_plugins_fragment() {
|
||||
assert!(is_contextual_user_fragment(&ContentItem::InputText {
|
||||
text: "<recommended_plugins>\n- Google Drive (google-drive@openai-curated-remote)\n</recommended_plugins>"
|
||||
.to_string(),
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn detects_legacy_goal_context_fragment() {
|
||||
assert!(is_contextual_user_fragment(&ContentItem::InputText {
|
||||
|
||||
@@ -23,6 +23,7 @@ mod plugin_instructions;
|
||||
mod realtime_end_instructions;
|
||||
mod realtime_start_instructions;
|
||||
mod realtime_start_with_instructions;
|
||||
mod recommended_plugins_instructions;
|
||||
mod subagent_notification;
|
||||
mod token_budget_context;
|
||||
mod turn_aborted;
|
||||
@@ -62,6 +63,7 @@ pub(crate) use plugin_instructions::PluginInstructions;
|
||||
pub(crate) use realtime_end_instructions::RealtimeEndInstructions;
|
||||
pub(crate) use realtime_start_instructions::RealtimeStartInstructions;
|
||||
pub(crate) use realtime_start_with_instructions::RealtimeStartWithInstructions;
|
||||
pub(crate) use recommended_plugins_instructions::RecommendedPluginsInstructions;
|
||||
pub(crate) use subagent_notification::SubagentNotification;
|
||||
pub(crate) use token_budget_context::TokenBudgetContext;
|
||||
pub(crate) use token_budget_context::TokenBudgetRemainingContext;
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
use super::ContextualUserFragment;
|
||||
use codex_tools::DiscoverableTool;
|
||||
|
||||
const RECOMMENDED_PLUGINS_INTRO: &str = "Here is a list of plugins that are available but not installed. If the user's query would benefit from one of these plugins, use the `request_plugin_install` tool to suggest that they install it. All entries have `tool_type: plugin`; pass `plugin` as `tool_type` and the parenthesized ID as `tool_id`. For example, suggest the Google Drive plugin if the query could possibly be better answered with access to Google Drive.";
|
||||
const MAX_RECOMMENDED_PLUGINS: usize = 50;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub(crate) struct RecommendedPluginsInstructions {
|
||||
plugins: Vec<DiscoverableTool>,
|
||||
}
|
||||
|
||||
impl RecommendedPluginsInstructions {
|
||||
pub(crate) fn from_plugins(plugins: &[DiscoverableTool]) -> Option<Self> {
|
||||
if plugins.is_empty() {
|
||||
return None;
|
||||
}
|
||||
Some(Self {
|
||||
plugins: plugins
|
||||
.iter()
|
||||
.take(MAX_RECOMMENDED_PLUGINS)
|
||||
.cloned()
|
||||
.collect(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ContextualUserFragment for RecommendedPluginsInstructions {
|
||||
fn role(&self) -> &'static str {
|
||||
"user"
|
||||
}
|
||||
|
||||
fn markers(&self) -> (&'static str, &'static str) {
|
||||
Self::type_markers()
|
||||
}
|
||||
|
||||
fn type_markers() -> (&'static str, &'static str) {
|
||||
("<recommended_plugins>", "</recommended_plugins>")
|
||||
}
|
||||
|
||||
fn body(&self) -> String {
|
||||
let plugins = self
|
||||
.plugins
|
||||
.iter()
|
||||
.map(|plugin| format!("- {} ({})", plugin.name(), plugin.id()))
|
||||
.collect::<Vec<_>>()
|
||||
.join("\n");
|
||||
format!("\n{RECOMMENDED_PLUGINS_INTRO}\n\n{plugins}\n")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user