mirror of
https://github.com/openai/codex.git
synced 2026-02-01 22:47:52 +00:00
Compare commits
2 Commits
pr7780
...
cconger/fl
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e70beb5b93 | ||
|
|
52f0056368 |
@@ -44,6 +44,7 @@ const CHANNEL_CAPACITY: usize = 128;
|
||||
pub async fn run_main(
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
cli_config_overrides: CliConfigOverrides,
|
||||
config_overrides: ConfigOverrides,
|
||||
) -> IoResult<()> {
|
||||
// Set up channels.
|
||||
let (incoming_tx, mut incoming_rx) = mpsc::channel::<JSONRPCMessage>(CHANNEL_CAPACITY);
|
||||
@@ -80,7 +81,7 @@ pub async fn run_main(
|
||||
format!("error parsing -c overrides: {e}"),
|
||||
)
|
||||
})?;
|
||||
let config = Config::load_with_cli_overrides(cli_kv_overrides, ConfigOverrides::default())
|
||||
let config = Config::load_with_cli_overrides(cli_kv_overrides, config_overrides)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
std::io::Error::new(ErrorKind::InvalidData, format!("error loading config: {e}"))
|
||||
|
||||
@@ -4,7 +4,12 @@ use codex_common::CliConfigOverrides;
|
||||
|
||||
fn main() -> anyhow::Result<()> {
|
||||
arg0_dispatch_or_else(|codex_linux_sandbox_exe| async move {
|
||||
run_main(codex_linux_sandbox_exe, CliConfigOverrides::default()).await?;
|
||||
run_main(
|
||||
codex_linux_sandbox_exe,
|
||||
CliConfigOverrides::default(),
|
||||
codex_core::config::ConfigOverrides::default(),
|
||||
)
|
||||
.await?;
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,7 +16,10 @@ use codex_cli::login::run_login_with_chatgpt;
|
||||
use codex_cli::login::run_login_with_device_code;
|
||||
use codex_cli::login::run_logout;
|
||||
use codex_cloud_tasks::Cli as CloudTasksCli;
|
||||
use codex_common::ApprovalModeCliArg;
|
||||
use codex_common::CliConfigOverrides;
|
||||
use codex_common::CommonCli;
|
||||
use codex_common::oss::get_default_model_for_oss_provider;
|
||||
use codex_exec::Cli as ExecCli;
|
||||
use codex_responses_api_proxy::Args as ResponsesApiProxyArgs;
|
||||
use codex_tui::AppExitInfo;
|
||||
@@ -32,9 +35,16 @@ mod wsl_paths;
|
||||
|
||||
use crate::mcp_cmd::McpCli;
|
||||
|
||||
use codex_core::LMSTUDIO_OSS_PROVIDER_ID;
|
||||
use codex_core::OLLAMA_OSS_PROVIDER_ID;
|
||||
use codex_core::config::Config;
|
||||
use codex_core::config::ConfigOverrides;
|
||||
use codex_core::config::find_codex_home;
|
||||
use codex_core::config::load_config_as_toml_with_cli_overrides;
|
||||
use codex_core::config::resolve_oss_provider;
|
||||
use codex_core::features::is_known_feature_key;
|
||||
use codex_core::protocol::AskForApproval;
|
||||
use codex_protocol::config_types::SandboxMode;
|
||||
|
||||
/// Codex CLI
|
||||
///
|
||||
@@ -207,6 +217,20 @@ struct LogoutCommand {
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
struct AppServerCommand {
|
||||
#[clap(flatten)]
|
||||
common: CommonCli,
|
||||
|
||||
#[clap(flatten)]
|
||||
config_overrides: CliConfigOverrides,
|
||||
|
||||
/// Configure when the model requires human approval before executing a command.
|
||||
#[arg(long = "ask-for-approval", short = 'a')]
|
||||
approval_policy: Option<codex_common::ApprovalModeCliArg>,
|
||||
|
||||
/// Enable web search (off by default). When enabled, the native Responses `web_search` tool is available to the model (no per‑call approval).
|
||||
#[arg(long = "search", default_value_t = false)]
|
||||
web_search: bool,
|
||||
|
||||
/// Omit to run the app server; specify a subcommand for tooling.
|
||||
#[command(subcommand)]
|
||||
subcommand: Option<AppServerSubcommand>,
|
||||
@@ -433,7 +457,29 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
||||
}
|
||||
Some(Subcommand::AppServer(app_server_cli)) => match app_server_cli.subcommand {
|
||||
None => {
|
||||
codex_app_server::run_main(codex_linux_sandbox_exe, root_config_overrides).await?;
|
||||
let mut cli_config_overrides = app_server_cli.config_overrides;
|
||||
if app_server_cli.web_search {
|
||||
cli_config_overrides
|
||||
.raw_overrides
|
||||
.push("features.web_search_request=true".to_string());
|
||||
}
|
||||
prepend_config_flags(&mut cli_config_overrides, root_config_overrides.clone());
|
||||
let cli_kv_overrides = cli_config_overrides
|
||||
.parse_overrides()
|
||||
.map_err(anyhow::Error::msg)?;
|
||||
let config_overrides = app_server_config_overrides_from_common(
|
||||
&app_server_cli.common,
|
||||
app_server_cli.approval_policy,
|
||||
cli_kv_overrides,
|
||||
codex_linux_sandbox_exe.clone(),
|
||||
)
|
||||
.await?;
|
||||
codex_app_server::run_main(
|
||||
codex_linux_sandbox_exe,
|
||||
cli_config_overrides,
|
||||
config_overrides,
|
||||
)
|
||||
.await?;
|
||||
}
|
||||
Some(AppServerSubcommand::GenerateTs(gen_cli)) => {
|
||||
codex_app_server_protocol::generate_ts(
|
||||
@@ -576,7 +622,7 @@ async fn cli_main(codex_linux_sandbox_exe: Option<PathBuf>) -> anyhow::Result<()
|
||||
|
||||
// Thread through relevant top-level flags (at minimum, `--profile`).
|
||||
let overrides = ConfigOverrides {
|
||||
config_profile: interactive.config_profile.clone(),
|
||||
config_profile: interactive.common.config_profile.clone(),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@@ -605,6 +651,85 @@ fn prepend_config_flags(
|
||||
.splice(0..0, cli_config_overrides.raw_overrides);
|
||||
}
|
||||
|
||||
async fn app_server_config_overrides_from_common(
|
||||
common: &CommonCli,
|
||||
approval_policy: Option<ApprovalModeCliArg>,
|
||||
cli_kv_overrides: Vec<(String, toml::Value)>,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
) -> anyhow::Result<ConfigOverrides> {
|
||||
let (sandbox_mode, approval_policy) = if common.full_auto {
|
||||
(
|
||||
Some(SandboxMode::WorkspaceWrite),
|
||||
Some(AskForApproval::OnRequest),
|
||||
)
|
||||
} else if common.dangerously_bypass_approvals_and_sandbox {
|
||||
(
|
||||
Some(SandboxMode::DangerFullAccess),
|
||||
Some(AskForApproval::Never),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
common.sandbox_mode.map(Into::into),
|
||||
approval_policy.map(Into::into),
|
||||
)
|
||||
};
|
||||
|
||||
let model_provider = if common.oss {
|
||||
let codex_home = find_codex_home()?;
|
||||
let config_toml =
|
||||
load_config_as_toml_with_cli_overrides(&codex_home, cli_kv_overrides).await?;
|
||||
Some(
|
||||
resolve_oss_provider(
|
||||
common.oss_provider.as_deref(),
|
||||
&config_toml,
|
||||
common.config_profile.clone(),
|
||||
)
|
||||
.ok_or_else(|| {
|
||||
anyhow::anyhow!(format!(
|
||||
"No default OSS provider configured. Use --local-provider=provider or set oss_provider to either {LMSTUDIO_OSS_PROVIDER_ID} or {OLLAMA_OSS_PROVIDER_ID} in config.toml"
|
||||
))
|
||||
})?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let model = if let Some(model) = &common.model {
|
||||
Some(model.clone())
|
||||
} else if common.oss {
|
||||
model_provider
|
||||
.as_deref()
|
||||
.and_then(get_default_model_for_oss_provider)
|
||||
.map(std::borrow::ToOwned::to_owned)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let cwd = common
|
||||
.cwd
|
||||
.as_ref()
|
||||
.map(|p| p.canonicalize().unwrap_or_else(|_| p.to_path_buf()));
|
||||
|
||||
Ok(ConfigOverrides {
|
||||
model,
|
||||
review_model: None,
|
||||
cwd,
|
||||
approval_policy,
|
||||
sandbox_mode,
|
||||
model_provider,
|
||||
config_profile: common.config_profile.clone(),
|
||||
codex_linux_sandbox_exe,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
include_apply_patch_tool: None,
|
||||
show_raw_agent_reasoning: common.oss.then_some(true),
|
||||
tools_web_search_request: None,
|
||||
experimental_sandbox_command_assessment: None,
|
||||
additional_writable_roots: common.add_dir.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Build the final `TuiCli` for a `codex resume` invocation.
|
||||
fn finalize_resume_interactive(
|
||||
mut interactive: TuiCli,
|
||||
@@ -633,39 +758,13 @@ fn finalize_resume_interactive(
|
||||
/// root-level flags. Only overrides fields explicitly set on the resume-scoped
|
||||
/// CLI. Also appends `-c key=value` overrides with highest precedence.
|
||||
fn merge_resume_cli_flags(interactive: &mut TuiCli, resume_cli: TuiCli) {
|
||||
if let Some(model) = resume_cli.model {
|
||||
interactive.model = Some(model);
|
||||
}
|
||||
if resume_cli.oss {
|
||||
interactive.oss = true;
|
||||
}
|
||||
if let Some(profile) = resume_cli.config_profile {
|
||||
interactive.config_profile = Some(profile);
|
||||
}
|
||||
if let Some(sandbox) = resume_cli.sandbox_mode {
|
||||
interactive.sandbox_mode = Some(sandbox);
|
||||
}
|
||||
interactive.common.apply_overrides(&resume_cli.common);
|
||||
if let Some(approval) = resume_cli.approval_policy {
|
||||
interactive.approval_policy = Some(approval);
|
||||
}
|
||||
if resume_cli.full_auto {
|
||||
interactive.full_auto = true;
|
||||
}
|
||||
if resume_cli.dangerously_bypass_approvals_and_sandbox {
|
||||
interactive.dangerously_bypass_approvals_and_sandbox = true;
|
||||
}
|
||||
if let Some(cwd) = resume_cli.cwd {
|
||||
interactive.cwd = Some(cwd);
|
||||
}
|
||||
if resume_cli.web_search {
|
||||
interactive.web_search = true;
|
||||
}
|
||||
if !resume_cli.images.is_empty() {
|
||||
interactive.images = resume_cli.images;
|
||||
}
|
||||
if !resume_cli.add_dir.is_empty() {
|
||||
interactive.add_dir.extend(resume_cli.add_dir);
|
||||
}
|
||||
if let Some(prompt) = resume_cli.prompt {
|
||||
interactive.prompt = Some(prompt);
|
||||
}
|
||||
@@ -759,11 +858,55 @@ mod tests {
|
||||
assert!(lines[1].contains("\u{1b}[36m"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exec_inherits_root_model_flag() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "-m", "codex-rapidash-300", "exec", "hi"])
|
||||
.expect("parse");
|
||||
let MultitoolCli { subcommand, .. } = cli;
|
||||
|
||||
let Subcommand::Exec(exec_cli) = subcommand.expect("exec present") else {
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
assert_eq!(exec_cli.common.model.as_deref(), Some("codex-rapidash-300"));
|
||||
assert_eq!(exec_cli.prompt.as_deref(), Some("hi"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn app_server_inherits_root_model_flag() {
|
||||
let cli = MultitoolCli::try_parse_from(["codex", "-m", "codex-rapidash-300", "app-server"])
|
||||
.expect("parse");
|
||||
let MultitoolCli { subcommand, .. } = cli;
|
||||
|
||||
let Subcommand::AppServer(app_server_cli) = subcommand.expect("app-server present") else {
|
||||
unreachable!()
|
||||
};
|
||||
assert!(app_server_cli.subcommand.is_none());
|
||||
|
||||
let cli_kv_overrides = app_server_cli
|
||||
.config_overrides
|
||||
.parse_overrides()
|
||||
.expect("parse");
|
||||
let config_overrides = app_server_config_overrides_from_common(
|
||||
&app_server_cli.common,
|
||||
app_server_cli.approval_policy,
|
||||
cli_kv_overrides,
|
||||
None,
|
||||
)
|
||||
.await
|
||||
.expect("merge");
|
||||
|
||||
assert_eq!(
|
||||
config_overrides.model.as_deref(),
|
||||
Some("codex-rapidash-300")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resume_model_flag_applies_when_no_root_flags() {
|
||||
let interactive = finalize_from_args(["codex", "resume", "-m", "gpt-5-test"].as_ref());
|
||||
|
||||
assert_eq!(interactive.model.as_deref(), Some("gpt-5-test"));
|
||||
assert_eq!(interactive.common.model.as_deref(), Some("gpt-5-test"));
|
||||
assert!(interactive.resume_picker);
|
||||
assert!(!interactive.resume_last);
|
||||
assert_eq!(interactive.resume_session_id, None);
|
||||
@@ -819,28 +962,33 @@ mod tests {
|
||||
.as_ref(),
|
||||
);
|
||||
|
||||
assert_eq!(interactive.model.as_deref(), Some("gpt-5-test"));
|
||||
assert!(interactive.oss);
|
||||
assert_eq!(interactive.config_profile.as_deref(), Some("my-profile"));
|
||||
assert_eq!(interactive.common.model.as_deref(), Some("gpt-5-test"));
|
||||
assert!(interactive.common.oss);
|
||||
assert_eq!(
|
||||
interactive.common.config_profile.as_deref(),
|
||||
Some("my-profile")
|
||||
);
|
||||
assert_matches!(
|
||||
interactive.sandbox_mode,
|
||||
interactive.common.sandbox_mode,
|
||||
Some(codex_common::SandboxModeCliArg::WorkspaceWrite)
|
||||
);
|
||||
assert_matches!(
|
||||
interactive.approval_policy,
|
||||
Some(codex_common::ApprovalModeCliArg::OnRequest)
|
||||
);
|
||||
assert!(interactive.full_auto);
|
||||
assert!(interactive.common.full_auto);
|
||||
assert_eq!(
|
||||
interactive.cwd.as_deref(),
|
||||
interactive.common.cwd.as_deref(),
|
||||
Some(std::path::Path::new("/tmp"))
|
||||
);
|
||||
assert!(interactive.web_search);
|
||||
let has_a = interactive
|
||||
.common
|
||||
.images
|
||||
.iter()
|
||||
.any(|p| p == std::path::Path::new("/tmp/a.png"));
|
||||
let has_b = interactive
|
||||
.common
|
||||
.images
|
||||
.iter()
|
||||
.any(|p| p == std::path::Path::new("/tmp/b.png"));
|
||||
@@ -860,7 +1008,7 @@ mod tests {
|
||||
]
|
||||
.as_ref(),
|
||||
);
|
||||
assert!(interactive.dangerously_bypass_approvals_and_sandbox);
|
||||
assert!(interactive.common.dangerously_bypass_approvals_and_sandbox);
|
||||
assert!(interactive.resume_picker);
|
||||
assert!(!interactive.resume_last);
|
||||
assert_eq!(interactive.resume_session_id, None);
|
||||
|
||||
108
codex-rs/common/src/common_cli.rs
Normal file
108
codex-rs/common/src/common_cli.rs
Normal file
@@ -0,0 +1,108 @@
|
||||
use clap::Parser;
|
||||
use clap::ValueHint;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::SandboxModeCliArg;
|
||||
|
||||
/// Flags shared across multiple Codex CLIs.
|
||||
#[derive(Parser, Debug, Default, Clone)]
|
||||
pub struct CommonCli {
|
||||
/// Optional image(s) to attach to the initial prompt.
|
||||
#[arg(
|
||||
long = "image",
|
||||
short = 'i',
|
||||
value_name = "FILE",
|
||||
value_delimiter = ',',
|
||||
num_args = 1..,
|
||||
global = true
|
||||
)]
|
||||
pub images: Vec<PathBuf>,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm', global = true)]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Convenience flag to select the local open source model provider. Equivalent to -c
|
||||
/// model_provider=oss; verifies a local LM Studio or Ollama server is running.
|
||||
#[arg(long = "oss", default_value_t = false, global = true)]
|
||||
pub oss: bool,
|
||||
|
||||
/// Specify which local provider to use (lmstudio or ollama).
|
||||
/// If not specified with --oss, will use config default or show selection.
|
||||
#[arg(long = "local-provider", global = true)]
|
||||
pub oss_provider: Option<String>,
|
||||
|
||||
/// Configuration profile from config.toml to specify default options.
|
||||
#[arg(long = "profile", short = 'p', global = true)]
|
||||
pub config_profile: Option<String>,
|
||||
|
||||
/// Select the sandbox policy to use when executing model-generated shell
|
||||
/// commands.
|
||||
#[arg(long = "sandbox", short = 's', value_enum, global = true)]
|
||||
pub sandbox_mode: Option<SandboxModeCliArg>,
|
||||
|
||||
/// Convenience alias for low-friction sandboxed automatic execution (-a on-request, --sandbox workspace-write).
|
||||
#[arg(long = "full-auto", default_value_t = false, global = true)]
|
||||
pub full_auto: bool,
|
||||
|
||||
/// Skip all confirmation prompts and execute commands without sandboxing.
|
||||
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
||||
#[arg(
|
||||
long = "dangerously-bypass-approvals-and-sandbox",
|
||||
alias = "yolo",
|
||||
default_value_t = false,
|
||||
conflicts_with = "full_auto",
|
||||
global = true
|
||||
)]
|
||||
pub dangerously_bypass_approvals_and_sandbox: bool,
|
||||
|
||||
/// Tell the agent to use the specified directory as its working root.
|
||||
#[clap(long = "cd", short = 'C', value_name = "DIR", global = true)]
|
||||
pub cwd: Option<PathBuf>,
|
||||
|
||||
/// Additional directories that should be writable alongside the primary workspace.
|
||||
#[arg(
|
||||
long = "add-dir",
|
||||
value_name = "DIR",
|
||||
value_hint = ValueHint::DirPath,
|
||||
global = true
|
||||
)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
impl CommonCli {
|
||||
/// Apply overrides from another `CommonCli`, giving precedence to fields that
|
||||
/// are explicitly set on `other`.
|
||||
pub fn apply_overrides(&mut self, other: &CommonCli) {
|
||||
if let Some(model) = &other.model {
|
||||
self.model = Some(model.clone());
|
||||
}
|
||||
if other.oss {
|
||||
self.oss = true;
|
||||
}
|
||||
if let Some(provider) = &other.oss_provider {
|
||||
self.oss_provider = Some(provider.clone());
|
||||
}
|
||||
if let Some(profile) = &other.config_profile {
|
||||
self.config_profile = Some(profile.clone());
|
||||
}
|
||||
if let Some(sandbox) = other.sandbox_mode {
|
||||
self.sandbox_mode = Some(sandbox);
|
||||
}
|
||||
if other.full_auto {
|
||||
self.full_auto = true;
|
||||
}
|
||||
if other.dangerously_bypass_approvals_and_sandbox {
|
||||
self.dangerously_bypass_approvals_and_sandbox = true;
|
||||
}
|
||||
if let Some(cwd) = &other.cwd {
|
||||
self.cwd = Some(cwd.clone());
|
||||
}
|
||||
if !other.images.is_empty() {
|
||||
self.images = other.images.clone();
|
||||
}
|
||||
if !other.add_dir.is_empty() {
|
||||
self.add_dir.extend(other.add_dir.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,9 @@ pub use approval_mode_cli_arg::ApprovalModeCliArg;
|
||||
#[cfg(feature = "cli")]
|
||||
mod sandbox_mode_cli_arg;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
mod common_cli;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub use sandbox_mode_cli_arg::SandboxModeCliArg;
|
||||
|
||||
@@ -22,6 +25,9 @@ mod config_override;
|
||||
#[cfg(feature = "cli")]
|
||||
pub use config_override::CliConfigOverrides;
|
||||
|
||||
#[cfg(feature = "cli")]
|
||||
pub use common_cli::CommonCli;
|
||||
|
||||
mod sandbox_summary;
|
||||
|
||||
#[cfg(feature = "sandbox_summary")]
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use clap::Parser;
|
||||
use clap::ValueEnum;
|
||||
use codex_common::CliConfigOverrides;
|
||||
use codex_common::CommonCli;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
@@ -10,58 +11,14 @@ pub struct Cli {
|
||||
#[command(subcommand)]
|
||||
pub command: Option<Command>,
|
||||
|
||||
/// Optional image(s) to attach to the initial prompt.
|
||||
#[arg(long = "image", short = 'i', value_name = "FILE", value_delimiter = ',', num_args = 1..)]
|
||||
pub images: Vec<PathBuf>,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm')]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Use open-source provider.
|
||||
#[arg(long = "oss", default_value_t = false)]
|
||||
pub oss: bool,
|
||||
|
||||
/// Specify which local provider to use (lmstudio or ollama).
|
||||
/// If not specified with --oss, will use config default or show selection.
|
||||
#[arg(long = "local-provider")]
|
||||
pub oss_provider: Option<String>,
|
||||
|
||||
/// Select the sandbox policy to use when executing model-generated shell
|
||||
/// commands.
|
||||
#[arg(long = "sandbox", short = 's', value_enum)]
|
||||
pub sandbox_mode: Option<codex_common::SandboxModeCliArg>,
|
||||
|
||||
/// Configuration profile from config.toml to specify default options.
|
||||
#[arg(long = "profile", short = 'p')]
|
||||
pub config_profile: Option<String>,
|
||||
|
||||
/// Convenience alias for low-friction sandboxed automatic execution (-a on-request, --sandbox workspace-write).
|
||||
#[arg(long = "full-auto", default_value_t = false)]
|
||||
pub full_auto: bool,
|
||||
|
||||
/// Skip all confirmation prompts and execute commands without sandboxing.
|
||||
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
||||
#[arg(
|
||||
long = "dangerously-bypass-approvals-and-sandbox",
|
||||
alias = "yolo",
|
||||
default_value_t = false,
|
||||
conflicts_with = "full_auto"
|
||||
)]
|
||||
pub dangerously_bypass_approvals_and_sandbox: bool,
|
||||
|
||||
/// Tell the agent to use the specified directory as its working root.
|
||||
#[clap(long = "cd", short = 'C', value_name = "DIR")]
|
||||
pub cwd: Option<PathBuf>,
|
||||
/// Model and execution controls shared with the interactive CLI.
|
||||
#[clap(flatten)]
|
||||
pub common: CommonCli,
|
||||
|
||||
/// Allow running Codex outside a Git repository.
|
||||
#[arg(long = "skip-git-repo-check", default_value_t = false)]
|
||||
pub skip_git_repo_check: bool,
|
||||
|
||||
/// Additional directories that should be writable alongside the primary workspace.
|
||||
#[arg(long = "add-dir", value_name = "DIR", value_hint = clap::ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
|
||||
/// Path to a JSON Schema file describing the model's final response shape.
|
||||
#[arg(long = "output-schema", value_name = "FILE")]
|
||||
pub output_schema: Option<PathBuf>,
|
||||
|
||||
@@ -11,6 +11,7 @@ pub mod event_processor_with_jsonl_output;
|
||||
pub mod exec_events;
|
||||
|
||||
pub use cli::Cli;
|
||||
use codex_common::CommonCli;
|
||||
use codex_common::oss::ensure_oss_provider_ready;
|
||||
use codex_common::oss::get_default_model_for_oss_provider;
|
||||
use codex_core::AuthManager;
|
||||
@@ -59,6 +60,17 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
||||
|
||||
let Cli {
|
||||
command,
|
||||
common,
|
||||
skip_git_repo_check,
|
||||
color,
|
||||
last_message_file,
|
||||
json: json_mode,
|
||||
prompt,
|
||||
output_schema: output_schema_path,
|
||||
config_overrides,
|
||||
} = cli;
|
||||
|
||||
let CommonCli {
|
||||
images,
|
||||
model: model_cli_arg,
|
||||
oss,
|
||||
@@ -67,16 +79,10 @@ pub async fn run_main(cli: Cli, codex_linux_sandbox_exe: Option<PathBuf>) -> any
|
||||
full_auto,
|
||||
dangerously_bypass_approvals_and_sandbox,
|
||||
cwd,
|
||||
skip_git_repo_check,
|
||||
add_dir,
|
||||
color,
|
||||
last_message_file,
|
||||
json: json_mode,
|
||||
sandbox_mode: sandbox_mode_cli_arg,
|
||||
prompt,
|
||||
output_schema: output_schema_path,
|
||||
config_overrides,
|
||||
} = cli;
|
||||
..
|
||||
} = common;
|
||||
|
||||
// Determine the prompt source (parent or subcommand) and read from stdin if needed.
|
||||
let prompt_arg = match &command {
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
use clap::Parser;
|
||||
use clap::ValueHint;
|
||||
use codex_common::ApprovalModeCliArg;
|
||||
use codex_common::CliConfigOverrides;
|
||||
use std::path::PathBuf;
|
||||
use codex_common::CommonCli;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(version)]
|
||||
pub struct Cli {
|
||||
#[clap(flatten)]
|
||||
pub common: CommonCli,
|
||||
|
||||
/// Optional user prompt to start the session.
|
||||
#[arg(value_name = "PROMPT", value_hint = clap::ValueHint::Other)]
|
||||
pub prompt: Option<String>,
|
||||
|
||||
/// Optional image(s) to attach to the initial prompt.
|
||||
#[arg(long = "image", short = 'i', value_name = "FILE", value_delimiter = ',', num_args = 1..)]
|
||||
pub images: Vec<PathBuf>,
|
||||
|
||||
// Internal controls set by the top-level `codex resume` subcommand.
|
||||
// These are not exposed as user flags on the base `codex` command.
|
||||
#[clap(skip)]
|
||||
@@ -28,59 +26,14 @@ pub struct Cli {
|
||||
#[clap(skip)]
|
||||
pub resume_session_id: Option<String>,
|
||||
|
||||
/// Model the agent should use.
|
||||
#[arg(long, short = 'm')]
|
||||
pub model: Option<String>,
|
||||
|
||||
/// Convenience flag to select the local open source model provider. Equivalent to -c
|
||||
/// model_provider=oss; verifies a local LM Studio or Ollama server is running.
|
||||
#[arg(long = "oss", default_value_t = false)]
|
||||
pub oss: bool,
|
||||
|
||||
/// Specify which local provider to use (lmstudio or ollama).
|
||||
/// If not specified with --oss, will use config default or show selection.
|
||||
#[arg(long = "local-provider")]
|
||||
pub oss_provider: Option<String>,
|
||||
|
||||
/// Configuration profile from config.toml to specify default options.
|
||||
#[arg(long = "profile", short = 'p')]
|
||||
pub config_profile: Option<String>,
|
||||
|
||||
/// Select the sandbox policy to use when executing model-generated shell
|
||||
/// commands.
|
||||
#[arg(long = "sandbox", short = 's')]
|
||||
pub sandbox_mode: Option<codex_common::SandboxModeCliArg>,
|
||||
|
||||
/// Configure when the model requires human approval before executing a command.
|
||||
#[arg(long = "ask-for-approval", short = 'a')]
|
||||
pub approval_policy: Option<ApprovalModeCliArg>,
|
||||
|
||||
/// Convenience alias for low-friction sandboxed automatic execution (-a on-request, --sandbox workspace-write).
|
||||
#[arg(long = "full-auto", default_value_t = false)]
|
||||
pub full_auto: bool,
|
||||
|
||||
/// Skip all confirmation prompts and execute commands without sandboxing.
|
||||
/// EXTREMELY DANGEROUS. Intended solely for running in environments that are externally sandboxed.
|
||||
#[arg(
|
||||
long = "dangerously-bypass-approvals-and-sandbox",
|
||||
alias = "yolo",
|
||||
default_value_t = false,
|
||||
conflicts_with_all = ["approval_policy", "full_auto"]
|
||||
)]
|
||||
pub dangerously_bypass_approvals_and_sandbox: bool,
|
||||
|
||||
/// Tell the agent to use the specified directory as its working root.
|
||||
#[clap(long = "cd", short = 'C', value_name = "DIR")]
|
||||
pub cwd: Option<PathBuf>,
|
||||
|
||||
/// Enable web search (off by default). When enabled, the native Responses `web_search` tool is available to the model (no per‑call approval).
|
||||
#[arg(long = "search", default_value_t = false)]
|
||||
pub web_search: bool,
|
||||
|
||||
/// Additional directories that should be writable alongside the primary workspace.
|
||||
#[arg(long = "add-dir", value_name = "DIR", value_hint = ValueHint::DirPath)]
|
||||
pub add_dir: Vec<PathBuf>,
|
||||
|
||||
#[clap(skip)]
|
||||
pub config_overrides: CliConfigOverrides,
|
||||
}
|
||||
|
||||
@@ -102,19 +102,19 @@ pub async fn run_main(
|
||||
mut cli: Cli,
|
||||
codex_linux_sandbox_exe: Option<PathBuf>,
|
||||
) -> std::io::Result<AppExitInfo> {
|
||||
let (sandbox_mode, approval_policy) = if cli.full_auto {
|
||||
let (sandbox_mode, approval_policy) = if cli.common.full_auto {
|
||||
(
|
||||
Some(SandboxMode::WorkspaceWrite),
|
||||
Some(AskForApproval::OnRequest),
|
||||
)
|
||||
} else if cli.dangerously_bypass_approvals_and_sandbox {
|
||||
} else if cli.common.dangerously_bypass_approvals_and_sandbox {
|
||||
(
|
||||
Some(SandboxMode::DangerFullAccess),
|
||||
Some(AskForApproval::Never),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
cli.sandbox_mode.map(Into::<SandboxMode>::into),
|
||||
cli.common.sandbox_mode.map(Into::<SandboxMode>::into),
|
||||
cli.approval_policy.map(Into::into),
|
||||
)
|
||||
};
|
||||
@@ -161,11 +161,11 @@ pub async fn run_main(
|
||||
}
|
||||
};
|
||||
|
||||
let model_provider_override = if cli.oss {
|
||||
let model_provider_override = if cli.common.oss {
|
||||
let resolved = resolve_oss_provider(
|
||||
cli.oss_provider.as_deref(),
|
||||
cli.common.oss_provider.as_deref(),
|
||||
&config_toml,
|
||||
cli.config_profile.clone(),
|
||||
cli.common.config_profile.clone(),
|
||||
);
|
||||
|
||||
if let Some(provider) = resolved {
|
||||
@@ -185,9 +185,9 @@ pub async fn run_main(
|
||||
};
|
||||
|
||||
// When using `--oss`, let the bootstrapper pick the model based on selected provider
|
||||
let model = if let Some(model) = &cli.model {
|
||||
let model = if let Some(model) = &cli.common.model {
|
||||
Some(model.clone())
|
||||
} else if cli.oss {
|
||||
} else if cli.common.oss {
|
||||
// Use the provider from model_provider_override
|
||||
model_provider_override
|
||||
.as_ref()
|
||||
@@ -198,8 +198,12 @@ pub async fn run_main(
|
||||
};
|
||||
|
||||
// canonicalize the cwd
|
||||
let cwd = cli.cwd.clone().map(|p| p.canonicalize().unwrap_or(p));
|
||||
let additional_dirs = cli.add_dir.clone();
|
||||
let cwd = cli
|
||||
.common
|
||||
.cwd
|
||||
.clone()
|
||||
.map(|p| p.canonicalize().unwrap_or(p));
|
||||
let additional_dirs = cli.common.add_dir.clone();
|
||||
|
||||
let overrides = ConfigOverrides {
|
||||
model,
|
||||
@@ -208,13 +212,13 @@ pub async fn run_main(
|
||||
sandbox_mode,
|
||||
cwd,
|
||||
model_provider: model_provider_override.clone(),
|
||||
config_profile: cli.config_profile.clone(),
|
||||
config_profile: cli.common.config_profile.clone(),
|
||||
codex_linux_sandbox_exe,
|
||||
base_instructions: None,
|
||||
developer_instructions: None,
|
||||
compact_prompt: None,
|
||||
include_apply_patch_tool: None,
|
||||
show_raw_agent_reasoning: cli.oss.then_some(true),
|
||||
show_raw_agent_reasoning: cli.common.oss.then_some(true),
|
||||
tools_web_search_request: None,
|
||||
experimental_sandbox_command_assessment: None,
|
||||
additional_writable_roots: additional_dirs,
|
||||
@@ -222,7 +226,7 @@ pub async fn run_main(
|
||||
|
||||
let config = load_config_or_exit(cli_kv_overrides.clone(), overrides.clone()).await;
|
||||
|
||||
if let Some(warning) = add_dir_warning_message(&cli.add_dir, &config.sandbox_policy) {
|
||||
if let Some(warning) = add_dir_warning_message(&cli.common.add_dir, &config.sandbox_policy) {
|
||||
#[allow(clippy::print_stderr)]
|
||||
{
|
||||
eprintln!("Error adding directories: {warning}");
|
||||
@@ -280,7 +284,7 @@ pub async fn run_main(
|
||||
.with_target(false)
|
||||
.with_filter(targets);
|
||||
|
||||
if cli.oss && model_provider_override.is_some() {
|
||||
if cli.common.oss && model_provider_override.is_some() {
|
||||
// We're in the oss section, so provider_id should be Some
|
||||
// Let's handle None case gracefully though just in case
|
||||
let provider_id = match model_provider_override.as_ref() {
|
||||
@@ -508,7 +512,8 @@ async fn run_ratatui_app(
|
||||
resume_picker::ResumeSelection::StartFresh
|
||||
};
|
||||
|
||||
let Cli { prompt, images, .. } = cli;
|
||||
let Cli { prompt, common, .. } = cli;
|
||||
let images = common.images;
|
||||
|
||||
let app_result = App::run(
|
||||
&mut tui,
|
||||
|
||||
Reference in New Issue
Block a user