diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs index cf6e8b51..1a6313db 100644 --- a/codex-rs/core/src/protocol.rs +++ b/codex-rs/core/src/protocol.rs @@ -278,8 +278,9 @@ pub struct Event { } /// Response event from the agent -#[derive(Debug, Clone, Deserialize, Serialize)] +#[derive(Debug, Clone, Deserialize, Serialize, Display)] #[serde(tag = "type", rename_all = "snake_case")] +#[strum(serialize_all = "lowercase")] pub enum EventMsg { /// Error while executing a submission Error(ErrorEvent), diff --git a/codex-rs/mcp-server/src/outgoing_message.rs b/codex-rs/mcp-server/src/outgoing_message.rs index a1eea65f..e4af1f78 100644 --- a/codex-rs/mcp-server/src/outgoing_message.rs +++ b/codex-rs/mcp-server/src/outgoing_message.rs @@ -83,11 +83,26 @@ impl OutgoingMessageSender { let params = Some(serde_json::to_value(event).expect("Event must serialize")); let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { method: "codex/event".to_string(), + params: params.clone(), + }); + let _ = self.sender.send(outgoing_message).await; + + self.send_event_as_notification_new_schema(event, params) + .await; + } + // should be backwards compatible. + // it will replace send_event_as_notification eventually. + async fn send_event_as_notification_new_schema( + &self, + event: &Event, + params: Option, + ) { + let outgoing_message = OutgoingMessage::Notification(OutgoingNotification { + method: event.msg.to_string(), params, }); let _ = self.sender.send(outgoing_message).await; } - pub(crate) async fn send_error(&self, id: RequestId, error: JSONRPCErrorError) { let outgoing_message = OutgoingMessage::Error(OutgoingError { id, error }); let _ = self.sender.send(outgoing_message).await; diff --git a/codex-rs/mcp-server/tests/common/mcp_process.rs b/codex-rs/mcp-server/tests/common/mcp_process.rs index b27a96eb..528a4015 100644 --- a/codex-rs/mcp-server/tests/common/mcp_process.rs +++ b/codex-rs/mcp-server/tests/common/mcp_process.rs @@ -270,27 +270,49 @@ impl McpProcess { pub async fn read_stream_until_configured_response_message( &mut self, ) -> anyhow::Result { + let mut sid_old: Option = None; + let mut sid_new: Option = None; loop { let message = self.read_jsonrpc_message().await?; eprint!("message: {message:?}"); match message { JSONRPCMessage::Notification(notification) => { - if notification.method == "codex/event" { - if let Some(params) = notification.params { + if let Some(params) = notification.params { + // Back-compat schema: method == "codex/event" and msg.type == "session_configured" + if notification.method == "codex/event" { if let Some(msg) = params.get("msg") { - if let Some(msg_type) = msg.get("type") { - if msg_type == "session_configured" { - if let Some(session_id) = msg.get("session_id") { - return Ok(session_id - .to_string() - .trim_matches('"') - .to_string()); - } + if msg.get("type").and_then(|v| v.as_str()) + == Some("session_configured") + { + if let Some(session_id) = + msg.get("session_id").and_then(|v| v.as_str()) + { + sid_old = Some(session_id.to_string()); } } } } + // New schema: method is the Display of EventMsg::SessionConfigured => "SessionConfigured" + if notification.method == "sessionconfigured" { + if let Some(msg) = params.get("msg") { + if let Some(session_id) = + msg.get("session_id").and_then(|v| v.as_str()) + { + sid_new = Some(session_id.to_string()); + } + } + } + } + + if sid_old.is_some() && sid_new.is_some() { + // Both seen, they must match + assert_eq!( + sid_old.as_ref().unwrap(), + sid_new.as_ref().unwrap(), + "session_id mismatch between old and new schema" + ); + return Ok(sid_old.unwrap()); } } JSONRPCMessage::Request(_) => {