mirror of
https://github.com/openai/codex.git
synced 2026-04-24 06:35:50 +00:00
Merge remote-tracking branch 'upstream/dev/codex/add-fork-option-to-codex-exec' into repair/collab-stack-refresh-20260402
This commit is contained in:
@@ -122,6 +122,18 @@ enum InitialOperation {
|
||||
},
|
||||
}
|
||||
|
||||
enum StdinPromptBehavior {
|
||||
/// Read stdin only when there is no positional prompt, which is the legacy
|
||||
/// `codex exec` behavior for `codex exec` with piped input.
|
||||
RequiredIfPiped,
|
||||
/// Always treat stdin as the prompt, used for the explicit `codex exec -`
|
||||
/// sentinel and similar forced-stdin call sites.
|
||||
Forced,
|
||||
/// If stdin is piped alongside a positional prompt, treat stdin as
|
||||
/// additional context to append rather than as the primary prompt.
|
||||
OptionalAppend,
|
||||
}
|
||||
|
||||
struct RequestIdSequencer {
|
||||
next: i64,
|
||||
}
|
||||
@@ -640,7 +652,7 @@ async fn run_exec_session(args: ExecRunArgs) -> anyhow::Result<()> {
|
||||
)
|
||||
}
|
||||
(None, root_prompt, imgs) => {
|
||||
let prompt_text = resolve_prompt(root_prompt);
|
||||
let prompt_text = resolve_root_prompt(root_prompt);
|
||||
let mut items: Vec<UserInput> = imgs
|
||||
.into_iter()
|
||||
.map(|path| UserInput::LocalImage { path })
|
||||
@@ -1588,46 +1600,92 @@ fn decode_utf16(
|
||||
String::from_utf16(&units).map_err(|_| PromptDecodeError::InvalidUtf16 { encoding })
|
||||
}
|
||||
|
||||
fn read_prompt_from_stdin(behavior: StdinPromptBehavior) -> Option<String> {
|
||||
let stdin_is_terminal = std::io::stdin().is_terminal();
|
||||
|
||||
match behavior {
|
||||
StdinPromptBehavior::RequiredIfPiped if stdin_is_terminal => {
|
||||
eprintln!(
|
||||
"No prompt provided. Either specify one as an argument or pipe the prompt into stdin."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
StdinPromptBehavior::RequiredIfPiped => {
|
||||
eprintln!("Reading prompt from stdin...");
|
||||
}
|
||||
StdinPromptBehavior::Forced => {}
|
||||
StdinPromptBehavior::OptionalAppend if stdin_is_terminal => return None,
|
||||
StdinPromptBehavior::OptionalAppend => {
|
||||
eprintln!("Reading additional input from stdin...");
|
||||
}
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
if let Err(e) = std::io::stdin().read_to_end(&mut bytes) {
|
||||
eprintln!("Failed to read prompt from stdin: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let buffer = match decode_prompt_bytes(&bytes) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read prompt from stdin: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
if buffer.trim().is_empty() {
|
||||
match behavior {
|
||||
StdinPromptBehavior::OptionalAppend => None,
|
||||
StdinPromptBehavior::RequiredIfPiped | StdinPromptBehavior::Forced => {
|
||||
eprintln!("No prompt provided via stdin.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Some(buffer)
|
||||
}
|
||||
}
|
||||
|
||||
fn prompt_with_stdin_context(prompt: &str, stdin_text: &str) -> String {
|
||||
let mut combined = format!("{prompt}\n\n<stdin>\n{stdin_text}");
|
||||
if !stdin_text.ends_with('\n') {
|
||||
combined.push('\n');
|
||||
}
|
||||
combined.push_str("</stdin>");
|
||||
combined
|
||||
}
|
||||
|
||||
fn resolve_prompt(prompt_arg: Option<String>) -> String {
|
||||
match prompt_arg {
|
||||
Some(p) if p != "-" => p,
|
||||
maybe_dash => {
|
||||
let force_stdin = matches!(maybe_dash.as_deref(), Some("-"));
|
||||
|
||||
if std::io::stdin().is_terminal() && !force_stdin {
|
||||
eprintln!(
|
||||
"No prompt provided. Either specify one as an argument or pipe the prompt into stdin."
|
||||
);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
if !force_stdin {
|
||||
eprintln!("Reading prompt from stdin...");
|
||||
}
|
||||
|
||||
let mut bytes = Vec::new();
|
||||
if let Err(e) = std::io::stdin().read_to_end(&mut bytes) {
|
||||
eprintln!("Failed to read prompt from stdin: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let buffer = match decode_prompt_bytes(&bytes) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
eprintln!("Failed to read prompt from stdin: {e}");
|
||||
std::process::exit(1);
|
||||
}
|
||||
let behavior = if matches!(maybe_dash.as_deref(), Some("-")) {
|
||||
StdinPromptBehavior::Forced
|
||||
} else {
|
||||
StdinPromptBehavior::RequiredIfPiped
|
||||
};
|
||||
|
||||
if buffer.trim().is_empty() {
|
||||
eprintln!("No prompt provided via stdin.");
|
||||
std::process::exit(1);
|
||||
}
|
||||
buffer
|
||||
let Some(prompt) = read_prompt_from_stdin(behavior) else {
|
||||
unreachable!("required stdin prompt should produce content");
|
||||
};
|
||||
prompt
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_root_prompt(prompt_arg: Option<String>) -> String {
|
||||
match prompt_arg {
|
||||
Some(prompt) if prompt != "-" => {
|
||||
if let Some(stdin_text) = read_prompt_from_stdin(StdinPromptBehavior::OptionalAppend) {
|
||||
prompt_with_stdin_context(&prompt, &stdin_text)
|
||||
} else {
|
||||
prompt
|
||||
}
|
||||
}
|
||||
maybe_dash => resolve_prompt(maybe_dash),
|
||||
}
|
||||
}
|
||||
|
||||
fn build_review_request(args: &ReviewArgs) -> anyhow::Result<ReviewRequest> {
|
||||
let target = if args.uncommitted {
|
||||
ReviewTarget::UncommittedChanges
|
||||
|
||||
Reference in New Issue
Block a user