mirror of
https://github.com/openai/codex.git
synced 2026-11-15 21:50:46 +00:00
Merge branch 'main' of github.com:openai/codex into dev/mzeng/codex_apps_mcp_declare
# Conflicts: # codex-rs/Cargo.lock # codex-rs/codex-mcp/Cargo.toml # codex-rs/codex-mcp/src/connection_manager.rs # codex-rs/codex-mcp/src/connection_manager_tests.rs # codex-rs/codex-mcp/src/rmcp_client.rs # codex-rs/codex-mcp/src/tools.rs # codex-rs/core/src/connectors_tests.rs # codex-rs/core/src/mcp_tool_exposure_test.rs # codex-rs/core/src/tools/handlers/tool_search.rs # codex-rs/core/src/tools/spec_tests.rs
This commit is contained in:
@@ -12,6 +12,7 @@ path = "src/main.rs"
|
||||
[lib]
|
||||
name = "codex_cli"
|
||||
path = "src/lib.rs"
|
||||
doctest = false
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
@@ -21,6 +22,7 @@ anyhow = { workspace = true }
|
||||
clap = { workspace = true, features = ["derive"] }
|
||||
clap_complete = { workspace = true }
|
||||
codex-app-server = { workspace = true }
|
||||
codex-app-server-daemon = { workspace = true }
|
||||
codex-app-server-protocol = { workspace = true }
|
||||
codex-app-server-test-client = { workspace = true }
|
||||
codex-arg0 = { workspace = true }
|
||||
|
||||
@@ -370,6 +370,11 @@ async fn run_command_under_windows_session(
|
||||
cwd.as_path(),
|
||||
env,
|
||||
None,
|
||||
/*read_roots_override*/ None,
|
||||
/*read_roots_include_platform_defaults*/ false,
|
||||
/*write_roots_override*/ None,
|
||||
/*deny_read_paths_override*/ &[],
|
||||
/*deny_write_paths_override*/ &[],
|
||||
/*tty*/ false,
|
||||
/*stdin_open*/ true,
|
||||
config.permissions.windows_sandbox_private_desktop,
|
||||
@@ -384,6 +389,8 @@ async fn run_command_under_windows_session(
|
||||
cwd.as_path(),
|
||||
env,
|
||||
None,
|
||||
/*additional_deny_read_paths*/ &[],
|
||||
/*additional_deny_write_paths*/ &[],
|
||||
/*tty*/ false,
|
||||
/*stdin_open*/ true,
|
||||
config.permissions.windows_sandbox_private_desktop,
|
||||
|
||||
+197
-107
@@ -3,6 +3,9 @@ use clap::CommandFactory;
|
||||
use clap::Parser;
|
||||
use clap_complete::Shell;
|
||||
use clap_complete::generate;
|
||||
use codex_app_server_daemon::BootstrapOptions as AppServerBootstrapOptions;
|
||||
use codex_app_server_daemon::LifecycleCommand as AppServerLifecycleCommand;
|
||||
use codex_app_server_daemon::RemoteControlMode as AppServerRemoteControlMode;
|
||||
use codex_arg0::Arg0DispatchPaths;
|
||||
use codex_arg0::arg0_dispatch_or_else;
|
||||
use codex_chatgpt::apply_command::ApplyCommand;
|
||||
@@ -126,7 +129,7 @@ enum Subcommand {
|
||||
/// [experimental] Run the app server or related tooling.
|
||||
AppServer(AppServerCommand),
|
||||
|
||||
/// [experimental] Start a headless app-server with remote control enabled.
|
||||
/// [experimental] Ensure the app-server daemon is running with remote control enabled.
|
||||
RemoteControl,
|
||||
|
||||
/// Launch the Codex desktop app (opens the app installer if missing).
|
||||
@@ -417,7 +420,7 @@ struct AppServerCommand {
|
||||
subcommand: Option<AppServerSubcommand>,
|
||||
|
||||
/// Transport endpoint URL. Supported values: `stdio://` (default),
|
||||
/// `unix://`, `unix://PATH`, `ws://IP:PORT`, `off`.
|
||||
/// `unix://`, `unix://PATH`, `off`.
|
||||
#[arg(
|
||||
long = "listen",
|
||||
value_name = "URL",
|
||||
@@ -442,9 +445,6 @@ struct AppServerCommand {
|
||||
/// See https://developers.openai.com/codex/config-advanced/#metrics for more details.
|
||||
#[arg(long = "analytics-default-enabled")]
|
||||
analytics_default_enabled: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
auth: codex_app_server::AppServerWebsocketAuthArgs,
|
||||
}
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -469,6 +469,9 @@ struct ExecServerCommand {
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
#[allow(clippy::enum_variant_names)]
|
||||
enum AppServerSubcommand {
|
||||
/// Manage the local app-server daemon.
|
||||
Daemon(AppServerDaemonCommand),
|
||||
|
||||
/// Proxy stdio bytes to the running app-server control socket.
|
||||
Proxy(AppServerProxyCommand),
|
||||
|
||||
@@ -483,6 +486,40 @@ enum AppServerSubcommand {
|
||||
GenerateInternalJsonSchema(GenerateInternalJsonSchemaCommand),
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct AppServerDaemonCommand {
|
||||
#[command(subcommand)]
|
||||
subcommand: AppServerDaemonSubcommand,
|
||||
}
|
||||
|
||||
#[derive(Debug, clap::Subcommand)]
|
||||
enum AppServerDaemonSubcommand {
|
||||
/// Install durable local app-server management for SSH-driven use.
|
||||
Bootstrap(AppServerBootstrapCommand),
|
||||
|
||||
/// Start the local app server daemon if it is not already running.
|
||||
Start,
|
||||
|
||||
/// Restart the local app server daemon.
|
||||
Restart,
|
||||
|
||||
/// Enable remote_control for future starts and a currently running managed daemon.
|
||||
EnableRemoteControl,
|
||||
|
||||
/// Disable remote_control for future starts and a currently running managed daemon.
|
||||
DisableRemoteControl,
|
||||
|
||||
/// Stop the local app server daemon.
|
||||
Stop,
|
||||
|
||||
/// Print local CLI and running app-server versions as JSON.
|
||||
Version,
|
||||
|
||||
/// [internal] Run the detached pid-backed standalone updater loop.
|
||||
#[clap(hide = true)]
|
||||
PidUpdateLoop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct AppServerProxyCommand {
|
||||
/// Path to the app-server Unix domain socket to connect to.
|
||||
@@ -490,6 +527,13 @@ struct AppServerProxyCommand {
|
||||
socket_path: Option<AbsolutePathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct AppServerBootstrapCommand {
|
||||
/// Launch the managed app-server with remote_control enabled.
|
||||
#[arg(long = "remote-control")]
|
||||
remote_control: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct GenerateTsCommand {
|
||||
/// Output directory where .ts files will be written
|
||||
@@ -728,14 +772,6 @@ struct FeatureSetArgs {
|
||||
feature: String,
|
||||
}
|
||||
|
||||
const REMOTE_CONTROL_FEATURE_OVERRIDE: &str = "features.remote_control=true";
|
||||
|
||||
fn enable_remote_control_for_invocation(config_overrides: &mut CliConfigOverrides) {
|
||||
config_overrides
|
||||
.raw_overrides
|
||||
.push(REMOTE_CONTROL_FEATURE_OVERRIDE.to_string());
|
||||
}
|
||||
|
||||
fn stage_str(stage: Stage) -> &'static str {
|
||||
match stage {
|
||||
Stage::UnderDevelopment => "under development",
|
||||
@@ -853,7 +889,6 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
subcommand,
|
||||
listen,
|
||||
analytics_default_enabled,
|
||||
auth,
|
||||
} = app_server_cli;
|
||||
reject_remote_mode_for_app_server_subcommand(
|
||||
root_remote.as_deref(),
|
||||
@@ -863,7 +898,6 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
match subcommand {
|
||||
None => {
|
||||
let transport = listen;
|
||||
let auth = auth.try_into_settings()?;
|
||||
codex_app_server::run_main_with_transport(
|
||||
arg0_paths.clone(),
|
||||
root_config_overrides,
|
||||
@@ -871,10 +905,44 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
analytics_default_enabled,
|
||||
transport,
|
||||
codex_protocol::protocol::SessionSource::VSCode,
|
||||
auth,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Some(AppServerSubcommand::Daemon(daemon_cli)) => match daemon_cli.subcommand {
|
||||
AppServerDaemonSubcommand::Start => {
|
||||
print_app_server_daemon_output(AppServerLifecycleCommand::Start).await?;
|
||||
}
|
||||
AppServerDaemonSubcommand::Bootstrap(bootstrap_cli) => {
|
||||
let output =
|
||||
codex_app_server_daemon::bootstrap(AppServerBootstrapOptions {
|
||||
remote_control_enabled: bootstrap_cli.remote_control,
|
||||
})
|
||||
.await?;
|
||||
println!("{}", serde_json::to_string(&output)?);
|
||||
}
|
||||
AppServerDaemonSubcommand::Restart => {
|
||||
print_app_server_daemon_output(AppServerLifecycleCommand::Restart).await?;
|
||||
}
|
||||
AppServerDaemonSubcommand::EnableRemoteControl => {
|
||||
print_app_server_remote_control_output(AppServerRemoteControlMode::Enabled)
|
||||
.await?;
|
||||
}
|
||||
AppServerDaemonSubcommand::DisableRemoteControl => {
|
||||
print_app_server_remote_control_output(
|
||||
AppServerRemoteControlMode::Disabled,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
AppServerDaemonSubcommand::Stop => {
|
||||
print_app_server_daemon_output(AppServerLifecycleCommand::Stop).await?;
|
||||
}
|
||||
AppServerDaemonSubcommand::Version => {
|
||||
print_app_server_daemon_output(AppServerLifecycleCommand::Version).await?;
|
||||
}
|
||||
AppServerDaemonSubcommand::PidUpdateLoop => {
|
||||
codex_app_server_daemon::run_pid_update_loop().await?;
|
||||
}
|
||||
},
|
||||
Some(AppServerSubcommand::Proxy(proxy_cli)) => {
|
||||
let socket_path = match proxy_cli.socket_path {
|
||||
Some(socket_path) => socket_path,
|
||||
@@ -913,17 +981,8 @@ async fn cli_main(arg0_paths: Arg0DispatchPaths) -> anyhow::Result<()> {
|
||||
root_remote_auth_token_env.as_deref(),
|
||||
"remote-control",
|
||||
)?;
|
||||
enable_remote_control_for_invocation(&mut root_config_overrides);
|
||||
codex_app_server::run_main_with_transport(
|
||||
arg0_paths.clone(),
|
||||
root_config_overrides,
|
||||
codex_config::LoaderOverrides::default(),
|
||||
/*default_analytics_enabled*/ false,
|
||||
codex_app_server::AppServerTransport::Off,
|
||||
codex_protocol::protocol::SessionSource::Cli,
|
||||
codex_app_server::AppServerWebsocketAuthSettings::default(),
|
||||
)
|
||||
.await?;
|
||||
let output = codex_app_server_daemon::ensure_remote_control_started().await?;
|
||||
println!("{}", serde_json::to_string(&output)?);
|
||||
}
|
||||
#[cfg(any(target_os = "macos", target_os = "windows"))]
|
||||
Some(Subcommand::App(app_cli)) => {
|
||||
@@ -1547,6 +1606,20 @@ fn reject_remote_mode_for_app_server_subcommand(
|
||||
) -> anyhow::Result<()> {
|
||||
let subcommand_name = match subcommand {
|
||||
None => "app-server",
|
||||
Some(AppServerSubcommand::Daemon(daemon)) => match daemon.subcommand {
|
||||
AppServerDaemonSubcommand::Bootstrap(_) => "app-server daemon bootstrap",
|
||||
AppServerDaemonSubcommand::Start => "app-server daemon start",
|
||||
AppServerDaemonSubcommand::Restart => "app-server daemon restart",
|
||||
AppServerDaemonSubcommand::EnableRemoteControl => {
|
||||
"app-server daemon enable-remote-control"
|
||||
}
|
||||
AppServerDaemonSubcommand::DisableRemoteControl => {
|
||||
"app-server daemon disable-remote-control"
|
||||
}
|
||||
AppServerDaemonSubcommand::Stop => "app-server daemon stop",
|
||||
AppServerDaemonSubcommand::Version => "app-server daemon version",
|
||||
AppServerDaemonSubcommand::PidUpdateLoop => "app-server daemon pid-update-loop",
|
||||
},
|
||||
Some(AppServerSubcommand::Proxy(_)) => "app-server proxy",
|
||||
Some(AppServerSubcommand::GenerateTs(_)) => "app-server generate-ts",
|
||||
Some(AppServerSubcommand::GenerateJsonSchema(_)) => "app-server generate-json-schema",
|
||||
@@ -1557,6 +1630,20 @@ fn reject_remote_mode_for_app_server_subcommand(
|
||||
reject_remote_mode_for_subcommand(remote, remote_auth_token_env, subcommand_name)
|
||||
}
|
||||
|
||||
async fn print_app_server_daemon_output(command: AppServerLifecycleCommand) -> anyhow::Result<()> {
|
||||
let output = codex_app_server_daemon::run(command).await?;
|
||||
println!("{}", serde_json::to_string(&output)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn print_app_server_remote_control_output(
|
||||
mode: AppServerRemoteControlMode,
|
||||
) -> anyhow::Result<()> {
|
||||
let output = codex_app_server_daemon::set_remote_control(mode).await?;
|
||||
println!("{}", serde_json::to_string(&output)?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_remote_auth_token_from_env_var_with<F>(
|
||||
env_var_name: &str,
|
||||
get_var: F,
|
||||
@@ -2305,24 +2392,6 @@ mod tests {
|
||||
assert!(app_server.analytics_default_enabled);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remote_control_override_is_appended_after_root_toggles() {
|
||||
let mut config_overrides = CliConfigOverrides::default();
|
||||
config_overrides
|
||||
.raw_overrides
|
||||
.push("features.remote_control=false".to_string());
|
||||
|
||||
enable_remote_control_for_invocation(&mut config_overrides);
|
||||
|
||||
assert_eq!(
|
||||
config_overrides.raw_overrides,
|
||||
vec![
|
||||
"features.remote_control=false".to_string(),
|
||||
REMOTE_CONTROL_FEATURE_OVERRIDE.to_string(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reject_remote_flag_for_remote_control() {
|
||||
let cli = MultitoolCli::try_parse_from([
|
||||
@@ -2452,16 +2521,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_listen_websocket_url_parses() {
|
||||
let app_server = app_server_from_args(
|
||||
["codex", "app-server", "--listen", "ws://127.0.0.1:4500"].as_ref(),
|
||||
);
|
||||
assert_eq!(
|
||||
app_server.listen,
|
||||
codex_app_server::AppServerTransport::WebSocket {
|
||||
bind_address: "127.0.0.1:4500".parse().expect("valid socket address"),
|
||||
}
|
||||
);
|
||||
fn app_server_listen_websocket_url_fails_to_parse() {
|
||||
let parse_result = MultitoolCli::try_parse_from([
|
||||
"codex",
|
||||
"app-server",
|
||||
"--listen",
|
||||
"ws://127.0.0.1:4500",
|
||||
]);
|
||||
assert!(parse_result.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -2524,6 +2591,70 @@ mod tests {
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_daemon_subcommands_parse() {
|
||||
assert!(matches!(
|
||||
app_server_from_args(
|
||||
[
|
||||
"codex",
|
||||
"app-server",
|
||||
"daemon",
|
||||
"bootstrap",
|
||||
"--remote-control"
|
||||
]
|
||||
.as_ref()
|
||||
)
|
||||
.subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::Bootstrap(AppServerBootstrapCommand {
|
||||
remote_control: true
|
||||
})
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
app_server_from_args(["codex", "app-server", "daemon", "start"].as_ref()).subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::Start
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
app_server_from_args(["codex", "app-server", "daemon", "restart"].as_ref()).subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::Restart
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
app_server_from_args(
|
||||
["codex", "app-server", "daemon", "enable-remote-control"].as_ref()
|
||||
)
|
||||
.subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::EnableRemoteControl
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
app_server_from_args(
|
||||
["codex", "app-server", "daemon", "disable-remote-control"].as_ref()
|
||||
)
|
||||
.subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::DisableRemoteControl
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
app_server_from_args(["codex", "app-server", "daemon", "stop"].as_ref()).subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::Stop
|
||||
}))
|
||||
));
|
||||
assert!(matches!(
|
||||
app_server_from_args(["codex", "app-server", "daemon", "version"].as_ref()).subcommand,
|
||||
Some(AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::Version
|
||||
}))
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_proxy_sock_path_parses() {
|
||||
let app_server =
|
||||
@@ -2553,58 +2684,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_capability_token_flags_parse() {
|
||||
let app_server = app_server_from_args(
|
||||
[
|
||||
"codex",
|
||||
"app-server",
|
||||
"--ws-auth",
|
||||
"capability-token",
|
||||
"--ws-token-file",
|
||||
"/tmp/codex-token",
|
||||
]
|
||||
.as_ref(),
|
||||
);
|
||||
assert_eq!(
|
||||
app_server.auth.ws_auth,
|
||||
Some(codex_app_server::WebsocketAuthCliMode::CapabilityToken)
|
||||
);
|
||||
assert_eq!(
|
||||
app_server.auth.ws_token_file,
|
||||
Some(PathBuf::from("/tmp/codex-token"))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_server_signed_bearer_flags_parse() {
|
||||
let app_server = app_server_from_args(
|
||||
[
|
||||
"codex",
|
||||
"app-server",
|
||||
"--ws-auth",
|
||||
"signed-bearer-token",
|
||||
"--ws-shared-secret-file",
|
||||
"/tmp/codex-secret",
|
||||
"--ws-issuer",
|
||||
"issuer",
|
||||
"--ws-audience",
|
||||
"audience",
|
||||
"--ws-max-clock-skew-seconds",
|
||||
"9",
|
||||
]
|
||||
.as_ref(),
|
||||
);
|
||||
assert_eq!(
|
||||
app_server.auth.ws_auth,
|
||||
Some(codex_app_server::WebsocketAuthCliMode::SignedBearerToken)
|
||||
);
|
||||
assert_eq!(
|
||||
app_server.auth.ws_shared_secret_file,
|
||||
Some(PathBuf::from("/tmp/codex-secret"))
|
||||
);
|
||||
assert_eq!(app_server.auth.ws_issuer.as_deref(), Some("issuer"));
|
||||
assert_eq!(app_server.auth.ws_audience.as_deref(), Some("audience"));
|
||||
assert_eq!(app_server.auth.ws_max_clock_skew_seconds, Some(9));
|
||||
fn reject_remote_auth_token_env_for_app_server_version() {
|
||||
let subcommand = AppServerSubcommand::Daemon(AppServerDaemonCommand {
|
||||
subcommand: AppServerDaemonSubcommand::Version,
|
||||
});
|
||||
let err = reject_remote_mode_for_app_server_subcommand(
|
||||
/*remote*/ None,
|
||||
Some("CODEX_REMOTE_AUTH_TOKEN"),
|
||||
Some(&subcommand),
|
||||
)
|
||||
.expect_err("app-server daemon version should reject --remote-auth-token-env");
|
||||
assert!(err.to_string().contains("app-server daemon version"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user