code-mode: define process host wire protocol (#29804)

## Why

The process-owned code mode implementation needs an explicit, bounded
wire contract before either side depends on it. Keeping framing and
message semantics in `codex-code-mode-protocol` gives the client and
sidecar one shared source of truth and makes compatibility failures
detectable during connection setup.

## What changed

- adds a versioned client/host handshake with required and optional
capabilities
- defines operation requests and responses for session lifecycle and
cell control
- defines reverse delegate request, response, cancellation, and
cell-closure messages
- adds a four-byte little-endian length-prefixed JSON codec with a hard
frame cap
- rejects malformed frames, unknown fields, invalid identifiers, and
unsupported protocol states
- locks the wire representation down with explicit JSON round-trip tests

## Testing

- `just test -p codex-code-mode-protocol`

## Stack

Part 1 of 6. Followed by
[#29805](https://github.com/openai/codex/pull/29805).
This commit is contained in:
Channing Conger
2026-06-24 20:03:22 -07:00
committed by GitHub
parent f8937b7d86
commit b3e1c33776
7 changed files with 1424 additions and 99 deletions
+121 -6
View File
@@ -2,13 +2,22 @@ use std::fmt;
use serde::Deserialize;
use serde::Serialize;
use serde_json::Value as JsonValue;
use super::Capability;
use super::CapabilitySet;
use super::DelegateRequestId;
use super::HandshakeRejectReason;
use super::ProtocolVersion;
use super::RequestId;
use super::SessionId;
use super::SupportedProtocolVersions;
use super::WireCellId;
use super::WireExecuteRequest;
use super::WireNestedToolCall;
use super::WireRuntimeResponse;
use super::WireWaitOutcome;
use super::WireWaitRequest;
#[derive(Clone, Debug, PartialEq, Serialize)]
#[serde(rename_all = "camelCase")]
@@ -116,27 +125,133 @@ impl HostHello {
}
/// Messages sent from a client to the code-mode host.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "type", rename_all_fields = "camelCase")]
pub enum ClientToHost {
#[serde(rename = "connection/hello")]
ClientHello(ClientHello),
#[serde(rename = "session/open")]
OpenSession { session_id: SessionId },
#[serde(rename = "session/close")]
CloseSession { session_id: SessionId },
#[serde(rename = "operation/request")]
Request { id: RequestId, request: HostRequest },
#[serde(rename = "delegate/response")]
DelegateResponse {
id: DelegateRequestId,
result: WireResult<DelegateResponse>,
},
}
/// Messages sent from the code-mode host to a client.
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "type", rename_all_fields = "camelCase")]
pub enum HostToClient {
#[serde(rename = "connection/ready")]
HostHello(HostHello),
#[serde(rename = "connection/rejected")]
HandshakeRejected { reason: HandshakeRejectReason },
#[serde(rename = "operation/response")]
Response {
id: RequestId,
result: WireResult<HostResponse>,
},
#[serde(rename = "execute/initialResponse")]
InitialResponse {
id: RequestId,
result: WireResult<WireRuntimeResponse>,
},
#[serde(rename = "delegate/request")]
DelegateRequest {
id: DelegateRequestId,
session_id: SessionId,
request: DelegateRequest,
},
#[serde(rename = "delegate/cancel")]
CancelDelegateRequest { id: DelegateRequestId },
#[serde(rename = "cell/closed")]
CellClosed {
session_id: SessionId,
cell_id: WireCellId,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "method", rename_all_fields = "camelCase")]
pub enum HostRequest {
#[serde(rename = "session/open")]
OpenSession { session_id: SessionId },
#[serde(rename = "session/execute")]
Execute {
session_id: SessionId,
request: WireExecuteRequest,
},
#[serde(rename = "session/wait")]
Wait {
session_id: SessionId,
request: WireWaitRequest,
},
#[serde(rename = "session/terminate")]
Terminate {
session_id: SessionId,
cell_id: WireCellId,
},
#[serde(rename = "session/shutdown")]
ShutdownSession { session_id: SessionId },
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "type", rename_all_fields = "camelCase")]
pub enum HostResponse {
#[serde(rename = "session/ready")]
SessionReady { session_id: SessionId },
#[serde(rename = "execution/started")]
ExecutionStarted { cell_id: WireCellId },
#[serde(rename = "wait/completed")]
WaitCompleted { outcome: WireWaitOutcome },
#[serde(rename = "session/closed")]
SessionClosed { session_id: SessionId },
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "type", rename_all_fields = "camelCase")]
pub enum DelegateRequest {
#[serde(rename = "tool/invoke")]
InvokeTool { invocation: WireNestedToolCall },
#[serde(rename = "notification/send")]
Notify {
call_id: String,
cell_id: WireCellId,
text: String,
},
}
#[derive(Clone, Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "type", rename_all_fields = "camelCase")]
pub enum DelegateResponse {
#[serde(rename = "tool/result")]
ToolResult { result: JsonValue },
#[serde(rename = "notification/delivered")]
NotificationDelivered,
}
#[derive(Debug, Deserialize, PartialEq, Serialize)]
#[serde(deny_unknown_fields, tag = "status", rename_all_fields = "camelCase")]
pub enum WireResult<T> {
#[serde(rename = "ok")]
Ok { value: T },
#[serde(rename = "error")]
Err { message: String },
}
impl<T> WireResult<T> {
pub fn from_result(result: Result<T, String>) -> Self {
match result {
Ok(value) => Self::Ok { value },
Err(message) => Self::Err { message },
}
}
pub fn into_result(self) -> Result<T, String> {
match self {
Self::Ok { value } => Ok(value),
Self::Err { message } => Err(message),
}
}
}