| |
|
|
| use codex_config::DEFAULT_MCP_SERVER_ENVIRONMENT_ID; |
| use codex_config::HookHandlerConfig; |
| use codex_exec_server::ExecutorCapabilityDiscoverySnapshot; |
| use codex_mcp::CODEX_APPS_MCP_SERVER_NAME; |
| use codex_mcp::MCP_TOOL_CODEX_APPS_META_KEY; |
| use codex_mcp::ToolInfo; |
| use codex_plugin::ExecutorPluginHookSource; |
| use codex_plugin::PluginId; |
| use codex_plugin::is_allowlisted_bundled_cleanup_hook; |
| use codex_plugin::manifest::PluginManifestHooks; |
| use codex_protocol::capabilities::CapabilityRootLocation; |
| use serde_json::Map; |
| use serde_json::Value; |
|
|
| use crate::manifest::parse_plugin_manifest_uri; |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub fn executor_plugin_hook_sources<'a>( |
| snapshot: &ExecutorCapabilityDiscoverySnapshot, |
| lookup_enabled_tool: impl Fn(&str, &str) -> Option<&'a ToolInfo>, |
| ) -> Vec<ExecutorPluginHookSource> { |
| let mut sources = Vec::new(); |
|
|
| for entry in snapshot.roots() { |
| let Ok(plugin_id) = PluginId::parse(&entry.selected_root.id) else { |
| continue; |
| }; |
| let CapabilityRootLocation::Environment { |
| environment_id, |
| path: plugin_root, |
| } = &entry.selected_root.location; |
| let Ok(discovery) = &entry.result else { |
| continue; |
| }; |
| let Some(plugin) = &discovery.plugin else { |
| continue; |
| }; |
| let Ok(manifest) = parse_plugin_manifest_uri( |
| plugin_root, |
| &plugin.manifest.path, |
| &plugin.manifest.contents, |
| ) else { |
| continue; |
| }; |
| |
| let Some(PluginManifestHooks::Inline(hook_files)) = manifest.paths.hooks else { |
| continue; |
| }; |
|
|
| for (hook_index, hook_file) in hook_files.into_iter().enumerate() { |
| let manifest_relative_path = plugin |
| .manifest |
| .path |
| .relative_path_from(plugin_root) |
| .unwrap_or_else(|| plugin.manifest.path.to_string()); |
|
|
| sources.push(ExecutorPluginHookSource { |
| plugin_id: plugin_id.clone(), |
| environment_id: environment_id.clone(), |
| mcp_environment_id: None, |
| mcp_metadata: None, |
| plugin_root: plugin_root.clone(), |
| manifest_path: plugin.manifest.path.clone(), |
| source_relative_path: format!("{manifest_relative_path}#hooks[{hook_index}]"), |
| hooks: hook_file.hooks, |
| }); |
| } |
| } |
|
|
| |
| sources |
| .into_iter() |
| .filter_map(|mut source| { |
| let plugin_id = source.plugin_id.as_key(); |
| for (event, groups) in source.hooks.matcher_groups_mut() { |
| groups.retain_mut(|group| { |
| group.hooks.retain(|handler| { |
| let app_connector_id = match handler { |
| HookHandlerConfig::McpTool { server, tool, .. } |
| if server == CODEX_APPS_MCP_SERVER_NAME => |
| { |
| lookup_enabled_tool(server, tool) |
| .and_then(|info| info.connector_id.as_deref()) |
| } |
| _ => None, |
| }; |
| is_allowlisted_bundled_cleanup_hook( |
| &plugin_id, |
| event, |
| group.matcher.as_deref(), |
| handler, |
| app_connector_id, |
| ) |
| }); |
| !group.hooks.is_empty() |
| }); |
| } |
| (!source.hooks.is_empty()).then_some(source) |
| }) |
| .filter_map(|source| resolve_mcp_routing(source, &lookup_enabled_tool)) |
| .collect() |
| } |
|
|
| |
| fn resolve_mcp_routing<'a>( |
| mut source: ExecutorPluginHookSource, |
| lookup_enabled_tool: &impl Fn(&str, &str) -> Option<&'a ToolInfo>, |
| ) -> Option<ExecutorPluginHookSource> { |
| let HookHandlerConfig::McpTool { server, tool, .. } = source |
| .hooks |
| .matcher_groups_mut() |
| .into_iter() |
| .find_map(|(_, groups)| groups.first())? |
| .hooks |
| .first()? |
| else { |
| return None; |
| }; |
| if server != CODEX_APPS_MCP_SERVER_NAME { |
| return Some(source); |
| } |
|
|
| let tool_info = lookup_enabled_tool(server, tool)?; |
| let routing_metadata = tool_info |
| .tool |
| .meta |
| .as_ref()? |
| .get(MCP_TOOL_CODEX_APPS_META_KEY)? |
| .as_object()?; |
| routing_metadata.get("resource_uri")?.as_str()?; |
| source.mcp_environment_id = Some(DEFAULT_MCP_SERVER_ENVIRONMENT_ID.to_string()); |
| source.mcp_metadata = Some(Map::from_iter([( |
| MCP_TOOL_CODEX_APPS_META_KEY.to_string(), |
| Value::Object(routing_metadata.clone()), |
| )])); |
| Some(source) |
| } |
|
|
| #[cfg(test)] |
| #[path = "executor_hooks_tests.rs"] |
| mod tests; |
|
|