mirror of
https://github.com/openai/codex.git
synced 2026-04-29 00:55:38 +00:00
331 lines
15 KiB
Markdown
331 lines
15 KiB
Markdown
# PR #1684: Changing method in MCP notifications
|
|
|
|
- URL: https://github.com/openai/codex/pull/1684
|
|
- Author: aibrahim-oai
|
|
- Created: 2025-07-26 00:11:20 UTC
|
|
- Updated: 2025-07-27 03:43:38 UTC
|
|
- Changes: +50/-12, Files changed: 3, Commits: 12
|
|
|
|
## Description
|
|
|
|
- Changing the codex/event type
|
|
|
|
## Full Diff
|
|
|
|
```diff
|
|
diff --git a/codex-rs/core/src/protocol.rs b/codex-rs/core/src/protocol.rs
|
|
index cf6e8b5191..1a6313db92 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 a1eea65f25..e4af1f78cd 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<serde_json::Value>,
|
|
+ ) {
|
|
+ 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 b27a96eb89..528a40152f 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<String> {
|
|
+ let mut sid_old: Option<String> = None;
|
|
+ let mut sid_new: Option<String> = 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(_) => {
|
|
```
|
|
|
|
## Review Comments
|
|
|
|
### codex-rs/core/src/protocol.rs
|
|
|
|
- Created: 2025-07-26 03:09:43 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2232408024
|
|
|
|
```diff
|
|
@@ -338,6 +338,34 @@ pub enum EventMsg {
|
|
ShutdownComplete,
|
|
}
|
|
|
|
+impl fmt::Display for EventMsg {
|
|
```
|
|
|
|
> Please use strum macros instead and use lowercase?
|
|
|
|
- Created: 2025-07-27 03:03:59 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233616677
|
|
|
|
```diff
|
|
@@ -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")]
|
|
```
|
|
|
|
> I misspoke, I think this should not be plain `lowercase`, but likely one of `snake_case` or `camelCase`, whatever we decide. While Rust prefers `snake_case`, MCP prefers `camelCase`, so perhaps we should adopt that here since the primary serialization use case is MCP?
|
|
|
|
### codex-rs/mcp-server/src/outgoing_message.rs
|
|
|
|
- Created: 2025-07-27 03:04:47 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233616831
|
|
|
|
```diff
|
|
@@ -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.
|
|
```
|
|
|
|
> I believe this is a net-new set of notifications. The old ones all have `codex/event` as the method whereas the new ones use the specific notification type. It should be fine to disambiguate.
|
|
|
|
- Created: 2025-07-27 03:05:20 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233617072
|
|
|
|
```diff
|
|
@@ -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<serde_json::Value>,
|
|
```
|
|
|
|
> Though we also plan to change the `params` in the new schema, but that is not obvious from this general datatype...
|
|
|
|
- Created: 2025-07-27 03:05:31 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233617124
|
|
|
|
```diff
|
|
@@ -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<serde_json::Value>,
|
|
+ ) {
|
|
+ let outgoing_message = OutgoingMessage::Notification(OutgoingNotification {
|
|
+ method: event.msg.to_string(),
|
|
params,
|
|
});
|
|
let _ = self.sender.send(outgoing_message).await;
|
|
}
|
|
-
|
|
```
|
|
|
|
> restore?
|
|
|
|
### codex-rs/mcp-server/tests/common/mcp_process.rs
|
|
|
|
- Created: 2025-07-27 01:36:44 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233580571
|
|
|
|
```diff
|
|
@@ -270,27 +270,49 @@ impl McpProcess {
|
|
pub async fn read_stream_until_configured_response_message(
|
|
&mut self,
|
|
) -> anyhow::Result<String> {
|
|
+ let mut sid_old: Option<String> = None;
|
|
+ let mut sid_new: Option<String> = 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" {
|
|
```
|
|
|
|
> It is normal to do this if you use serde or strum macros or something. You can also use these to override the name for individual variants.
|
|
|
|
- Created: 2025-07-27 03:08:24 UTC | Link: https://github.com/openai/codex/pull/1684#discussion_r2233618044
|
|
|
|
```diff
|
|
@@ -270,27 +270,49 @@ impl McpProcess {
|
|
pub async fn read_stream_until_configured_response_message(
|
|
&mut self,
|
|
) -> anyhow::Result<String> {
|
|
+ let mut sid_old: Option<String> = None;
|
|
+ let mut sid_new: Option<String> = 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" {
|
|
```
|
|
|
|
> Note we were also doing this before: it's just that it was on our custom `type` field instead of the `method` field.
|
|
>
|
|
> In practice, if you have good integration tests, it should catch the fact if someone renames the variant without thinking about the serialization implications. It's that or you end up with a lot of copy paste where ever `rename` line matches the variant name. |