use crate::function_tool::FunctionCallError; use crate::session::session::Session; use crate::session::turn_context::TurnContext; use crate::tools::context::ToolInvocation; use crate::tools::context::ToolOutput; use crate::tools::context::ToolPayload; use crate::tools::registry::ToolHandler; use crate::tools::registry::ToolKind; use codex_protocol::config_types::ModeKind; use codex_protocol::models::FunctionCallOutputPayload; use codex_protocol::models::ResponseInputItem; use codex_protocol::plan_tool::UpdatePlanArgs; use codex_protocol::protocol::EventMsg; use serde_json::Value as JsonValue; pub struct PlanHandler; pub struct PlanToolOutput; const PLAN_UPDATED_MESSAGE: &str = "Plan updated"; impl ToolOutput for PlanToolOutput { fn log_preview(&self) -> String { PLAN_UPDATED_MESSAGE.to_string() } fn success_for_logging(&self) -> bool { true } fn to_response_item(&self, call_id: &str, _payload: &ToolPayload) -> ResponseInputItem { let mut output = FunctionCallOutputPayload::from_text(PLAN_UPDATED_MESSAGE.to_string()); output.success = Some(true); ResponseInputItem::FunctionCallOutput { call_id: call_id.to_string(), output, } } fn code_mode_result(&self, _payload: &ToolPayload) -> JsonValue { JsonValue::Object(serde_json::Map::new()) } } impl ToolHandler for PlanHandler { type Output = PlanToolOutput; fn kind(&self) -> ToolKind { ToolKind::Function } async fn handle(&self, invocation: ToolInvocation) -> Result { let ToolInvocation { session, turn, call_id, payload, .. } = invocation; let arguments = match payload { ToolPayload::Function { arguments } => arguments, _ => { return Err(FunctionCallError::RespondToModel( "update_plan handler received unsupported payload".to_string(), )); } }; handle_update_plan(session.as_ref(), turn.as_ref(), arguments, call_id).await?; Ok(PlanToolOutput) } } /// 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, turn_context: &TurnContext, arguments: String, _call_id: String, ) -> Result { if turn_context.collaboration_mode.mode == ModeKind::Plan { return Err(FunctionCallError::RespondToModel( "update_plan is a TODO/checklist tool and is not allowed in Plan mode".to_string(), )); } let args = parse_update_plan_arguments(&arguments)?; session .send_event(turn_context, 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}")) }) }