mirror of
https://github.com/openai/codex.git
synced 2026-09-06 15:29:32 +00:00
## Summary - add validated protocol-version, capability, and session identifier types - define explicit `ClientToHost` and `HostToClient` JSON envelopes for connection negotiation and session open/close acknowledgements - reject invalid states and unknown fields during decoding, with explicit wire-format and round-trip coverage ## Why This establishes the transport-neutral encoding shape needed to build and test the new code-mode host incrementally. Cell, tool callback, and failure-domain messages are intentionally deferred until their actors and behavior tests establish the required semantics. This is additive protocol scaffolding and does not change the current production code-mode implementation. ## Validation
143 lines
4.1 KiB
Rust
143 lines
4.1 KiB
Rust
use std::fmt;
|
|
|
|
use serde::Deserialize;
|
|
use serde::Serialize;
|
|
|
|
use super::Capability;
|
|
use super::CapabilitySet;
|
|
use super::HandshakeRejectReason;
|
|
use super::ProtocolVersion;
|
|
use super::SessionId;
|
|
use super::SupportedProtocolVersions;
|
|
|
|
#[derive(Clone, Debug, PartialEq, Serialize)]
|
|
#[serde(rename_all = "camelCase")]
|
|
pub struct ClientHello {
|
|
supported_versions: SupportedProtocolVersions,
|
|
required_capabilities: CapabilitySet,
|
|
optional_capabilities: CapabilitySet,
|
|
}
|
|
|
|
impl ClientHello {
|
|
pub fn new(
|
|
supported_versions: SupportedProtocolVersions,
|
|
required_capabilities: CapabilitySet,
|
|
optional_capabilities: CapabilitySet,
|
|
) -> Result<Self, ClientHelloError> {
|
|
if let Some(capability) = required_capabilities
|
|
.iter()
|
|
.find(|capability| optional_capabilities.contains(capability))
|
|
{
|
|
return Err(ClientHelloError::OverlappingCapability(capability.clone()));
|
|
}
|
|
Ok(Self {
|
|
supported_versions,
|
|
required_capabilities,
|
|
optional_capabilities,
|
|
})
|
|
}
|
|
|
|
pub fn supported_versions(&self) -> &SupportedProtocolVersions {
|
|
&self.supported_versions
|
|
}
|
|
|
|
pub fn required_capabilities(&self) -> &CapabilitySet {
|
|
&self.required_capabilities
|
|
}
|
|
|
|
pub fn optional_capabilities(&self) -> &CapabilitySet {
|
|
&self.optional_capabilities
|
|
}
|
|
}
|
|
|
|
#[derive(Deserialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
struct ClientHelloWire {
|
|
supported_versions: SupportedProtocolVersions,
|
|
required_capabilities: CapabilitySet,
|
|
optional_capabilities: CapabilitySet,
|
|
}
|
|
|
|
impl<'de> Deserialize<'de> for ClientHello {
|
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
where
|
|
D: serde::Deserializer<'de>,
|
|
{
|
|
let wire = ClientHelloWire::deserialize(deserializer)?;
|
|
Self::new(
|
|
wire.supported_versions,
|
|
wire.required_capabilities,
|
|
wire.optional_capabilities,
|
|
)
|
|
.map_err(serde::de::Error::custom)
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
|
pub enum ClientHelloError {
|
|
OverlappingCapability(Capability),
|
|
}
|
|
|
|
impl fmt::Display for ClientHelloError {
|
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
match self {
|
|
Self::OverlappingCapability(capability) => write!(
|
|
formatter,
|
|
"capability `{capability}` cannot be both required and optional"
|
|
),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl std::error::Error for ClientHelloError {}
|
|
|
|
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
|
|
#[serde(deny_unknown_fields, rename_all = "camelCase")]
|
|
pub struct HostHello {
|
|
selected_version: ProtocolVersion,
|
|
capabilities: CapabilitySet,
|
|
}
|
|
|
|
impl HostHello {
|
|
pub fn new(selected_version: ProtocolVersion, capabilities: CapabilitySet) -> Self {
|
|
Self {
|
|
selected_version,
|
|
capabilities,
|
|
}
|
|
}
|
|
|
|
pub fn selected_version(&self) -> ProtocolVersion {
|
|
self.selected_version
|
|
}
|
|
|
|
pub fn capabilities(&self) -> &CapabilitySet {
|
|
&self.capabilities
|
|
}
|
|
}
|
|
|
|
/// Messages sent from a client to the code-mode host.
|
|
#[derive(Clone, 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 },
|
|
}
|
|
|
|
/// Messages sent from the code-mode host to a client.
|
|
#[derive(Clone, 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 = "session/ready")]
|
|
SessionReady { session_id: SessionId },
|
|
#[serde(rename = "session/closed")]
|
|
SessionClosed { session_id: SessionId },
|
|
}
|