diff --git a/codex-rs/core/src/client_common.rs b/codex-rs/core/src/client_common.rs index 2c727d2766..fb0a7a1d0f 100644 --- a/codex-rs/core/src/client_common.rs +++ b/codex-rs/core/src/client_common.rs @@ -157,6 +157,7 @@ fn strip_total_output_header(output: &str) -> Option<(&str, u32)> { } pub(crate) mod tools { + #[cfg(test)] pub(crate) use codex_tools::ResponsesApiTool; pub(crate) use codex_tools::ToolSearchOutputTool; pub(crate) use codex_tools::ToolSpec; diff --git a/codex-rs/core/src/tools/context_tests.rs b/codex-rs/core/src/tools/context_tests.rs index 54bf2ec75b..5a3c48809c 100644 --- a/codex-rs/core/src/tools/context_tests.rs +++ b/codex-rs/core/src/tools/context_tests.rs @@ -147,7 +147,7 @@ fn tool_search_payloads_roundtrip_as_tool_search_outputs() { description: String::new(), strict: false, defer_loading: Some(true), - parameters: crate::tools::spec::JsonSchema::Object { + parameters: codex_tools::JsonSchema::Object { properties: Default::default(), required: None, additional_properties: None, diff --git a/codex-rs/core/src/tools/handlers/mod.rs b/codex-rs/core/src/tools/handlers/mod.rs index e3229d7e6c..a2c2652215 100644 --- a/codex-rs/core/src/tools/handlers/mod.rs +++ b/codex-rs/core/src/tools/handlers/mod.rs @@ -22,7 +22,6 @@ use codex_sandboxing::policy_transforms::intersect_permission_profiles; use codex_sandboxing::policy_transforms::merge_permission_profiles; use codex_sandboxing::policy_transforms::normalize_additional_permissions; use codex_utils_absolute_path::AbsolutePathBufGuard; -pub use plan::PLAN_TOOL; use serde::Deserialize; use serde_json::Value; use std::path::Path; diff --git a/codex-rs/core/src/tools/handlers/plan.rs b/codex-rs/core/src/tools/handlers/plan.rs index af8dc2c310..ce0b98b1cb 100644 --- a/codex-rs/core/src/tools/handlers/plan.rs +++ b/codex-rs/core/src/tools/handlers/plan.rs @@ -1,5 +1,3 @@ -use crate::client_common::tools::ResponsesApiTool; -use crate::client_common::tools::ToolSpec; use crate::codex::Session; use crate::codex::TurnContext; use crate::function_tool::FunctionCallError; @@ -8,7 +6,6 @@ use crate::tools::context::ToolOutput; use crate::tools::context::ToolPayload; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; -use crate::tools::spec::JsonSchema; use async_trait::async_trait; use codex_protocol::config_types::ModeKind; use codex_protocol::models::FunctionCallOutputPayload; @@ -16,8 +13,6 @@ use codex_protocol::models::ResponseInputItem; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::EventMsg; use serde_json::Value as JsonValue; -use std::collections::BTreeMap; -use std::sync::LazyLock; pub struct PlanHandler; @@ -49,50 +44,6 @@ impl ToolOutput for PlanToolOutput { } } -pub static PLAN_TOOL: LazyLock = LazyLock::new(|| { - let mut plan_item_props = BTreeMap::new(); - plan_item_props.insert("step".to_string(), JsonSchema::String { description: None }); - plan_item_props.insert( - "status".to_string(), - JsonSchema::String { - description: Some("One of: pending, in_progress, completed".to_string()), - }, - ); - - let plan_items_schema = JsonSchema::Array { - description: Some("The list of steps".to_string()), - items: Box::new(JsonSchema::Object { - properties: plan_item_props, - required: Some(vec!["step".to_string(), "status".to_string()]), - additional_properties: Some(false.into()), - }), - }; - - let mut properties = BTreeMap::new(); - properties.insert( - "explanation".to_string(), - JsonSchema::String { description: None }, - ); - properties.insert("plan".to_string(), plan_items_schema); - - ToolSpec::Function(ResponsesApiTool { - name: "update_plan".to_string(), - description: r#"Updates the task plan. -Provide an optional explanation and a list of plan items, each with a step and status. -At most one step can be in_progress at a time. -"# - .to_string(), - strict: false, - defer_loading: None, - parameters: JsonSchema::Object { - properties, - required: Some(vec!["plan".to_string()]), - additional_properties: Some(false.into()), - }, - output_schema: None, - }) -}); - #[async_trait] impl ToolHandler for PlanHandler { type Output = PlanToolOutput; diff --git a/codex-rs/core/src/tools/handlers/tool_search_tests.rs b/codex-rs/core/src/tools/handlers/tool_search_tests.rs index 9a90ad738c..43e0c5de70 100644 --- a/codex-rs/core/src/tools/handlers/tool_search_tests.rs +++ b/codex-rs/core/src/tools/handlers/tool_search_tests.rs @@ -105,7 +105,7 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() { description: "Create a calendar event.".to_string(), strict: false, defer_loading: Some(true), - parameters: crate::tools::spec::JsonSchema::Object { + parameters: codex_tools::JsonSchema::Object { properties: Default::default(), required: None, additional_properties: None, @@ -117,7 +117,7 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() { description: "List calendar events.".to_string(), strict: false, defer_loading: Some(true), - parameters: crate::tools::spec::JsonSchema::Object { + parameters: codex_tools::JsonSchema::Object { properties: Default::default(), required: None, additional_properties: None, @@ -134,7 +134,7 @@ fn serialize_tool_search_output_tools_groups_results_by_namespace() { description: "Read an email.".to_string(), strict: false, defer_loading: Some(true), - parameters: crate::tools::spec::JsonSchema::Object { + parameters: codex_tools::JsonSchema::Object { properties: Default::default(), required: None, additional_properties: None, @@ -187,7 +187,7 @@ fn serialize_tool_search_output_tools_falls_back_to_connector_name_description() description: "Read multiple emails.".to_string(), strict: false, defer_loading: Some(true), - parameters: crate::tools::spec::JsonSchema::Object { + parameters: codex_tools::JsonSchema::Object { properties: Default::default(), required: None, additional_properties: None, diff --git a/codex-rs/core/src/tools/spec.rs b/codex-rs/core/src/tools/spec.rs index a8d124246a..9e4f4cf40d 100644 --- a/codex-rs/core/src/tools/spec.rs +++ b/codex-rs/core/src/tools/spec.rs @@ -5,7 +5,6 @@ use crate::shell::Shell; use crate::shell::ShellType; use crate::tools::code_mode::PUBLIC_TOOL_NAME; use crate::tools::code_mode::WAIT_TOOL_NAME; -use crate::tools::handlers::PLAN_TOOL; use crate::tools::handlers::TOOL_SEARCH_DEFAULT_LIMIT; use crate::tools::handlers::TOOL_SEARCH_TOOL_NAME; use crate::tools::handlers::TOOL_SUGGEST_TOOL_NAME; @@ -59,6 +58,7 @@ use codex_tools::create_spawn_agents_on_csv_tool; use codex_tools::create_test_sync_tool; use codex_tools::create_tool_search_tool; use codex_tools::create_tool_suggest_tool; +use codex_tools::create_update_plan_tool; use codex_tools::create_view_image_tool; use codex_tools::create_wait_agent_tool_v1; use codex_tools::create_wait_agent_tool_v2; @@ -71,7 +71,6 @@ use codex_tools::request_user_input_tool_description; use codex_tools::tool_spec_to_code_mode_tool_definition; use std::collections::HashMap; -pub type JsonSchema = codex_tools::JsonSchema; pub use codex_tools::ShellCommandBackendConfig; pub use codex_tools::ToolsConfig; pub use codex_tools::ToolsConfigParams; @@ -318,7 +317,7 @@ pub(crate) fn build_specs_with_discoverable_tools( push_tool_spec( &mut builder, - PLAN_TOOL.clone(), + create_update_plan_tool(), /*supports_parallel_tool_calls*/ false, config.code_mode_enabled, ); diff --git a/codex-rs/core/src/tools/spec_tests.rs b/codex-rs/core/src/tools/spec_tests.rs index 989b4fd165..cef3a42fde 100644 --- a/codex-rs/core/src/tools/spec_tests.rs +++ b/codex-rs/core/src/tools/spec_tests.rs @@ -24,6 +24,7 @@ use codex_tools::ConfiguredToolSpec; use codex_tools::DiscoverablePluginInfo; use codex_tools::DiscoverableTool; use codex_tools::FreeformTool; +use codex_tools::JsonSchema; use codex_tools::ResponsesApiTool; use codex_tools::ResponsesApiWebSearchFilters; use codex_tools::ResponsesApiWebSearchUserLocation; @@ -41,6 +42,7 @@ use codex_tools::create_send_input_tool_v1; use codex_tools::create_send_message_tool; use codex_tools::create_spawn_agent_tool_v1; use codex_tools::create_spawn_agent_tool_v2; +use codex_tools::create_update_plan_tool; use codex_tools::create_view_image_tool; use codex_tools::create_wait_agent_tool_v1; use codex_tools::create_wait_agent_tool_v2; @@ -331,7 +333,7 @@ fn test_full_toolset_specs_for_gpt5_codex_unified_exec_web_search() { exec_permission_approvals_enabled: false, }), create_write_stdin_tool(), - PLAN_TOOL.clone(), + create_update_plan_tool(), request_user_input_tool_spec(/*default_mode_request_user_input*/ false), create_apply_patch_freeform_tool(), ToolSpec::WebSearch { diff --git a/codex-rs/tools/src/lib.rs b/codex-rs/tools/src/lib.rs index 61a21bef71..23573f3c0f 100644 --- a/codex-rs/tools/src/lib.rs +++ b/codex-rs/tools/src/lib.rs @@ -12,6 +12,7 @@ mod json_schema; mod local_tool; mod mcp_resource_tool; mod mcp_tool; +mod plan_tool; mod request_user_input_tool; mod responses_api; mod tool_config; @@ -64,6 +65,7 @@ pub use mcp_resource_tool::create_list_mcp_resources_tool; pub use mcp_resource_tool::create_read_mcp_resource_tool; pub use mcp_tool::mcp_call_tool_result_output_schema; pub use mcp_tool::parse_mcp_tool; +pub use plan_tool::create_update_plan_tool; pub use request_user_input_tool::create_request_user_input_tool; pub use request_user_input_tool::request_user_input_tool_description; pub use request_user_input_tool::request_user_input_unavailable_message; diff --git a/codex-rs/tools/src/plan_tool.rs b/codex-rs/tools/src/plan_tool.rs new file mode 100644 index 0000000000..89ddcb5976 --- /dev/null +++ b/codex-rs/tools/src/plan_tool.rs @@ -0,0 +1,51 @@ +use crate::JsonSchema; +use crate::ResponsesApiTool; +use crate::ToolSpec; +use std::collections::BTreeMap; + +pub fn create_update_plan_tool() -> ToolSpec { + let plan_item_properties = BTreeMap::from([ + ("step".to_string(), JsonSchema::String { description: None }), + ( + "status".to_string(), + JsonSchema::String { + description: Some("One of: pending, in_progress, completed".to_string()), + }, + ), + ]); + + let properties = BTreeMap::from([ + ( + "explanation".to_string(), + JsonSchema::String { description: None }, + ), + ( + "plan".to_string(), + JsonSchema::Array { + description: Some("The list of steps".to_string()), + items: Box::new(JsonSchema::Object { + properties: plan_item_properties, + required: Some(vec!["step".to_string(), "status".to_string()]), + additional_properties: Some(false.into()), + }), + }, + ), + ]); + + ToolSpec::Function(ResponsesApiTool { + name: "update_plan".to_string(), + description: r#"Updates the task plan. +Provide an optional explanation and a list of plan items, each with a step and status. +At most one step can be in_progress at a time. +"# + .to_string(), + strict: false, + defer_loading: None, + parameters: JsonSchema::Object { + properties, + required: Some(vec!["plan".to_string()]), + additional_properties: Some(false.into()), + }, + output_schema: None, + }) +}