mirror of
https://github.com/openai/codex.git
synced 2026-09-11 20:36:49 +00:00
## What changed - Add `RealtimeWebrtcSession` and cloneable handles for startup, answer negotiation, audio controls, level meters, and error reporting. - Open devices after answer negotiation and apply the latest controls before enabling audio. Preserve subsequent control transitions in order and close the session if the command queue fills. - Cancel pending work on explicit close, external cancellation, or final handle drop, with a shared runtime keeping helper reaping alive. - Add audio-state polling to consume microphone and speaker peaks and detect helper, device, or connection failures. - Check packaged helper/runtime availability and initialize build information in the standalone TUI for the helper handshake. ## Testing Add unit and helper-process integration tests covering startup control ordering, queue overflow, cancellation, helper reaping and loss, package availability, SDP debug redaction, and peak accumulation, clamping, and consumption. GitOrigin-RevId: 458cd80353697f7411cee785f380d1d53aef8416
110 lines
3.8 KiB
Rust
110 lines
3.8 KiB
Rust
//! Exercises the public session actor against a separate framed-IPC helper, without native audio.
|
|
|
|
mod common;
|
|
|
|
use anyhow::Result;
|
|
use codex_realtime_webrtc::AudioControls;
|
|
use codex_realtime_webrtc::RealtimeWebrtcSession;
|
|
use futures::future::AbortHandle;
|
|
use pretty_assertions::assert_eq;
|
|
use std::fs;
|
|
use std::thread;
|
|
|
|
#[test]
|
|
fn startup_controls_meters_and_helper_loss() -> Result<()> {
|
|
let Some(root) = common::package("startup_controls_meters_and_helper_loss")? else {
|
|
return Ok(());
|
|
};
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
assert!(!RealtimeWebrtcSession::is_supported());
|
|
let libraries = root.join("codex-resources/voice/lib");
|
|
fs::create_dir_all(&libraries)?;
|
|
fs::write(libraries.join("libgstreamer-1.0.so.0"), b"fixture runtime")?;
|
|
assert!(RealtimeWebrtcSession::is_supported());
|
|
}
|
|
let (_abort, registration) = AbortHandle::new_pair();
|
|
let started = RealtimeWebrtcSession::start(registration)?;
|
|
assert_eq!(started.offer_sdp, "synthetic-offer");
|
|
let handle = started.handle;
|
|
handle.set_microphone_muted(/*muted*/ true)?;
|
|
let answer_handle = handle.clone();
|
|
let answer = thread::spawn(move || answer_handle.apply_answer_sdp("synthetic-answer".into()));
|
|
common::wait_for(|| root.join("answer").exists())?;
|
|
handle.set_speaker_suppressed(/*suppressed*/ true);
|
|
fs::write(root.join("release"), [])?;
|
|
answer.join().expect("answer thread")?;
|
|
let initial: Vec<AudioControls> = serde_json::from_slice(&fs::read(root.join("controls"))?)?;
|
|
assert_eq!(
|
|
initial,
|
|
vec![AudioControls {
|
|
microphone_muted: true,
|
|
speaker_suppressed: true
|
|
}]
|
|
);
|
|
handle.set_microphone_muted(/*muted*/ false)?;
|
|
handle.set_speaker_suppressed(/*suppressed*/ false);
|
|
let expected = serde_json::to_vec(&vec![
|
|
AudioControls {
|
|
microphone_muted: true,
|
|
speaker_suppressed: true,
|
|
},
|
|
AudioControls {
|
|
microphone_muted: false,
|
|
speaker_suppressed: true,
|
|
},
|
|
AudioControls {
|
|
microphone_muted: false,
|
|
speaker_suppressed: false,
|
|
},
|
|
])?;
|
|
common::wait_for(|| fs::read(root.join("controls")).is_ok_and(|data| data == expected))?;
|
|
common::wait_for(|| handle.take_microphone_peak() == 123)?;
|
|
common::wait_for(|| handle.take_speaker_peak() == 456)?;
|
|
fs::write(root.join("exit"), [])?;
|
|
let mut error = None;
|
|
common::wait_for(|| {
|
|
error = handle.take_error();
|
|
error.is_some()
|
|
})?;
|
|
assert_eq!(error.as_deref(), Some("Voice helper stopped unexpectedly."));
|
|
assert_eq!(handle.take_error(), None);
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn external_cancellation_interrupts_startup() -> Result<()> {
|
|
let Some(root) = common::package("external_cancellation_interrupts_startup")? else {
|
|
return Ok(());
|
|
};
|
|
fs::write(root.join("hold-initialization"), [])?;
|
|
let (abort, registration) = AbortHandle::new_pair();
|
|
let (result, received) = std::sync::mpsc::sync_channel(/*bound*/ 1);
|
|
let startup = thread::spawn(move || {
|
|
let _ = result.send(RealtimeWebrtcSession::start(registration));
|
|
});
|
|
common::wait_for(|| root.join("initializing").exists())?;
|
|
abort.abort();
|
|
assert!(
|
|
received
|
|
.recv_timeout(std::time::Duration::from_secs(/*secs*/ 2))?
|
|
.is_err()
|
|
);
|
|
startup.join().expect("startup thread");
|
|
#[cfg(unix)]
|
|
common::wait_for_helper_reaped(&root)?;
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(unix)]
|
|
#[test]
|
|
fn last_owner_drop_reaps_helper() -> Result<()> {
|
|
let Some(root) = common::package("last_owner_drop_reaps_helper")? else {
|
|
return Ok(());
|
|
};
|
|
let (_abort, registration) = AbortHandle::new_pair();
|
|
let started = RealtimeWebrtcSession::start(registration)?;
|
|
drop(started);
|
|
common::wait_for_helper_reaped(&root)
|
|
}
|