use std::collections::HashMap; use std::path::PathBuf; use std::sync::Arc; use codex_core::Codex; use codex_core::codex_wrapper::init_codex; use codex_core::config::Config as CodexConfig; use codex_core::config::ConfigOverrides; use codex_core::protocol::EventMsg; use codex_core::protocol::SessionConfiguredEvent; use mcp_types::RequestId; use tokio::sync::Mutex; use uuid::Uuid; use crate::conversation_loop::run_conversation_loop; use crate::json_to_toml::json_to_toml; use crate::mcp_protocol::ConversationCreateArgs; use crate::mcp_protocol::ConversationCreateResult; use crate::mcp_protocol::ConversationId; use crate::mcp_protocol::ToolCallResponseResult; use crate::message_processor::MessageProcessor; pub(crate) async fn handle_create_conversation( message_processor: &MessageProcessor, id: RequestId, args: ConversationCreateArgs, ) { // Build ConfigOverrides from args let ConversationCreateArgs { prompt: _, // not used here; creation only establishes the session model, cwd, approval_policy, sandbox, config, profile, base_instructions, } = args; // Convert config overrides JSON into CLI-style TOML overrides let cli_overrides: Vec<(String, toml::Value)> = match config { Some(v) => match v.as_object() { Some(map) => map .into_iter() .map(|(k, v)| (k.clone(), json_to_toml(v.clone()))) .collect(), None => Vec::new(), }, None => Vec::new(), }; let overrides = ConfigOverrides { model: Some(model.clone()), cwd: Some(PathBuf::from(cwd)), approval_policy, sandbox_mode: sandbox, model_provider: None, config_profile: profile, codex_linux_sandbox_exe: None, base_instructions, include_plan_tool: None, disable_response_storage: None, show_raw_agent_reasoning: None, }; let cfg: CodexConfig = match CodexConfig::load_with_cli_overrides(cli_overrides, overrides) { Ok(cfg) => cfg, Err(e) => { message_processor .send_response_with_optional_error( id, Some(ToolCallResponseResult::ConversationCreate( ConversationCreateResult::Error { message: format!("Failed to load config: {e}"), }, )), Some(true), ) .await; return; } }; // Initialize Codex session let codex_conversation = match init_codex(cfg).await { Ok(conv) => conv, Err(e) => { message_processor .send_response_with_optional_error( id, Some(ToolCallResponseResult::ConversationCreate( ConversationCreateResult::Error { message: format!("Failed to initialize session: {e}"), }, )), Some(true), ) .await; return; } }; // Expect SessionConfigured; if not, return error. let EventMsg::SessionConfigured(SessionConfiguredEvent { model, .. }) = &codex_conversation.session_configured.msg else { message_processor .send_response_with_optional_error( id, Some(ToolCallResponseResult::ConversationCreate( ConversationCreateResult::Error { message: "Expected SessionConfigured event".to_string(), }, )), Some(true), ) .await; return; }; let effective_model = model.clone(); let session_id = codex_conversation.session_id; let codex_arc = Arc::new(codex_conversation.codex); // Store session for future calls insert_session( session_id, codex_arc.clone(), message_processor.session_map(), ) .await; // Run the conversation loop in the background so this request can return immediately. let outgoing = message_processor.outgoing(); let spawn_id = id.clone(); tokio::spawn(async move { run_conversation_loop(codex_arc.clone(), outgoing, spawn_id).await; }); // Reply with the new conversation id and effective model message_processor .send_response_with_optional_error( id, Some(ToolCallResponseResult::ConversationCreate( ConversationCreateResult::Ok { conversation_id: ConversationId(session_id), model: effective_model, }, )), Some(false), ) .await; } async fn insert_session( session_id: Uuid, codex: Arc, session_map: Arc>>>, ) { let mut guard = session_map.lock().await; guard.insert(session_id, codex); }