mirror of
https://github.com/openai/codex.git
synced 2026-11-13 06:45:07 +00:00
Add the voice helper lifecycle foundation (#41897)
## What changed - Add `codex-realtime-webrtc` with a length-prefixed, 256-byte JSON control protocol for `hello`, `ready`, `close`, and `closed` messages. - Add a hardened `codex-voice-host` binary that validates protocol and build compatibility, handles orderly shutdown and parent pipe loss, and exposes its build commit. - Keep this initial foundation limited to process lifecycle management; it does not open audio devices, load native plugins, negotiate WebRTC, or enable voice in the TUI. ## Testing - Exercise the helper as a subprocess, covering handshake, explicit close, parent pipe loss, malformed frames, incompatible builds, and invalid message order. GitOrigin-RevId: 6d8d7dd7ba5c4886879882d79314d235f6ab7cf4
This commit is contained in:
1 parent
65237aeca0
commit
d60560f14e
11 files changed
+358
No files matched your search
@@ -0,0 +1,7 @@
|
||||
mod protocol;
|
||||
|
||||
pub use protocol::MAX_FRAME_BYTES;
|
||||
pub use protocol::Message;
|
||||
pub use protocol::decode_frame;
|
||||
pub use protocol::encode_frame;
|
||||
pub use protocol::read_message;
|
||||
@@ -0,0 +1,63 @@
|
||||
//! Bounded, same-build lifecycle protocol; this stage carries no audio or credentials.
|
||||
|
||||
use std::io;
|
||||
use std::io::Read;
|
||||
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
|
||||
pub const MAX_FRAME_BYTES: usize = 256;
|
||||
|
||||
#[derive(Debug, Deserialize, PartialEq, Serialize)]
|
||||
#[serde(
|
||||
tag = "type",
|
||||
rename_all = "camelCase",
|
||||
rename_all_fields = "camelCase",
|
||||
deny_unknown_fields
|
||||
)]
|
||||
pub enum Message {
|
||||
Hello { protocol: u32, build_commit: String },
|
||||
Ready {},
|
||||
Close {},
|
||||
Closed {},
|
||||
}
|
||||
|
||||
pub fn encode_frame(message: &Message) -> io::Result<Vec<u8>> {
|
||||
let payload = serde_json::to_vec(message)?;
|
||||
if payload.len() > MAX_FRAME_BYTES {
|
||||
return Err(io::Error::other("voice frame exceeds limit"));
|
||||
}
|
||||
let mut frame = (payload.len() as u32).to_be_bytes().to_vec();
|
||||
frame.extend(payload);
|
||||
Ok(frame)
|
||||
}
|
||||
|
||||
pub fn decode_frame(frame: &[u8]) -> io::Result<Option<Message>> {
|
||||
let [a, b, c, d, ..] = frame else {
|
||||
return Ok(None);
|
||||
};
|
||||
let length = u32::from_be_bytes([*a, *b, *c, *d]) as usize;
|
||||
if length > MAX_FRAME_BYTES || frame.len() > length + 4 {
|
||||
return Err(io::Error::other("invalid voice frame length"));
|
||||
}
|
||||
frame
|
||||
.get(4..length + 4)
|
||||
.map(serde_json::from_slice)
|
||||
.transpose()
|
||||
.map_err(|_| io::Error::other("invalid voice frame"))
|
||||
}
|
||||
|
||||
pub fn read_message(reader: &mut impl Read) -> io::Result<Option<Message>> {
|
||||
let mut header = [0; 4];
|
||||
match reader.read_exact(&mut header[..1]) {
|
||||
Err(error) if error.kind() == io::ErrorKind::UnexpectedEof => return Ok(None),
|
||||
result => result?,
|
||||
}
|
||||
reader.read_exact(&mut header[1..])?;
|
||||
decode_frame(&header)?;
|
||||
let length = u32::from_be_bytes(header) as usize;
|
||||
let mut frame = header.to_vec();
|
||||
frame.resize(length + 4, /*value*/ 0);
|
||||
reader.read_exact(&mut frame[4..])?;
|
||||
decode_frame(&frame)
|
||||
}
|
||||
Reference in new issue
Block a user