mirror of
https://github.com/openai/codex.git
synced 2026-11-05 20:10:33 +00:00
update tool descs with plugins at tool-build time
This commit is contained in:
@@ -206,7 +206,6 @@ use crate::mentions::collect_tool_mentions_from_messages;
|
||||
use crate::network_policy_decision::execpolicy_network_rule_amendment;
|
||||
use crate::plugins::PluginCapabilitySummary;
|
||||
use crate::plugins::PluginsManager;
|
||||
use crate::plugins::annotate_tools_with_plugin_sources;
|
||||
use crate::plugins::render_explicit_plugin_instructions;
|
||||
use crate::project_doc::get_user_instructions;
|
||||
use crate::protocol::AgentMessageContentDeltaEvent;
|
||||
@@ -1528,7 +1527,7 @@ impl Session {
|
||||
tool_approvals: Mutex::new(ApprovalStore::default()),
|
||||
execve_session_approvals: RwLock::new(HashMap::new()),
|
||||
skills_manager,
|
||||
plugins_manager,
|
||||
plugins_manager: Arc::clone(&plugins_manager),
|
||||
mcp_manager,
|
||||
file_watcher,
|
||||
agent_control,
|
||||
@@ -1613,6 +1612,10 @@ impl Session {
|
||||
.map(|(name, _)| name.clone())
|
||||
.collect();
|
||||
required_mcp_servers.sort();
|
||||
let plugin_capability_index = plugins_manager
|
||||
.plugins_for_config(config.as_ref())
|
||||
.capability_index()
|
||||
.clone();
|
||||
{
|
||||
let mut cancel_guard = sess.services.mcp_startup_cancellation_token.lock().await;
|
||||
cancel_guard.cancel();
|
||||
@@ -1627,6 +1630,7 @@ impl Session {
|
||||
sandbox_state,
|
||||
config.codex_home.clone(),
|
||||
codex_apps_tools_cache_key(auth),
|
||||
plugin_capability_index,
|
||||
)
|
||||
.await;
|
||||
{
|
||||
@@ -3572,6 +3576,12 @@ impl Session {
|
||||
) {
|
||||
let auth = self.services.auth_manager.auth().await;
|
||||
let config = self.get_config().await;
|
||||
let plugin_capability_index = self
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(config.as_ref())
|
||||
.capability_index()
|
||||
.clone();
|
||||
let mcp_servers = with_codex_apps_mcp(
|
||||
mcp_servers,
|
||||
self.features.enabled(Feature::Apps),
|
||||
@@ -3599,6 +3609,7 @@ impl Session {
|
||||
sandbox_state,
|
||||
config.codex_home.clone(),
|
||||
codex_apps_tools_cache_key(auth.as_ref()),
|
||||
plugin_capability_index,
|
||||
)
|
||||
.await;
|
||||
{
|
||||
@@ -5699,10 +5710,6 @@ async fn built_tools(
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn_context.config);
|
||||
// Annotate tool descriptions at runtime so prompt-visible tools and
|
||||
// search_tool_bm25 share the same plugin provenance without mutating the
|
||||
// cached MCP tool inventory.
|
||||
mcp_tools = annotate_tools_with_plugin_sources(mcp_tools, loaded_plugins.capability_index());
|
||||
|
||||
let mut effective_explicitly_enabled_connectors = explicitly_enabled_connectors.clone();
|
||||
effective_explicitly_enabled_connectors.extend(sess.get_connector_selection().await);
|
||||
|
||||
@@ -31,6 +31,7 @@ use crate::mcp::with_codex_apps_mcp;
|
||||
use crate::mcp_connection_manager::McpConnectionManager;
|
||||
use crate::mcp_connection_manager::codex_apps_tools_cache_key;
|
||||
use crate::plugins::AppConnectorId;
|
||||
use crate::plugins::PluginCapabilityIndex;
|
||||
use crate::token_data::TokenData;
|
||||
|
||||
pub const CONNECTORS_CACHE_TTL: Duration = Duration::from_secs(3600);
|
||||
@@ -162,6 +163,7 @@ pub async fn list_accessible_connectors_from_mcp_tools_with_options_and_status(
|
||||
sandbox_state,
|
||||
config.codex_home.clone(),
|
||||
codex_apps_tools_cache_key(auth.as_ref()),
|
||||
PluginCapabilityIndex::default(),
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -217,7 +217,12 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
|
||||
config.cli_auth_credentials_store_mode,
|
||||
);
|
||||
let auth = auth_manager.auth().await;
|
||||
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(config.codex_home.clone())));
|
||||
let plugins_manager = Arc::new(PluginsManager::new(config.codex_home.clone()));
|
||||
let plugin_capability_index = plugins_manager
|
||||
.plugins_for_config(config)
|
||||
.capability_index()
|
||||
.clone();
|
||||
let mcp_manager = McpManager::new(plugins_manager);
|
||||
let mcp_servers = mcp_manager.effective_servers(config, auth.as_ref());
|
||||
if mcp_servers.is_empty() {
|
||||
return McpListToolsResponseEvent {
|
||||
@@ -251,6 +256,7 @@ pub async fn collect_mcp_snapshot(config: &Config) -> McpListToolsResponseEvent
|
||||
sandbox_state,
|
||||
config.codex_home.clone(),
|
||||
codex_apps_tools_cache_key(auth.as_ref()),
|
||||
plugin_capability_index,
|
||||
)
|
||||
.await;
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
//! in a single aggregated map using the fully-qualified tool name
|
||||
//! `"<server><MCP_TOOL_NAME_DELIMITER><tool>"` as the key.
|
||||
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::collections::HashSet;
|
||||
use std::env;
|
||||
@@ -79,6 +80,8 @@ use crate::codex::INITIAL_SUBMIT_ID;
|
||||
use crate::config::types::McpServerConfig;
|
||||
use crate::config::types::McpServerTransportConfig;
|
||||
use crate::connectors::is_connector_id_allowed;
|
||||
use crate::plugins::AppConnectorId;
|
||||
use crate::plugins::PluginCapabilityIndex;
|
||||
|
||||
/// Delimiter used to separate the server name from the tool name in a fully
|
||||
/// qualified tool name.
|
||||
@@ -341,6 +344,7 @@ struct ManagedClient {
|
||||
tool_timeout: Option<Duration>,
|
||||
server_supports_sandbox_state_capability: bool,
|
||||
codex_apps_tools_cache_context: Option<CodexAppsToolsCacheContext>,
|
||||
plugin_capability_index: Arc<PluginCapabilityIndex>,
|
||||
}
|
||||
|
||||
impl ManagedClient {
|
||||
@@ -350,12 +354,17 @@ impl ManagedClient {
|
||||
&& let CachedCodexAppsToolsLoad::Hit(tools) =
|
||||
load_cached_codex_apps_tools(cache_context)
|
||||
{
|
||||
// Keep the disk cache raw because plugin provenance is session/config-scoped,
|
||||
// while the codex_apps cache is only keyed per user account.
|
||||
emit_duration(
|
||||
MCP_TOOLS_LIST_DURATION_METRIC,
|
||||
total_start.elapsed(),
|
||||
&[("cache", "hit")],
|
||||
);
|
||||
return filter_tools(tools, &self.tool_filter);
|
||||
return annotate_tools_with_plugin_sources(
|
||||
filter_tools(tools, &self.tool_filter),
|
||||
self.plugin_capability_index.as_ref(),
|
||||
);
|
||||
}
|
||||
|
||||
if self.codex_apps_tools_cache_context.is_some() {
|
||||
@@ -394,6 +403,9 @@ struct AsyncManagedClient {
|
||||
}
|
||||
|
||||
impl AsyncManagedClient {
|
||||
// Keep this constructor flat so the startup inputs remain readable at the
|
||||
// single call site instead of introducing a one-off params wrapper.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn new(
|
||||
server_name: String,
|
||||
config: McpServerConfig,
|
||||
@@ -402,16 +414,23 @@ impl AsyncManagedClient {
|
||||
tx_event: Sender<Event>,
|
||||
elicitation_requests: ElicitationRequestManager,
|
||||
codex_apps_tools_cache_context: Option<CodexAppsToolsCacheContext>,
|
||||
plugin_capability_index: Arc<PluginCapabilityIndex>,
|
||||
) -> Self {
|
||||
let tool_filter = ToolFilter::from_config(&config);
|
||||
let startup_snapshot = load_startup_cached_codex_apps_tools_snapshot(
|
||||
&server_name,
|
||||
codex_apps_tools_cache_context.as_ref(),
|
||||
)
|
||||
.map(|tools| filter_tools(tools, &tool_filter));
|
||||
.map(|tools| {
|
||||
annotate_tools_with_plugin_sources(
|
||||
filter_tools(tools, &tool_filter),
|
||||
plugin_capability_index.as_ref(),
|
||||
)
|
||||
});
|
||||
let startup_tool_filter = tool_filter;
|
||||
let startup_complete = Arc::new(AtomicBool::new(false));
|
||||
let startup_complete_for_fut = Arc::clone(&startup_complete);
|
||||
let startup_plugin_capability_index = Arc::clone(&plugin_capability_index);
|
||||
let fut = async move {
|
||||
let outcome = async {
|
||||
if let Err(error) = validate_mcp_server_name(&server_name) {
|
||||
@@ -432,6 +451,7 @@ impl AsyncManagedClient {
|
||||
tx_event,
|
||||
elicitation_requests,
|
||||
codex_apps_tools_cache_context,
|
||||
plugin_capability_index: startup_plugin_capability_index,
|
||||
},
|
||||
)
|
||||
.or_cancel(&cancel_token)
|
||||
@@ -552,12 +572,14 @@ impl McpConnectionManager {
|
||||
initial_sandbox_state: SandboxState,
|
||||
codex_home: PathBuf,
|
||||
codex_apps_tools_cache_key: CodexAppsToolsCacheKey,
|
||||
plugin_capability_index: PluginCapabilityIndex,
|
||||
) -> (Self, CancellationToken) {
|
||||
let cancel_token = CancellationToken::new();
|
||||
let mut clients = HashMap::new();
|
||||
let mut server_origins = HashMap::new();
|
||||
let mut join_set = JoinSet::new();
|
||||
let elicitation_requests = ElicitationRequestManager::new(approval_policy.value());
|
||||
let plugin_capability_index = Arc::new(plugin_capability_index);
|
||||
let mcp_servers = mcp_servers.clone();
|
||||
for (server_name, cfg) in mcp_servers.into_iter().filter(|(_, cfg)| cfg.enabled) {
|
||||
if let Some(origin) = transport_origin(&cfg.transport) {
|
||||
@@ -588,6 +610,7 @@ impl McpConnectionManager {
|
||||
tx_event.clone(),
|
||||
elicitation_requests.clone(),
|
||||
codex_apps_tools_cache_context,
|
||||
Arc::clone(&plugin_capability_index),
|
||||
);
|
||||
clients.insert(server_name.clone(), async_managed_client.clone());
|
||||
let tx_event = tx_event.clone();
|
||||
@@ -1135,6 +1158,63 @@ pub(crate) fn filter_mcp_tools_by_name(
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn annotate_tools_with_plugin_sources(
|
||||
mut tools: Vec<ToolInfo>,
|
||||
capability_index: &PluginCapabilityIndex,
|
||||
) -> Vec<ToolInfo> {
|
||||
for tool in &mut tools {
|
||||
let mut plugin_names = match tool.connector_id.as_ref() {
|
||||
Some(connector_id) => capability_index
|
||||
.plugins_for_connector_id(&AppConnectorId(connector_id.clone()))
|
||||
.into_iter()
|
||||
.map(|plugin| plugin.display_name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
None => capability_index
|
||||
.plugins_for_mcp_server_name(tool.server_name.as_str())
|
||||
.into_iter()
|
||||
.map(|plugin| plugin.display_name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
if plugin_names.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
plugin_names.sort_unstable();
|
||||
plugin_names.dedup();
|
||||
|
||||
let plugin_source_note = if plugin_names.len() == 1 {
|
||||
format!("This tool is part of plugin `{}`.", plugin_names[0])
|
||||
} else {
|
||||
format!(
|
||||
"This tool is part of plugins {}.",
|
||||
plugin_names
|
||||
.into_iter()
|
||||
.map(|plugin_name| format!("`{plugin_name}`"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
};
|
||||
|
||||
let description = tool
|
||||
.tool
|
||||
.description
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.unwrap_or("");
|
||||
let annotated_description = if description.is_empty() {
|
||||
plugin_source_note
|
||||
} else if matches!(description.chars().last(), Some('.' | '!' | '?')) {
|
||||
format!("{description} {plugin_source_note}")
|
||||
} else {
|
||||
format!("{description}. {plugin_source_note}")
|
||||
};
|
||||
tool.tool.description = Some(Cow::Owned(annotated_description));
|
||||
}
|
||||
|
||||
tools
|
||||
}
|
||||
|
||||
fn normalize_codex_apps_tool_title(
|
||||
server_name: &str,
|
||||
connector_name: Option<&str>,
|
||||
@@ -1233,6 +1313,7 @@ async fn start_server_task(
|
||||
tx_event,
|
||||
elicitation_requests,
|
||||
codex_apps_tools_cache_context,
|
||||
plugin_capability_index,
|
||||
} = params;
|
||||
let elicitation = elicitation_capability_for_server(&server_name);
|
||||
let params = InitializeRequestParams {
|
||||
@@ -1285,7 +1366,10 @@ async fn start_server_task(
|
||||
&[("cache", "miss")],
|
||||
);
|
||||
}
|
||||
let tools = filter_tools(tools, &tool_filter);
|
||||
let tools = annotate_tools_with_plugin_sources(
|
||||
filter_tools(tools, &tool_filter),
|
||||
plugin_capability_index.as_ref(),
|
||||
);
|
||||
|
||||
let server_supports_sandbox_state_capability = initialize_result
|
||||
.capabilities
|
||||
@@ -1301,6 +1385,7 @@ async fn start_server_task(
|
||||
tool_filter,
|
||||
server_supports_sandbox_state_capability,
|
||||
codex_apps_tools_cache_context,
|
||||
plugin_capability_index,
|
||||
};
|
||||
|
||||
Ok(managed)
|
||||
@@ -1313,6 +1398,7 @@ struct StartServerTaskParams {
|
||||
tx_event: Sender<Event>,
|
||||
elicitation_requests: ElicitationRequestManager,
|
||||
codex_apps_tools_cache_context: Option<CodexAppsToolsCacheContext>,
|
||||
plugin_capability_index: Arc<PluginCapabilityIndex>,
|
||||
}
|
||||
|
||||
async fn make_rmcp_client(
|
||||
|
||||
@@ -17,7 +17,6 @@ use crate::config_loader::ConfigLayerStack;
|
||||
use crate::features::Feature;
|
||||
use crate::features::FeatureOverrides;
|
||||
use crate::features::Features;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use codex_app_server_protocol::ConfigValueWriteParams;
|
||||
use codex_app_server_protocol::MergeStrategy;
|
||||
use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
@@ -25,7 +24,6 @@ use serde::Deserialize;
|
||||
use serde_json::Map as JsonMap;
|
||||
use serde_json::Value as JsonValue;
|
||||
use serde_json::json;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use std::fs;
|
||||
use std::path::Path;
|
||||
@@ -97,7 +95,6 @@ impl PluginCapabilitySummary {
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct PluginCapabilityIndex {
|
||||
plugins: Vec<PluginCapabilitySummary>,
|
||||
plugin_index_by_config_name: HashMap<String, usize>,
|
||||
plugin_indexes_by_connector_id: HashMap<AppConnectorId, Vec<usize>>,
|
||||
plugin_indexes_by_mcp_server_name: HashMap<String, Vec<usize>>,
|
||||
}
|
||||
@@ -112,9 +109,6 @@ impl PluginCapabilityIndex {
|
||||
};
|
||||
|
||||
let plugin_index = capability_index.plugins.len();
|
||||
capability_index
|
||||
.plugin_index_by_config_name
|
||||
.insert(summary.config_name.clone(), plugin_index);
|
||||
|
||||
for connector_id in &summary.app_connector_ids {
|
||||
capability_index
|
||||
@@ -142,12 +136,6 @@ impl PluginCapabilityIndex {
|
||||
&self.plugins
|
||||
}
|
||||
|
||||
pub fn plugin_for_config_name(&self, config_name: &str) -> Option<&PluginCapabilitySummary> {
|
||||
self.plugin_index_by_config_name
|
||||
.get(config_name)
|
||||
.map(|plugin_index| &self.plugins[*plugin_index])
|
||||
}
|
||||
|
||||
pub fn plugins_for_connector_id(
|
||||
&self,
|
||||
connector_id: &AppConnectorId,
|
||||
@@ -170,66 +158,6 @@ impl PluginCapabilityIndex {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn annotate_tools_with_plugin_sources(
|
||||
mut tools: HashMap<String, ToolInfo>,
|
||||
capability_index: &PluginCapabilityIndex,
|
||||
) -> HashMap<String, ToolInfo> {
|
||||
for tool in tools.values_mut() {
|
||||
// Recover plugin provenance from the app connector or MCP server and
|
||||
// append it to the shared description text used by both prompt tools
|
||||
// and search_tool_bm25 indexing.
|
||||
let mut plugin_names = match tool.connector_id.as_ref() {
|
||||
Some(connector_id) => capability_index
|
||||
.plugins_for_connector_id(&AppConnectorId(connector_id.clone()))
|
||||
.into_iter()
|
||||
.map(|plugin| plugin.display_name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
None => capability_index
|
||||
.plugins_for_mcp_server_name(tool.server_name.as_str())
|
||||
.into_iter()
|
||||
.map(|plugin| plugin.display_name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
if plugin_names.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
plugin_names.sort_unstable();
|
||||
plugin_names.dedup();
|
||||
|
||||
let plugin_source_note = if plugin_names.len() == 1 {
|
||||
format!("This tool is part of plugin `{}`.", plugin_names[0])
|
||||
} else {
|
||||
format!(
|
||||
"This tool is part of plugins {}.",
|
||||
plugin_names
|
||||
.into_iter()
|
||||
.map(|plugin_name| format!("`{plugin_name}`"))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
)
|
||||
};
|
||||
|
||||
let description = tool
|
||||
.tool
|
||||
.description
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.unwrap_or("");
|
||||
let annotated_description = if description.is_empty() {
|
||||
plugin_source_note
|
||||
} else if matches!(description.chars().last(), Some('.' | '!' | '?')) {
|
||||
format!("{description} {plugin_source_note}")
|
||||
} else {
|
||||
format!("{description}. {plugin_source_note}")
|
||||
};
|
||||
tool.tool.description = Some(Cow::Owned(annotated_description));
|
||||
}
|
||||
|
||||
tools
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PluginLoadOutcome {
|
||||
plugins: Vec<LoadedPlugin>,
|
||||
@@ -1112,8 +1040,11 @@ mod tests {
|
||||
assert_eq!(
|
||||
outcome
|
||||
.capability_index()
|
||||
.plugin_for_config_name("empty@test"),
|
||||
None
|
||||
.plugins()
|
||||
.iter()
|
||||
.map(|plugin| plugin.config_name.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
vec!["skills@test", "alpha@test", "beta@test"]
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,6 @@ pub use manager::PluginInstallError;
|
||||
pub use manager::PluginInstallRequest;
|
||||
pub use manager::PluginLoadOutcome;
|
||||
pub use manager::PluginsManager;
|
||||
pub(crate) use manager::annotate_tools_with_plugin_sources;
|
||||
pub(crate) use manager::plugin_namespace_for_skill_path;
|
||||
pub(crate) use manifest::load_plugin_manifest;
|
||||
pub(crate) use manifest::plugin_manifest_name;
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::connectors;
|
||||
use crate::function_tool::FunctionCallError;
|
||||
use crate::mcp::CODEX_APPS_MCP_SERVER_NAME;
|
||||
use crate::mcp_connection_manager::ToolInfo;
|
||||
use crate::plugins::annotate_tools_with_plugin_sources;
|
||||
use crate::tools::context::ToolInvocation;
|
||||
use crate::tools::context::ToolOutput;
|
||||
use crate::tools::context::ToolPayload;
|
||||
@@ -126,15 +125,6 @@ impl ToolHandler for SearchToolBm25Handler {
|
||||
);
|
||||
let mcp_tools = filter_codex_apps_mcp_tools(mcp_tools, &connectors);
|
||||
let mcp_tools = connectors::filter_codex_apps_tools_by_policy(mcp_tools, &turn.config);
|
||||
let loaded_plugins = session
|
||||
.services
|
||||
.plugins_manager
|
||||
.plugins_for_config(&turn.config);
|
||||
// Reuse the same runtime annotation as prompt tool building so plugin
|
||||
// names become searchable through normal description indexing.
|
||||
let mcp_tools =
|
||||
annotate_tools_with_plugin_sources(mcp_tools, loaded_plugins.capability_index());
|
||||
|
||||
let mut entries: Vec<ToolEntry> = mcp_tools
|
||||
.into_iter()
|
||||
.map(|(name, info)| ToolEntry::new(name, info))
|
||||
|
||||
Reference in New Issue
Block a user