use std::process::Stdio; use std::time::Duration; use anyhow::Context; use anyhow::Result; use tokio::io::AsyncBufReadExt; use tokio::io::BufReader; use tokio::process::Child; use tokio::process::Command; use tokio::time::timeout; pub(crate) struct HostHarness { pub(crate) _child: Child, pub(crate) endpoint: String, } impl HostHarness { pub(crate) async fn start(listen_url: &str) -> Result { let host_program = codex_utils_cargo_bin::cargo_bin("codex-code-mode-host")?; let mut child = Command::new(host_program) .args(["--listen", listen_url]) .stdin(Stdio::null()) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .kill_on_drop(/*kill_on_drop*/ true) .spawn() .context("failed to start code-mode host")?; let stdout = child .stdout .take() .context("code-mode host stdout was not captured")?; let endpoint = timeout( Duration::from_secs(/*secs*/ 10), BufReader::new(stdout).lines().next_line(), ) .await .context("timed out waiting for code-mode host endpoint")?? .context("code-mode host exited before publishing its endpoint")?; let expected_scheme = if listen_url.starts_with("grpc://") { "http" } else { "ws" }; if !endpoint.starts_with(&format!("{expected_scheme}://127.0.0.1:")) { anyhow::bail!("unexpected code-mode host endpoint `{endpoint}`"); } Ok(Self { _child: child, endpoint, }) } }