use std::sync::Arc; use codex_extension_api::McpToolContext; use codex_extension_api::McpToolResultInput; use codex_extension_api::ToolCallOutcome; use codex_extension_api::ToolCallSource as ExtensionToolCallSource; use codex_extension_api::ToolFinishInput; use codex_extension_api::ToolStartInput; use codex_protocol::mcp::CallToolResult; use codex_tools::ToolName; use crate::session::session::Session; use crate::session::turn_context::TurnContext; use crate::tools::context::ToolCallSource; use crate::tools::context::ToolInvocation; pub(crate) async fn notify_tool_start( invocation: &ToolInvocation, mcp_tool: Option<&McpToolContext>, ) { let contributors = invocation .session .services .extensions .tool_lifecycle_contributors(); if contributors.is_empty() { return; } let thread_store = &invocation.session.services.thread_extension_data; let conversation_history = invocation.session.conversation_history_snapshot().await; let root_turn_id = invocation.turn.turn_metadata_state.root_turn_id(); let originating_item_id = invocation .originating_call() .await .and_then(|origin| origin.item_id); for contributor in contributors { contributor .on_tool_start(ToolStartInput { session_store: &invocation.session.services.session_extension_data, thread_store, turn_store: invocation.turn.extension_data.as_ref(), turn_id: invocation.turn.sub_id.as_str(), root_turn_id: root_turn_id.as_deref(), call_id: invocation.call_id.as_str(), originating_item_id: originating_item_id.as_ref(), tool_name: &invocation.tool_name, mcp_tool, payload: &invocation.payload, conversation_history: Arc::clone(&conversation_history), source: extension_tool_call_source(invocation.source.clone()), }) .await; } } pub(crate) async fn process_mcp_tool_result( session: &Session, turn: &TurnContext, call_id: &str, mcp_tool: &McpToolContext, arguments: &serde_json::Value, result: &mut CallToolResult, ) { for contributor in session.services.extensions.tool_lifecycle_contributors() { contributor .on_mcp_tool_result(McpToolResultInput { session_store: &session.services.session_extension_data, thread_store: &session.services.thread_extension_data, turn_store: turn.extension_data.as_ref(), turn_id: &turn.sub_id, call_id, mcp_tool, arguments, result, }) .await; } } pub(crate) async fn notify_tool_finish(invocation: &ToolInvocation, outcome: ToolCallOutcome) { notify_tool_finish_parts( invocation.session.as_ref(), invocation.turn.as_ref(), invocation.call_id.as_str(), &invocation.tool_name, invocation.source.clone(), outcome, ) .await; } pub(crate) async fn notify_tool_aborted( session: &Session, turn: &TurnContext, call_id: &str, tool_name: &ToolName, source: ToolCallSource, ) { notify_tool_finish_parts( session, turn, call_id, tool_name, source, ToolCallOutcome::Aborted, ) .await; } async fn notify_tool_finish_parts( session: &Session, turn: &TurnContext, call_id: &str, tool_name: &ToolName, source: ToolCallSource, outcome: ToolCallOutcome, ) { for contributor in session.services.extensions.tool_lifecycle_contributors() { contributor .on_tool_finish(ToolFinishInput { session_store: &session.services.session_extension_data, thread_store: &session.services.thread_extension_data, turn_store: turn.extension_data.as_ref(), turn_id: turn.sub_id.as_str(), call_id, tool_name, source: extension_tool_call_source(source.clone()), outcome, }) .await; } } pub(crate) fn extension_tool_call_source(source: ToolCallSource) -> ExtensionToolCallSource { match source { ToolCallSource::Direct | ToolCallSource::DirectPlaintextMessage => { ExtensionToolCallSource::Direct } ToolCallSource::CodeMode { cell_id, runtime_tool_call_id, } => ExtensionToolCallSource::CodeMode { cell_id, runtime_tool_call_id, }, } }