Codex Handbook
core/src/tools/handlers/plan.rs 105 lines
use crate::function_tool::FunctionCallError;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::plan_spec::create_update_plan_tool;use crate::tools::registry::CoreToolRuntime;use crate::tools::registry::ToolExecutor;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 codex_tools::ToolName;use codex_tools::ToolSpec;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 ToolExecutor<ToolInvocation> for PlanHandler {    fn tool_name(&self) -> ToolName {        ToolName::plain("update_plan")    }    fn spec(&self) -> ToolSpec {        create_update_plan_tool()    }    fn handle(&self, invocation: ToolInvocation) -> codex_tools::ToolExecutorFuture<'_> {        Box::pin(self.handle_call(invocation))    }}impl PlanHandler {    async fn handle_call(        &self,        invocation: ToolInvocation,    ) -> Result<Box<dyn crate::tools::context::ToolOutput>, FunctionCallError> {        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(),                ));            }        };        if turn.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.as_ref(), EventMsg::PlanUpdate(args))            .await;        Ok(boxed_tool_output(PlanToolOutput))    }}impl CoreToolRuntime for PlanHandler {}fn parse_update_plan_arguments(arguments: &str) -> Result<UpdatePlanArgs, FunctionCallError> {    serde_json::from_str::<UpdatePlanArgs>(arguments).map_err(|e| {        FunctionCallError::RespondToModel(format!("failed to parse function arguments: {e}"))    })}