mirror of
https://github.com/openai/codex.git
synced 2026-09-20 12:47:38 +00:00
Move local child-process spawning into codex-utils-pty (#46659)
## What changed Expose the shared `Child` type and `spawn_child` function from `codex-utils-pty`, and update the MCP stdio transport to use them. Move the existing macOS spawning implementation and its tests into the utility crate, preserving platform-specific spawning, piped stdio, and kill-on-drop behavior. GitOrigin-RevId: 26c2fb2f22cdba907aa9d18cab7dc8c4ac1c909e
This commit is contained in:
1
codex-rs/Cargo.lock
generated
1
codex-rs/Cargo.lock
generated
@@ -5115,6 +5115,7 @@ dependencies = [
|
||||
"portable-pty",
|
||||
"pretty_assertions",
|
||||
"shared_library",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
@@ -12,7 +12,6 @@ mod http_client_adapter;
|
||||
mod http_client_redirect;
|
||||
mod http_headers;
|
||||
mod in_process_transport;
|
||||
mod local_child;
|
||||
mod local_stdio_transport;
|
||||
mod logging_client_handler;
|
||||
mod oauth;
|
||||
|
||||
@@ -18,12 +18,11 @@ use tokio::process::ChildStdout;
|
||||
use tokio::process::Command;
|
||||
|
||||
use crate::bounded_stdio_transport::BoundedStdioTransport;
|
||||
use crate::local_child;
|
||||
use crate::local_child::LocalChild;
|
||||
use crate::protocol_mode::McpProtocolMode;
|
||||
use codex_utils_pty::Child;
|
||||
|
||||
pub(super) struct LocalStdioTransport {
|
||||
child: LocalChild,
|
||||
child: Child,
|
||||
transport: StdioTransport,
|
||||
}
|
||||
|
||||
@@ -40,7 +39,7 @@ impl LocalStdioTransport {
|
||||
program_name: String,
|
||||
protocol_mode: McpProtocolMode,
|
||||
) -> io::Result<(Self, Option<ChildStderr>)> {
|
||||
let mut child = local_child::spawn(command)?;
|
||||
let mut child = codex_utils_pty::spawn_child(command)?;
|
||||
let stdin = child
|
||||
.stdin
|
||||
.take()
|
||||
|
||||
@@ -10,9 +10,10 @@ workspace = true
|
||||
[dependencies]
|
||||
anyhow = { workspace = true }
|
||||
portable-pty = { workspace = true }
|
||||
tokio = { workspace = true, features = ["io-util", "macros", "net", "process", "rt-multi-thread", "sync", "time"] }
|
||||
tokio = { workspace = true, features = ["io-util", "macros", "net", "process", "rt-multi-thread", "signal", "sync", "time"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = { workspace = true }
|
||||
pretty_assertions = { workspace = true }
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
//! Uniform child-process API for local MCP servers, with platform-specific spawning.
|
||||
//! Uniform child-process API for local subprocesses, with platform-specific spawning.
|
||||
//!
|
||||
//! Commands must use the launcher's cleared environment, process group, and default
|
||||
//! argv[0]. Both implementations expose Tokio stdio handles and kill on drop.
|
||||
@@ -9,15 +9,15 @@ use std::process::Stdio;
|
||||
use tokio::process::Command;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
#[path = "macos_stdio.rs"]
|
||||
#[path = "macos_child.rs"]
|
||||
mod macos;
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub(super) use macos::LocalChild;
|
||||
pub use macos::Child;
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
pub(super) use tokio::process::Child as LocalChild;
|
||||
pub use tokio::process::Child;
|
||||
|
||||
pub(super) fn spawn(mut command: Command) -> io::Result<LocalChild> {
|
||||
pub fn spawn(mut command: Command) -> io::Result<Child> {
|
||||
command
|
||||
.kill_on_drop(true)
|
||||
.stdin(Stdio::piped())
|
||||
@@ -25,7 +25,7 @@ pub(super) fn spawn(mut command: Command) -> io::Result<LocalChild> {
|
||||
.stderr(Stdio::piped());
|
||||
#[cfg(target_os = "macos")]
|
||||
{
|
||||
LocalChild::spawn(command)
|
||||
Child::spawn(command)
|
||||
}
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
@@ -1,3 +1,6 @@
|
||||
mod child;
|
||||
pub use child::Child;
|
||||
pub use child::spawn as spawn_child;
|
||||
pub mod pipe;
|
||||
mod process;
|
||||
pub mod process_group;
|
||||
|
||||
@@ -19,7 +19,7 @@ use std::path::Path;
|
||||
use std::process::ExitStatus;
|
||||
use std::ptr;
|
||||
|
||||
use tokio::process::Child;
|
||||
use tokio::process::Child as TokioChild;
|
||||
use tokio::process::ChildStderr;
|
||||
use tokio::process::ChildStdin;
|
||||
use tokio::process::ChildStdout;
|
||||
@@ -29,19 +29,19 @@ use tokio::signal::unix::SignalKind;
|
||||
use tokio::signal::unix::signal;
|
||||
|
||||
/// Matches Tokio's child API while keeping native spawning private to macOS.
|
||||
pub(crate) struct LocalChild {
|
||||
pub struct Child {
|
||||
inner: ChildKind,
|
||||
pub(crate) stdin: Option<ChildStdin>,
|
||||
pub(crate) stdout: Option<ChildStdout>,
|
||||
pub(crate) stderr: Option<ChildStderr>,
|
||||
pub stdin: Option<ChildStdin>,
|
||||
pub stdout: Option<ChildStdout>,
|
||||
pub stderr: Option<ChildStderr>,
|
||||
}
|
||||
|
||||
enum ChildKind {
|
||||
Tokio(Child),
|
||||
Tokio(TokioChild),
|
||||
Native(NativeChild),
|
||||
}
|
||||
|
||||
impl LocalChild {
|
||||
impl Child {
|
||||
/// Uses native spawning for relative paths and bare names, retaining Tokio's
|
||||
/// fallback for unsuccessful PATH searches and executable text without a shebang.
|
||||
pub(super) fn spawn(mut command: Command) -> io::Result<Self> {
|
||||
@@ -66,14 +66,14 @@ impl LocalChild {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn id(&self) -> Option<u32> {
|
||||
pub fn id(&self) -> Option<u32> {
|
||||
match &self.inner {
|
||||
ChildKind::Tokio(child) => child.id(),
|
||||
ChildKind::Native(child) => child.id(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
pub async fn wait(&mut self) -> io::Result<ExitStatus> {
|
||||
self.stdin.take();
|
||||
match &mut self.inner {
|
||||
ChildKind::Tokio(child) => child.wait().await,
|
||||
@@ -81,7 +81,7 @@ impl LocalChild {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn kill(&mut self) -> io::Result<()> {
|
||||
pub async fn kill(&mut self) -> io::Result<()> {
|
||||
self.stdin.take();
|
||||
match &mut self.inner {
|
||||
ChildKind::Tokio(child) => child.kill().await,
|
||||
@@ -411,5 +411,5 @@ impl Drop for Attributes {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[path = "macos_stdio_tests.rs"]
|
||||
#[path = "macos_child_tests.rs"]
|
||||
mod tests;
|
||||
@@ -11,7 +11,7 @@ use tokio::io::AsyncReadExt;
|
||||
use tokio::io::AsyncWriteExt;
|
||||
|
||||
async fn native_output(command: Command) -> anyhow::Result<std::process::Output> {
|
||||
let mut child = crate::local_child::spawn(command)?;
|
||||
let mut child = crate::spawn_child(command)?;
|
||||
assert!(matches!(child.inner, ChildKind::Native(_)));
|
||||
drop(child.stdin.take());
|
||||
let mut stdout = child.stdout.take().expect("piped stdout");
|
||||
@@ -213,7 +213,7 @@ async fn launch_failures_preserve_os_errors() -> anyhow::Result<()> {
|
||||
.env_clear()
|
||||
.env("PATH", root.path())
|
||||
.current_dir(cwd);
|
||||
let error = crate::local_child::spawn(command)
|
||||
let error = crate::spawn_child(command)
|
||||
.err()
|
||||
.expect("spawn should fail");
|
||||
assert_eq!(error.raw_os_error(), Some(errno));
|
||||
@@ -233,7 +233,7 @@ async fn executable_text_without_shebang_retains_command_fallback() -> anyhow::R
|
||||
.current_dir(root.path())
|
||||
.env_clear()
|
||||
.env("PATH", ".");
|
||||
let mut child = crate::local_child::spawn(command)?;
|
||||
let mut child = crate::spawn_child(command)?;
|
||||
let mut output = Vec::new();
|
||||
child
|
||||
.stdout
|
||||
Reference in New Issue
Block a user