Support remote code-mode hosts in app-server (#35098)

## What changed

- Add `--code-mode-host ws://...` and `wss://...` support to `codex app-server`, gated by the `code_mode_host` feature. When omitted, app-server continues to start a local host.
- Share one remote WebSocket connection across the process's threads, using the configured HTTP client's proxy and TLS policy and preserving the existing framed host protocol.
- Reject invalid host URLs, bound WebSocket frame sizes, close connections cleanly, and return an error when a connection exceeds 1,024 pending delegate calls without disconnecting it.

## Testing

- Cover CLI validation, WebSocket protocol execution and shutdown, connection sharing across app-server threads, and delegate-call capacity recovery.

GitOrigin-RevId: 715e82d4d9db1e7e2f91b754a777dcab504e2ae4
This commit is contained in:
Channing Conger authored and copyberry committed 2026-07-24 04:37:01 +00:00
1 parent 0dfa778dae
commit f61b51ddd9
28 files changed
+977 -65

No files matched your search

+49
View File
@@ -516,6 +516,9 @@ struct AppServerCommand {
#[command(subcommand)]
subcommand: Option<AppServerSubcommand>,
#[command(flatten)]
code_mode_host: codex_app_server::AppServerCodeModeHostArgs,
/// Error out when config.toml contains fields that are not recognized by this version of Codex.
#[arg(long = "strict-config", default_value_t = false)]
strict_config: bool,
@@ -1099,6 +1102,7 @@ async fn cli_main(
Some(Subcommand::AppServer(app_server_cli)) => {
let AppServerCommand {
subcommand,
code_mode_host,
strict_config: app_server_strict_config,
listen,
stdio,
@@ -1122,6 +1126,7 @@ async fn cli_main(
};
let auth = auth.try_into_settings()?;
let runtime_options = codex_app_server::AppServerRuntimeOptions {
code_mode_host_transport: code_mode_host.into(),
remote_control_startup_mode: match (remote_control, remote_control_disabled)
{
(true, _) => {
@@ -3817,6 +3822,50 @@ mod tests {
assert!(err.to_string().contains("is empty"));
}
#[test]
fn app_server_code_mode_host_url_parses_independently_of_listen_transport() {
let app_server = app_server_from_args(
[
"codex",
"app-server",
"--code-mode-host",
"wss://example.test/code-mode",
"--listen",
"ws://127.0.0.1:4500",
]
.as_ref(),
);
assert_eq!(
app_server.code_mode_host.code_mode_host,
Some(
url::Url::parse("wss://example.test/code-mode")
.expect("test endpoint should parse")
)
);
assert_eq!(
app_server.listen,
codex_app_server::AppServerTransport::WebSocket {
bind_address: "127.0.0.1:4500".parse().expect("valid socket address"),
}
);
}
#[test]
fn app_server_rejects_invalid_code_mode_host_urls() {
for endpoint in [
"http://127.0.0.1:8765",
"ws://",
"wss://example.test/code-mode#fragment",
] {
let error =
MultitoolCli::try_parse_from(["codex", "app-server", "--code-mode-host", endpoint])
.expect_err("invalid code-mode host endpoint should fail argument parsing");
assert_eq!(error.kind(), clap::error::ErrorKind::ValueValidation);
}
}
#[test]
fn app_server_listen_websocket_url_parses() {
let app_server = app_server_from_args(