From 005d181501cac686fce96f61854dbf69c6fa69a4 Mon Sep 17 00:00:00 2001 From: Casey Chow Date: Mon, 18 May 2026 21:49:50 -0400 Subject: [PATCH] feat(core): add reload_plugins tool --- codex-rs/core/src/tools/handlers/mod.rs | 3 + .../core/src/tools/handlers/reload_plugins.rs | 70 +++++++++++++++++++ .../src/tools/handlers/reload_plugins_spec.rs | 33 +++++++++ codex-rs/core/src/tools/spec_plan.rs | 10 +++ codex-rs/core/src/tools/spec_plan_tests.rs | 31 ++++++++ 5 files changed, 147 insertions(+) create mode 100644 codex-rs/core/src/tools/handlers/reload_plugins.rs create mode 100644 codex-rs/core/src/tools/handlers/reload_plugins_spec.rs diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index 69281acc7d..8f8506d2ce 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -15,6 +15,8 @@ pub(crate) mod multi_agents_spec; pub(crate) mod multi_agents_v2; mod plan; pub(crate) mod plan_spec; +mod reload_plugins; +pub(crate) mod reload_plugins_spec; mod request_permissions; mod request_plugin_install; pub(crate) mod request_plugin_install_spec; @@ -59,6 +61,7 @@ pub use mcp_resource::ListMcpResourceTemplatesHandler; pub use mcp_resource::ListMcpResourcesHandler; pub use mcp_resource::ReadMcpResourceHandler; pub use plan::PlanHandler; +pub use reload_plugins::ReloadPluginsHandler; pub use request_permissions::RequestPermissionsHandler; pub use request_plugin_install::RequestPluginInstallHandler; pub use request_user_input::RequestUserInputHandler; diff --git a/codex-rs/core/src/tools/handlers/reload_plugins.rs b/codex-rs/core/src/tools/handlers/reload_plugins.rs new file mode 100644 index 0000000000..479f4eb510 --- /dev/null +++ b/codex-rs/core/src/tools/handlers/reload_plugins.rs @@ -0,0 +1,70 @@ +use crate::function_tool::FunctionCallError; +use crate::tools::context::FunctionToolOutput; +use crate::tools::context::ToolInvocation; +use crate::tools::context::ToolOutput; +use crate::tools::context::ToolPayload; +use crate::tools::context::boxed_tool_output; +use crate::tools::handlers::reload_plugins_spec::RELOAD_PLUGINS_TOOL_NAME; +use crate::tools::handlers::reload_plugins_spec::create_reload_plugins_tool; +use crate::tools::registry::CoreToolRuntime; +use crate::tools::registry::ToolExecutor; +use codex_tools::ToolName; +use codex_tools::ToolSpec; + +pub struct ReloadPluginsHandler; + +#[async_trait::async_trait] +impl ToolExecutor for ReloadPluginsHandler { + fn tool_name(&self) -> ToolName { + ToolName::plain(RELOAD_PLUGINS_TOOL_NAME) + } + + fn spec(&self) -> Option { + Some(create_reload_plugins_tool()) + } + + async fn handle( + &self, + invocation: ToolInvocation, + ) -> Result, FunctionCallError> { + let ToolInvocation { + payload, + session, + turn, + .. + } = invocation; + + match payload { + ToolPayload::Function { arguments } if arguments.trim() == "{}" => {} + ToolPayload::Function { arguments } if arguments.trim().is_empty() => {} + ToolPayload::Function { .. } => { + return Err(FunctionCallError::RespondToModel( + "reload_plugins does not accept arguments".to_string(), + )); + } + _ => { + return Err(FunctionCallError::Fatal(format!( + "{RELOAD_PLUGINS_TOOL_NAME} handler received unsupported payload" + ))); + } + } + + session.reload_user_config_layer().await; + let config = session.get_config().await; + session + .refresh_mcp_servers_now( + turn.as_ref(), + config.mcp_servers.get().clone(), + config.mcp_oauth_credentials_store_mode, + Some(session.mcp_elicitation_reviewer()), + ) + .await; + + Ok(boxed_tool_output(FunctionToolOutput::from_text( + "{\"reloaded\":true}".to_string(), + Some(true), + ))) + } +} + +impl CoreToolRuntime for ReloadPluginsHandler {} diff --git a/codex-rs/core/src/tools/handlers/reload_plugins_spec.rs b/codex-rs/core/src/tools/handlers/reload_plugins_spec.rs new file mode 100644 index 0000000000..baff25be30 --- /dev/null +++ b/codex-rs/core/src/tools/handlers/reload_plugins_spec.rs @@ -0,0 +1,33 @@ +use codex_tools::JsonSchema; +use codex_tools::ResponsesApiTool; +use codex_tools::ToolSpec; +use std::collections::BTreeMap; + +pub const RELOAD_PLUGINS_TOOL_NAME: &str = "reload_plugins"; + +pub fn create_reload_plugins_tool() -> ToolSpec { + ToolSpec::Function(ResponsesApiTool { + name: RELOAD_PLUGINS_TOOL_NAME.to_string(), + description: "Reload plugin configuration, clear plugin and skill caches, and rebuild MCP tools from the refreshed plugin state.".to_string(), + strict: false, + defer_loading: None, + parameters: JsonSchema::object(BTreeMap::new(), Some(Vec::new()), Some(false.into())), + output_schema: None, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn reload_plugins_tool_has_no_arguments() { + let ToolSpec::Function(tool) = create_reload_plugins_tool() else { + panic!("reload_plugins should be a function tool"); + }; + + assert_eq!(tool.name, RELOAD_PLUGINS_TOOL_NAME); + assert_eq!(tool.parameters.required, Some(Vec::new())); + assert_eq!(tool.parameters.properties, Some(BTreeMap::new())); + } +} diff --git a/codex-rs/core/src/tools/spec_plan.rs b/codex-rs/core/src/tools/spec_plan.rs index 3f355d2509..f003e37e48 100644 --- a/codex-rs/core/src/tools/spec_plan.rs +++ b/codex-rs/core/src/tools/spec_plan.rs @@ -16,6 +16,7 @@ use crate::tools::handlers::ListMcpResourcesHandler; use crate::tools::handlers::McpHandler; use crate::tools::handlers::PlanHandler; use crate::tools::handlers::ReadMcpResourceHandler; +use crate::tools::handlers::ReloadPluginsHandler; use crate::tools::handlers::RequestPermissionsHandler; use crate::tools::handlers::RequestPluginInstallHandler; use crate::tools::handlers::RequestUserInputHandler; @@ -45,6 +46,7 @@ use crate::tools::handlers::multi_agents_v2::ListAgentsHandler as ListAgentsHand use crate::tools::handlers::multi_agents_v2::SendMessageHandler as SendMessageHandlerV2; use crate::tools::handlers::multi_agents_v2::SpawnAgentHandler as SpawnAgentHandlerV2; use crate::tools::handlers::multi_agents_v2::WaitAgentHandler as WaitAgentHandlerV2; +use crate::tools::handlers::reload_plugins_spec::RELOAD_PLUGINS_TOOL_NAME; use crate::tools::handlers::view_image_spec::ViewImageToolOptions; use crate::tools::hosted_spec::WebSearchToolOptions; use crate::tools::hosted_spec::create_image_generation_tool; @@ -435,6 +437,14 @@ fn collect_tool_executors( executors.push(Arc::new(TestSyncHandler)); } + if config + .experimental_supported_tools + .iter() + .any(|tool| tool == RELOAD_PLUGINS_TOOL_NAME) + { + executors.push(Arc::new(ReloadPluginsHandler)); + } + if config.environment_mode.has_environment() { let include_environment_id = matches!(config.environment_mode, ToolEnvironmentMode::Multiple); diff --git a/codex-rs/core/src/tools/spec_plan_tests.rs b/codex-rs/core/src/tools/spec_plan_tests.rs index 3442fbb5ff..c8ca5a74c0 100644 --- a/codex-rs/core/src/tools/spec_plan_tests.rs +++ b/codex-rs/core/src/tools/spec_plan_tests.rs @@ -14,6 +14,7 @@ use crate::tools::handlers::multi_agents_spec::create_spawn_agent_tool_v2; use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v1; use crate::tools::handlers::multi_agents_spec::create_wait_agent_tool_v2; use crate::tools::handlers::plan_spec::create_update_plan_tool; +use crate::tools::handlers::reload_plugins_spec::RELOAD_PLUGINS_TOOL_NAME; use crate::tools::handlers::request_user_input_spec::REQUEST_USER_INPUT_TOOL_NAME; use crate::tools::handlers::request_user_input_spec::create_request_user_input_tool; use crate::tools::handlers::request_user_input_spec::request_user_input_tool_description; @@ -1392,6 +1393,36 @@ fn test_test_model_info_includes_sync_tool() { assert!(tools.iter().any(|tool| tool.name() == "test_sync_tool")); } +#[test] +fn test_model_info_includes_reload_plugins_tool() { + let mut model_info = model_info(); + model_info.experimental_supported_tools = vec![RELOAD_PLUGINS_TOOL_NAME.to_string()]; + let features = Features::with_defaults(); + let available_models = Vec::new(); + let tools_config = ToolsConfig::new(&ToolsConfigParams { + model_info: &model_info, + available_models: &available_models, + features: &features, + image_generation_tool_auth_allowed: true, + web_search_mode: Some(WebSearchMode::Cached), + session_source: SessionSource::Cli, + permission_profile: &PermissionProfile::Disabled, + windows_sandbox_level: WindowsSandboxLevel::Disabled, + }); + let (tools, _) = build_specs( + &tools_config, + /*mcp_tools*/ None, + /*deferred_mcp_tools*/ None, + &[], + ); + + assert!( + tools + .iter() + .any(|tool| tool.name() == RELOAD_PLUGINS_TOOL_NAME) + ); +} + #[test] fn test_build_specs_mcp_tools_converted() { let model_info = model_info();