feat(core): add reload_plugins tool

This commit is contained in:
Casey Chow
2026-05-18 21:49:50 -04:00
parent 1565b533f9
commit 005d181501
5 changed files with 147 additions and 0 deletions

View File

@@ -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;

View File

@@ -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<ToolInvocation> for ReloadPluginsHandler {
fn tool_name(&self) -> ToolName {
ToolName::plain(RELOAD_PLUGINS_TOOL_NAME)
}
fn spec(&self) -> Option<ToolSpec> {
Some(create_reload_plugins_tool())
}
async fn handle(
&self,
invocation: ToolInvocation,
) -> Result<Box<dyn ToolOutput>, 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 {}

View File

@@ -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()));
}
}

View File

@@ -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);

View File

@@ -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();