This PR does two things because after I got deep into the first one I started pulling on the thread to the second: - Makes `ConversationManager` the place where all in-memory conversations are created and stored. Previously, `MessageProcessor` in the `codex-mcp-server` crate was doing this via its `session_map`, but this is something that should be done in `codex-core`. - It unwinds the `ctrl_c: tokio::sync::Notify` that was threaded throughout our code. I think this made sense at one time, but now that we handle Ctrl-C within the TUI and have a proper `Op::Interrupt` event, I don't think this was quite right, so I removed it. For `codex exec` and `codex proto`, we now use `tokio::signal::ctrl_c()` directly, but we no longer make `Notify` a field of `Codex` or `CodexConversation`. Changes of note: - Adds the files `conversation_manager.rs` and `codex_conversation.rs` to `codex-core`. - `Codex` and `CodexSpawnOk` are no longer exported from `codex-core`: other crates must use `CodexConversation` instead (which is created via `ConversationManager`). - `core/src/codex_wrapper.rs` has been deleted in favor of `ConversationManager`. - `ConversationManager::new_conversation()` returns `NewConversation`, which is in line with the `new_conversation` tool we want to add to the MCP server. Note `NewConversation` includes `SessionConfiguredEvent`, so we eliminate checks in cases like `codex-rs/core/tests/client.rs` to verify `SessionConfiguredEvent` is the first event because that is now internal to `ConversationManager`. - Quite a bit of code was deleted from `codex-rs/mcp-server/src/message_processor.rs` since it no longer has to manage multiple conversations itself: it goes through `ConversationManager` instead. - `core/tests/live_agent.rs` has been deleted because I had to update a bunch of tests and all the tests in here were ignored, and I don't think anyone ever ran them, so this was just technical debt, at this point. - Removed `notify_on_sigint()` from `util.rs` (and in a follow-up, I hope to refactor the blandly-named `util.rs` into more descriptive files). - In general, I started replacing local variables named `codex` as `conversation`, where appropriate, though admittedly I didn't do it through all the integration tests because that would have added a lot of noise to this PR. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2240). * #2264 * #2263 * __->__ #2240
115 lines
3.3 KiB
Rust
115 lines
3.3 KiB
Rust
use codex_core::protocol::Op;
|
|
use codex_core::protocol::Submission;
|
|
use mcp_types::RequestId;
|
|
|
|
use crate::mcp_protocol::ConversationSendMessageArgs;
|
|
use crate::mcp_protocol::ConversationSendMessageResult;
|
|
use crate::mcp_protocol::ToolCallResponseResult;
|
|
use crate::message_processor::MessageProcessor;
|
|
|
|
pub(crate) async fn handle_send_message(
|
|
message_processor: &MessageProcessor,
|
|
id: RequestId,
|
|
arguments: ConversationSendMessageArgs,
|
|
) {
|
|
let ConversationSendMessageArgs {
|
|
conversation_id,
|
|
content: items,
|
|
parent_message_id: _,
|
|
conversation_overrides: _,
|
|
} = arguments;
|
|
|
|
if items.is_empty() {
|
|
message_processor
|
|
.send_response_with_optional_error(
|
|
id,
|
|
Some(ToolCallResponseResult::ConversationSendMessage(
|
|
ConversationSendMessageResult::Error {
|
|
message: "No content items provided".to_string(),
|
|
},
|
|
)),
|
|
Some(true),
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
|
|
let session_id = conversation_id.0;
|
|
let Ok(codex) = message_processor
|
|
.get_conversation_manager()
|
|
.get_conversation(session_id)
|
|
.await
|
|
else {
|
|
message_processor
|
|
.send_response_with_optional_error(
|
|
id,
|
|
Some(ToolCallResponseResult::ConversationSendMessage(
|
|
ConversationSendMessageResult::Error {
|
|
message: "Session does not exist".to_string(),
|
|
},
|
|
)),
|
|
Some(true),
|
|
)
|
|
.await;
|
|
return;
|
|
};
|
|
|
|
let running = {
|
|
let running_sessions = message_processor.running_session_ids();
|
|
let mut running_sessions = running_sessions.lock().await;
|
|
!running_sessions.insert(session_id)
|
|
};
|
|
|
|
if running {
|
|
message_processor
|
|
.send_response_with_optional_error(
|
|
id,
|
|
Some(ToolCallResponseResult::ConversationSendMessage(
|
|
ConversationSendMessageResult::Error {
|
|
message: "Session is already running".to_string(),
|
|
},
|
|
)),
|
|
Some(true),
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
|
|
let request_id_string = match &id {
|
|
RequestId::String(s) => s.clone(),
|
|
RequestId::Integer(i) => i.to_string(),
|
|
};
|
|
|
|
let submit_res = codex
|
|
.submit_with_id(Submission {
|
|
id: request_id_string,
|
|
op: Op::UserInput { items },
|
|
})
|
|
.await;
|
|
|
|
if let Err(e) = submit_res {
|
|
message_processor
|
|
.send_response_with_optional_error(
|
|
id,
|
|
Some(ToolCallResponseResult::ConversationSendMessage(
|
|
ConversationSendMessageResult::Error {
|
|
message: format!("Failed to submit user input: {e}"),
|
|
},
|
|
)),
|
|
Some(true),
|
|
)
|
|
.await;
|
|
return;
|
|
}
|
|
|
|
message_processor
|
|
.send_response_with_optional_error(
|
|
id,
|
|
Some(ToolCallResponseResult::ConversationSendMessage(
|
|
ConversationSendMessageResult::Ok,
|
|
)),
|
|
Some(false),
|
|
)
|
|
.await;
|
|
}
|