mirror of
https://github.com/openai/codex.git
synced 2026-10-18 17:18:52 +00:00
## Why Selected plugin MCP servers may be skipped after the optional startup grace period. When a user explicitly mentions a plugin or MCP server, the turn should continue waiting so the requested tools are available to the model. ## What changed - Track explicitly mentioned plugin IDs and MCP servers across later samples, steered input, and same-turn restarts. - Require matching selected-plugin MCP servers to finish startup before capturing the tool catalog, while leaving unmentioned servers optional. - Match `plugin://` identities independently of display names and ignore targeting query parameters such as `app` and `browserFamily`. ## Testing Added coverage for linked and structured plugin mentions, direct and batched capability discovery, delayed environment attachment, steering, and same-turn restarts. GitOrigin-RevId: c509ef8df45df1a7edb0da6afdb2682dc53a4de8
122 lines
4.0 KiB
Rust
122 lines
4.0 KiB
Rust
use std::collections::HashMap;
|
|
use std::collections::HashSet;
|
|
|
|
use codex_connectors::metadata::connector_mention_slug;
|
|
use codex_protocol::user_input::UserInput;
|
|
use codex_skills::ToolMentionKind;
|
|
use codex_skills::app_id_from_path;
|
|
use codex_skills::extract_tool_mentions_with_sigil;
|
|
use codex_skills::plugin_config_name_from_path;
|
|
use codex_skills::tool_kind_for_path;
|
|
|
|
use crate::connectors;
|
|
use crate::mention_syntax::PLUGIN_TEXT_MENTION_SIGIL;
|
|
use crate::mention_syntax::TOOL_MENTION_SIGIL;
|
|
|
|
use super::PluginCapabilitySummary;
|
|
|
|
pub(crate) struct CollectedToolMentions {
|
|
pub(crate) plain_names: HashSet<String>,
|
|
pub(crate) paths: HashSet<String>,
|
|
}
|
|
|
|
pub(crate) fn collect_tool_mentions_from_messages(messages: &[String]) -> CollectedToolMentions {
|
|
collect_tool_mentions_from_messages_with_sigil(messages, TOOL_MENTION_SIGIL)
|
|
}
|
|
|
|
fn collect_tool_mentions_from_messages_with_sigil(
|
|
messages: &[String],
|
|
sigil: char,
|
|
) -> CollectedToolMentions {
|
|
let mut plain_names = HashSet::new();
|
|
let mut paths = HashSet::new();
|
|
for message in messages {
|
|
let mentions = extract_tool_mentions_with_sigil(message, sigil);
|
|
plain_names.extend(mentions.plain_names().map(str::to_string));
|
|
paths.extend(mentions.paths().map(str::to_string));
|
|
}
|
|
CollectedToolMentions { plain_names, paths }
|
|
}
|
|
|
|
pub(crate) fn collect_explicit_app_ids(input: &[UserInput]) -> HashSet<String> {
|
|
let messages = input
|
|
.iter()
|
|
.filter_map(|item| match item {
|
|
UserInput::Text { text, .. } => Some(text.clone()),
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<String>>();
|
|
|
|
input
|
|
.iter()
|
|
.filter_map(|item| match item {
|
|
UserInput::Mention { path, .. } => Some(path.clone()),
|
|
_ => None,
|
|
})
|
|
.chain(collect_tool_mentions_from_messages(&messages).paths)
|
|
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::App)
|
|
.filter_map(|path| app_id_from_path(path.as_str()).map(str::to_string))
|
|
.collect()
|
|
}
|
|
|
|
/// Collect explicit structured or linked `plugin://...` mentions.
|
|
pub(crate) fn collect_explicit_plugin_mentions(
|
|
input: &[UserInput],
|
|
plugins: &[PluginCapabilitySummary],
|
|
) -> Vec<PluginCapabilitySummary> {
|
|
if plugins.is_empty() {
|
|
return Vec::new();
|
|
}
|
|
|
|
// `config_name` stores the full plugin ID, not its display or mention name.
|
|
let mentioned_plugin_ids = collect_explicit_plugin_ids(input);
|
|
plugins
|
|
.iter()
|
|
.filter(|plugin| mentioned_plugin_ids.contains(plugin.config_name.as_str()))
|
|
.cloned()
|
|
.collect()
|
|
}
|
|
|
|
/// Collect exact IDs from explicit `plugin://` references, independently of display names.
|
|
///
|
|
/// Host plugins use `<plugin>@<marketplace>` IDs; selected roots may supply opaque IDs.
|
|
pub(crate) fn collect_explicit_plugin_ids(input: &[UserInput]) -> HashSet<String> {
|
|
let messages = input
|
|
.iter()
|
|
.filter_map(|item| match item {
|
|
UserInput::Text { text, .. } => Some(text.clone()),
|
|
_ => None,
|
|
})
|
|
.collect::<Vec<String>>();
|
|
|
|
input
|
|
.iter()
|
|
.filter_map(|item| match item {
|
|
UserInput::Mention { path, .. } => Some(path.clone()),
|
|
_ => None,
|
|
})
|
|
.chain(
|
|
// Plugin plaintext links use `@`, not the default `$` tool sigil.
|
|
collect_tool_mentions_from_messages_with_sigil(&messages, PLUGIN_TEXT_MENTION_SIGIL)
|
|
.paths,
|
|
)
|
|
.filter(|path| tool_kind_for_path(path.as_str()) == ToolMentionKind::Plugin)
|
|
.filter_map(|path| plugin_config_name_from_path(path.as_str()).map(str::to_string))
|
|
.collect()
|
|
}
|
|
|
|
pub(crate) fn build_connector_slug_counts(
|
|
connectors: &[connectors::AppInfo],
|
|
) -> HashMap<String, usize> {
|
|
let mut counts: HashMap<String, usize> = HashMap::new();
|
|
for connector in connectors {
|
|
let slug = connector_mention_slug(connector);
|
|
*counts.entry(slug).or_insert(0) += 1;
|
|
}
|
|
counts
|
|
}
|
|
|
|
#[cfg(test)]
|
|
#[path = "mentions_tests.rs"]
|
|
mod tests;
|