use std::collections::BTreeMap; use std::sync::LazyLock; use crate::codex::Session; use crate::function_tool::FunctionCallError; use crate::openai_tools::JsonSchema; use crate::openai_tools::OpenAiTool; use crate::openai_tools::ResponsesApiTool; use crate::protocol::Event; use crate::protocol::EventMsg; // Use the canonical plan tool types from the protocol crate to ensure // type-identity matches events transported via `codex_protocol`. pub use codex_protocol::plan_tool::PlanItemArg; pub use codex_protocol::plan_tool::StepStatus; pub use codex_protocol::plan_tool::UpdatePlanArgs; // Types for the TODO tool arguments matching codex-vscode/todo-mcp/src/main.rs pub(crate) 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), }), }; let mut properties = BTreeMap::new(); properties.insert( "explanation".to_string(), JsonSchema::String { description: None }, ); properties.insert("plan".to_string(), plan_items_schema); OpenAiTool::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, parameters: JsonSchema::Object { properties, required: Some(vec!["plan".to_string()]), additional_properties: Some(false), }, }) }); /// This function doesn't do anything useful. However, it gives the model a structured way to record its plan that clients can read and render. /// So it's the _inputs_ to this function that are useful to clients, not the outputs and neither are actually useful for the model other /// than forcing it to come up and document a plan (TBD how that affects performance). pub(crate) async fn handle_update_plan( session: &Session, arguments: String, sub_id: String, _call_id: String, ) -> Result { let args = parse_update_plan_arguments(&arguments)?; session .send_event(Event { id: sub_id.to_string(), msg: EventMsg::PlanUpdate(args), }) .await; Ok("Plan updated".to_string()) } fn parse_update_plan_arguments(arguments: &str) -> Result { serde_json::from_str::(arguments).map_err(|e| { FunctionCallError::RespondToModel(format!("failed to parse function arguments: {e}")) }) }