mirror of
https://github.com/openai/codex.git
synced 2026-06-01 19:02:59 +00:00
Add support for UDS in codex --remote (#22414)
## Why Added support for UDS connections in `codex --remote`. TUI also now connects to local app-server using UDS by default if it is running and set to listen to UDS connection. ## What Changed - Introduced `RemoteAppServerEndpoint` with `WebSocket` and `UnixSocket` variants. - Reused the existing JSON-RPC-over-WebSocket protocol over either a TCP WebSocket stream or a UDS stream. - Updated `codex --remote` to accept `ws://host:port`, `wss://host:port`, `unix://`, and `unix://PATH`. - Kept `--remote-auth-token-env` restricted to `wss://` and loopback `ws://` remotes. - Added a fast TUI startup probe for the default daemon socket, falling back to the embedded app server when the daemon is absent or unresponsive. ## Verification - Manually verified that the updated remote flow works. - Added coverage for UDS remote round trips, WebSocket auth headers, auth-token transport policy, remote address parsing, and missing-daemon fallback. - Ran focused remote test coverage locally.
This commit is contained in:
@@ -722,9 +722,9 @@ struct FeatureToggles {
|
||||
|
||||
#[derive(Debug, Default, Parser, Clone)]
|
||||
struct InteractiveRemoteOptions {
|
||||
/// Connect the TUI to a remote app server websocket endpoint.
|
||||
/// Connect the TUI to a remote app server endpoint.
|
||||
///
|
||||
/// Accepted forms: `ws://host:port` or `wss://host:port`.
|
||||
/// Accepted forms: `ws://host:port`, `wss://host:port`, `unix://`, or `unix://PATH`.
|
||||
#[arg(long = "remote", value_name = "ADDR")]
|
||||
remote: Option<String>,
|
||||
|
||||
@@ -1709,27 +1709,39 @@ async fn run_interactive_tui(
|
||||
}
|
||||
}
|
||||
|
||||
let normalized_remote = remote
|
||||
let mut remote_endpoint = remote
|
||||
.as_deref()
|
||||
.map(codex_tui::normalize_remote_addr)
|
||||
.map(codex_tui::resolve_remote_addr)
|
||||
.transpose()
|
||||
.map_err(std::io::Error::other)?;
|
||||
if remote_auth_token_env.is_some() && normalized_remote.is_none() {
|
||||
return Ok(AppExitInfo::fatal(
|
||||
"`--remote-auth-token-env` requires `--remote`.",
|
||||
));
|
||||
if let Some(remote_auth_token_env) = remote_auth_token_env {
|
||||
let Some(endpoint) = remote_endpoint.as_mut() else {
|
||||
return Ok(AppExitInfo::fatal(
|
||||
"`--remote-auth-token-env` requires `--remote`.",
|
||||
));
|
||||
};
|
||||
if !codex_tui::remote_addr_supports_auth_token(endpoint) {
|
||||
return Ok(AppExitInfo::fatal(
|
||||
"`--remote-auth-token-env` requires a `wss://` or loopback `ws://` remote.",
|
||||
));
|
||||
}
|
||||
let auth_token = read_remote_auth_token_from_env_var(&remote_auth_token_env)
|
||||
.map_err(std::io::Error::other)?;
|
||||
let codex_tui::RemoteAppServerEndpoint::WebSocket {
|
||||
auth_token: slot, ..
|
||||
} = endpoint
|
||||
else {
|
||||
return Ok(AppExitInfo::fatal(
|
||||
"`--remote-auth-token-env` requires a `wss://` or loopback `ws://` remote.",
|
||||
));
|
||||
};
|
||||
*slot = Some(auth_token);
|
||||
}
|
||||
let remote_auth_token = remote_auth_token_env
|
||||
.as_deref()
|
||||
.map(read_remote_auth_token_from_env_var)
|
||||
.transpose()
|
||||
.map_err(std::io::Error::other)?;
|
||||
codex_tui::run_main(
|
||||
interactive,
|
||||
arg0_paths,
|
||||
codex_config::LoaderOverrides::default(),
|
||||
normalized_remote,
|
||||
remote_auth_token,
|
||||
remote_endpoint,
|
||||
)
|
||||
.await
|
||||
}
|
||||
@@ -2411,13 +2423,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reject_remote_flag_for_remote_control() {
|
||||
let cli = MultitoolCli::try_parse_from([
|
||||
"codex",
|
||||
"--remote",
|
||||
"ws://127.0.0.1:1234",
|
||||
"remote-control",
|
||||
])
|
||||
.expect("parse");
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--remote", "unix://", "remote-control"])
|
||||
.expect("parse");
|
||||
assert_matches!(cli.subcommand, Some(Subcommand::RemoteControl));
|
||||
|
||||
let err = reject_remote_mode_for_subcommand(
|
||||
@@ -2432,9 +2439,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn remote_flag_parses_for_interactive_root() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--remote", "ws://127.0.0.1:4500"])
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "--remote", "unix://codex.sock"])
|
||||
.expect("parse");
|
||||
assert_eq!(cli.remote.remote.as_deref(), Some("ws://127.0.0.1:4500"));
|
||||
assert_eq!(cli.remote.remote.as_deref(), Some("unix://codex.sock"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2456,14 +2463,14 @@ mod tests {
|
||||
#[test]
|
||||
fn remote_flag_parses_for_resume_subcommand() {
|
||||
let cli =
|
||||
MultitoolCli::try_parse_from(["codex", "resume", "--remote", "ws://127.0.0.1:4500"])
|
||||
MultitoolCli::try_parse_from(["codex", "resume", "--remote", "unix://codex.sock"])
|
||||
.expect("parse");
|
||||
let Subcommand::Resume(ResumeCommand { remote, .. }) =
|
||||
cli.subcommand.expect("resume present")
|
||||
else {
|
||||
panic!("expected resume subcommand");
|
||||
};
|
||||
assert_eq!(remote.remote.as_deref(), Some("ws://127.0.0.1:4500"));
|
||||
assert_eq!(remote.remote.as_deref(), Some("unix://codex.sock"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user