Codex Handbook
core/src/tools/code_mode/delegate.rs 312 lines
use std::collections::HashMap;use std::sync::Arc;use std::sync::Mutex;use codex_code_mode::CellId;use codex_code_mode::CodeModeNestedToolCall;use codex_code_mode::CodeModeSessionDelegate;use codex_code_mode::NotificationFuture;use codex_code_mode::ToolInvocationFuture;use codex_protocol::models::FunctionCallOutputPayload;use codex_protocol::models::ResponseItem;use serde_json::Value as JsonValue;use tokio::sync::oneshot;use tokio::sync::watch;use tokio_util::sync::CancellationToken;use super::ExecContext;use super::PUBLIC_TOOL_NAME;use super::call_nested_tool;use crate::tools::ToolRouter;use crate::tools::context::SharedTurnDiffTracker;use crate::tools::parallel::ToolCallRuntime;pub(super) struct CodeModeDispatchBroker {    dispatch_tx: async_channel::Sender<DispatchMessage>,    dispatch_rx: async_channel::Receiver<DispatchMessage>,    dispatch_gates: Arc<Mutex<HashMap<CellId, watch::Sender<bool>>>>,}impl CodeModeDispatchBroker {    pub(super) fn new() -> Self {        let (dispatch_tx, dispatch_rx) = async_channel::unbounded();        Self {            dispatch_tx,            dispatch_rx,            dispatch_gates: Arc::new(Mutex::new(HashMap::new())),        }    }    pub(super) fn mark_cell_ready_for_dispatch(&self, cell_id: &CellId) {        dispatch_gate(&self.dispatch_gates, cell_id).send_replace(true);    }    pub(super) fn close_cell(&self, cell_id: &CellId) {        remove_dispatch_gate(&self.dispatch_gates, cell_id);    }    pub(super) fn start_turn_worker(        &self,        exec: ExecContext,        router: Arc<ToolRouter>,        tracker: SharedTurnDiffTracker,    ) -> CodeModeDispatchWorker {        let tool_runtime = ToolCallRuntime::new(            router,            Arc::clone(&exec.session),            Arc::clone(&exec.turn),            tracker,        );        let host = Arc::new(CoreTurnHost { exec, tool_runtime });        let dispatch_rx = self.dispatch_rx.clone();        let dispatch_gates = Arc::clone(&self.dispatch_gates);        let (shutdown_tx, mut shutdown_rx) = oneshot::channel();        tokio::spawn(async move {            loop {                let message = tokio::select! {                    _ = &mut shutdown_rx => break,                    message = dispatch_rx.recv() => message.ok(),                };                let Some(message) = message else {                    break;                };                match message {                    DispatchMessage::Notify {                        call_id,                        cell_id,                        text,                        cancellation_token,                        response_tx,                    } => {                        let response = if wait_until_cell_ready_for_dispatch(                            &dispatch_gates,                            &cell_id,                            &cancellation_token,                        )                        .await                        {                            host.notify(call_id, cell_id, text).await                        } else {                            remove_dispatch_gate(&dispatch_gates, &cell_id);                            Err("code mode notification cancelled".to_string())                        };                        let _ = response_tx.send(response);                    }                    DispatchMessage::InvokeTool {                        invocation,                        cancellation_token,                        response_tx,                    } => {                        let cell_id = invocation.cell_id.clone();                        if !wait_until_cell_ready_for_dispatch(                            &dispatch_gates,                            &cell_id,                            &cancellation_token,                        )                        .await                        {                            remove_dispatch_gate(&dispatch_gates, &cell_id);                            continue;                        }                        let host = Arc::clone(&host);                        tokio::spawn(async move {                            let response = tokio::select! {                                response = host.invoke_tool(                                    invocation,                                    cancellation_token.clone(),                                ) => response,                                _ = cancellation_token.cancelled() => return,                            };                            let _ = response_tx.send(response);                        });                    }                }            }        });        CodeModeDispatchWorker {            shutdown_tx: Some(shutdown_tx),        }    }}fn dispatch_gate(    dispatch_gates: &Mutex<HashMap<CellId, watch::Sender<bool>>>,    cell_id: &CellId,) -> watch::Sender<bool> {    let mut dispatch_gates = match dispatch_gates.lock() {        Ok(dispatch_gates) => dispatch_gates,        Err(poisoned) => poisoned.into_inner(),    };    dispatch_gates        .entry(cell_id.clone())        .or_insert_with(|| watch::channel(false).0)        .clone()}fn remove_dispatch_gate(    dispatch_gates: &Mutex<HashMap<CellId, watch::Sender<bool>>>,    cell_id: &CellId,) {    let mut dispatch_gates = match dispatch_gates.lock() {        Ok(dispatch_gates) => dispatch_gates,        Err(poisoned) => poisoned.into_inner(),    };    dispatch_gates.remove(cell_id);}async fn wait_until_cell_ready_for_dispatch(    dispatch_gates: &Mutex<HashMap<CellId, watch::Sender<bool>>>,    cell_id: &CellId,    cancellation_token: &CancellationToken,) -> bool {    if cancellation_token.is_cancelled() {        return false;    }    let mut ready_rx = dispatch_gate(dispatch_gates, cell_id).subscribe();    loop {        if *ready_rx.borrow_and_update() {            return true;        }        tokio::select! {            changed = ready_rx.changed() => {                if changed.is_err() {                    return false;                }            }            _ = cancellation_token.cancelled() => return false,        }    }}impl CodeModeSessionDelegate for CodeModeDispatchBroker {    fn invoke_tool<'a>(        &'a self,        invocation: CodeModeNestedToolCall,        cancellation_token: CancellationToken,    ) -> ToolInvocationFuture<'a> {        Box::pin(async move {            if cancellation_token.is_cancelled() {                return Err("code mode nested tool call cancelled".to_string());            }            let (response_tx, response_rx) = oneshot::channel();            self.dispatch_tx                .send(DispatchMessage::InvokeTool {                    invocation,                    cancellation_token: cancellation_token.clone(),                    response_tx,                })                .await                .map_err(|_| "code mode nested tool dispatcher is unavailable".to_string())?;            tokio::select! {                response = response_rx => response                    .map_err(|_| "code mode nested tool dispatcher stopped".to_string())?,                _ = cancellation_token.cancelled() => {                    Err("code mode nested tool call cancelled".to_string())                }            }        })    }    fn notify<'a>(        &'a self,        call_id: String,        cell_id: CellId,        text: String,        cancellation_token: CancellationToken,    ) -> NotificationFuture<'a> {        Box::pin(async move {            if cancellation_token.is_cancelled() {                return Err("code mode notification cancelled".to_string());            }            let (response_tx, response_rx) = oneshot::channel();            self.dispatch_tx                .send(DispatchMessage::Notify {                    call_id,                    cell_id,                    text,                    cancellation_token: cancellation_token.clone(),                    response_tx,                })                .await                .map_err(|_| "code mode notification dispatcher is unavailable".to_string())?;            tokio::select! {                response = response_rx => response                    .map_err(|_| "code mode notification dispatcher stopped".to_string())?,                _ = cancellation_token.cancelled() => {                    Err("code mode notification cancelled".to_string())                }            }        })    }    fn cell_closed(&self, cell_id: &CellId) {        self.close_cell(cell_id);    }}enum DispatchMessage {    InvokeTool {        invocation: CodeModeNestedToolCall,        cancellation_token: CancellationToken,        response_tx: oneshot::Sender<Result<JsonValue, String>>,    },    Notify {        call_id: String,        cell_id: CellId,        text: String,        cancellation_token: CancellationToken,        response_tx: oneshot::Sender<Result<(), String>>,    },}pub(crate) struct CodeModeDispatchWorker {    shutdown_tx: Option<oneshot::Sender<()>>,}impl Drop for CodeModeDispatchWorker {    fn drop(&mut self) {        if let Some(shutdown_tx) = self.shutdown_tx.take() {            let _ = shutdown_tx.send(());        }    }}struct CoreTurnHost {    exec: ExecContext,    tool_runtime: ToolCallRuntime,}impl CoreTurnHost {    async fn invoke_tool(        &self,        invocation: CodeModeNestedToolCall,        cancellation_token: CancellationToken,    ) -> Result<JsonValue, String> {        call_nested_tool(            self.exec.clone(),            self.tool_runtime.clone(),            invocation,            cancellation_token,        )        .await        .map_err(|error| error.to_string())    }    async fn notify(&self, call_id: String, cell_id: CellId, text: String) -> Result<(), String> {        if text.trim().is_empty() {            return Ok(());        }        self.exec            .session            .inject_if_running(vec![ResponseItem::CustomToolCallOutput {                call_id,                name: Some(PUBLIC_TOOL_NAME.to_string()),                output: FunctionCallOutputPayload::from_text(text),                metadata: None,            }])            .await            .map_err(|_| {                format!("failed to inject exec notify message for cell {cell_id}: no active turn")            })    }}