mirror of
https://github.com/openai/codex.git
synced 2026-05-06 12:26:38 +00:00
Compare commits
16 Commits
abhinav/se
...
codex/stat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ea735b69da | ||
|
|
b459ba0f4d | ||
|
|
66e5d99eb2 | ||
|
|
d9f639ba6d | ||
|
|
ebd79231a5 | ||
|
|
8b9888d60b | ||
|
|
57e8dd5e7b | ||
|
|
209bc225a5 | ||
|
|
903c56aa87 | ||
|
|
6bd78a51b5 | ||
|
|
9a05898cf0 | ||
|
|
9136dee011 | ||
|
|
f2924bf70c | ||
|
|
47964c77db | ||
|
|
0782c6050e | ||
|
|
4c0c1b7eee |
@@ -19,6 +19,7 @@ use crate::IoError;
|
||||
use crate::MaybeApplyPatchVerified;
|
||||
use crate::parser::Hunk;
|
||||
use crate::parser::ParseError;
|
||||
use crate::parser::ParsePatchMode;
|
||||
use crate::parser::parse_patch;
|
||||
use crate::unified_diff_from_chunks;
|
||||
use std::str::Utf8Error;
|
||||
@@ -102,17 +103,19 @@ fn extract_apply_patch_from_shell(
|
||||
}
|
||||
|
||||
// TODO: make private once we remove tests in lib.rs
|
||||
pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch {
|
||||
pub fn maybe_parse_apply_patch(argv: &[String], mode: ParsePatchMode) -> MaybeApplyPatch {
|
||||
match argv {
|
||||
// Direct invocation: apply_patch <patch>
|
||||
[cmd, body] if APPLY_PATCH_COMMANDS.contains(&cmd.as_str()) => match parse_patch(body) {
|
||||
Ok(source) => MaybeApplyPatch::Body(source),
|
||||
Err(e) => MaybeApplyPatch::PatchParseError(e),
|
||||
},
|
||||
[cmd, body] if APPLY_PATCH_COMMANDS.contains(&cmd.as_str()) => {
|
||||
match parse_patch(body, mode) {
|
||||
Ok(source) => MaybeApplyPatch::Body(source),
|
||||
Err(e) => MaybeApplyPatch::PatchParseError(e),
|
||||
}
|
||||
}
|
||||
// Shell heredoc form: (optional `cd <path> &&`) apply_patch <<'EOF' ...
|
||||
_ => match parse_shell_script(argv) {
|
||||
Some((shell, script)) => match extract_apply_patch_from_shell(shell, script) {
|
||||
Ok((body, workdir)) => match parse_patch(&body) {
|
||||
Ok((body, workdir)) => match parse_patch(&body, mode) {
|
||||
Ok(mut source) => {
|
||||
source.workdir = workdir;
|
||||
MaybeApplyPatch::Body(source)
|
||||
@@ -133,6 +136,7 @@ pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch {
|
||||
/// patch.
|
||||
pub async fn maybe_parse_apply_patch_verified(
|
||||
argv: &[String],
|
||||
mode: ParsePatchMode,
|
||||
cwd: &AbsolutePathBuf,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&codex_exec_server::FileSystemSandboxContext>,
|
||||
@@ -140,17 +144,17 @@ pub async fn maybe_parse_apply_patch_verified(
|
||||
// Detect a raw patch body passed directly as the command or as the body of a shell
|
||||
// script. In these cases, report an explicit error rather than applying the patch.
|
||||
if let [body] = argv
|
||||
&& parse_patch(body).is_ok()
|
||||
&& parse_patch(body, mode).is_ok()
|
||||
{
|
||||
return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation);
|
||||
}
|
||||
if let Some((_, script)) = parse_shell_script(argv)
|
||||
&& parse_patch(script).is_ok()
|
||||
&& parse_patch(script, mode).is_ok()
|
||||
{
|
||||
return MaybeApplyPatchVerified::CorrectnessError(ApplyPatchError::ImplicitInvocation);
|
||||
}
|
||||
|
||||
match maybe_parse_apply_patch(argv) {
|
||||
match maybe_parse_apply_patch(argv, mode) {
|
||||
MaybeApplyPatch::Body(ApplyPatchArgs {
|
||||
patch,
|
||||
hunks,
|
||||
@@ -377,6 +381,7 @@ fn extract_apply_patch_from_bash(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::parse_patch;
|
||||
use crate::unified_diff_from_chunks;
|
||||
use assert_matches::assert_matches;
|
||||
use codex_exec_server::LOCAL_FS;
|
||||
@@ -437,7 +442,7 @@ mod tests {
|
||||
}
|
||||
|
||||
fn assert_match_args(args: Vec<String>, expected_workdir: Option<&str>) {
|
||||
match maybe_parse_apply_patch(&args) {
|
||||
match maybe_parse_apply_patch(&args, ParsePatchMode::Legacy) {
|
||||
MaybeApplyPatch::Body(ApplyPatchArgs { hunks, workdir, .. }) => {
|
||||
assert_eq!(workdir.as_deref(), expected_workdir);
|
||||
assert_eq!(hunks, expected_single_add());
|
||||
@@ -454,7 +459,7 @@ mod tests {
|
||||
fn assert_not_match(script: &str) {
|
||||
let args = args_bash(script);
|
||||
assert_matches!(
|
||||
maybe_parse_apply_patch(&args),
|
||||
maybe_parse_apply_patch(&args, ParsePatchMode::Legacy),
|
||||
MaybeApplyPatch::NotApplyPatch
|
||||
);
|
||||
}
|
||||
@@ -467,6 +472,7 @@ mod tests {
|
||||
assert_matches!(
|
||||
maybe_parse_apply_patch_verified(
|
||||
&args,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
@@ -484,6 +490,7 @@ mod tests {
|
||||
assert_matches!(
|
||||
maybe_parse_apply_patch_verified(
|
||||
&args,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
@@ -504,7 +511,7 @@ mod tests {
|
||||
"#,
|
||||
]);
|
||||
|
||||
match maybe_parse_apply_patch(&args) {
|
||||
match maybe_parse_apply_patch(&args, ParsePatchMode::Legacy) {
|
||||
MaybeApplyPatch::Body(ApplyPatchArgs { hunks, .. }) => {
|
||||
assert_eq!(
|
||||
hunks,
|
||||
@@ -529,7 +536,7 @@ mod tests {
|
||||
"#,
|
||||
]);
|
||||
|
||||
match maybe_parse_apply_patch(&args) {
|
||||
match maybe_parse_apply_patch(&args, ParsePatchMode::Legacy) {
|
||||
MaybeApplyPatch::Body(ApplyPatchArgs { hunks, .. }) => {
|
||||
assert_eq!(
|
||||
hunks,
|
||||
@@ -568,7 +575,7 @@ mod tests {
|
||||
PATCH"#,
|
||||
]);
|
||||
|
||||
match maybe_parse_apply_patch(&args) {
|
||||
match maybe_parse_apply_patch(&args, ParsePatchMode::Legacy) {
|
||||
MaybeApplyPatch::Body(ApplyPatchArgs { hunks, workdir, .. }) => {
|
||||
assert_eq!(workdir, None);
|
||||
assert_eq!(
|
||||
@@ -689,7 +696,7 @@ PATCH"#,
|
||||
path.display()
|
||||
));
|
||||
|
||||
let patch = parse_patch(&patch).unwrap();
|
||||
let patch = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
let chunks = match patch.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
@@ -728,7 +735,7 @@ PATCH"#,
|
||||
path.display()
|
||||
));
|
||||
|
||||
let patch = parse_patch(&patch).unwrap();
|
||||
let patch = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
let chunks = match patch.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
@@ -773,6 +780,7 @@ PATCH"#,
|
||||
|
||||
let result = maybe_parse_apply_patch_verified(
|
||||
&argv,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
@@ -827,6 +835,7 @@ PATCH"#,
|
||||
|
||||
let result = maybe_parse_apply_patch_verified(
|
||||
&argv,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(session_dir.path()).unwrap(),
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
|
||||
@@ -19,6 +19,7 @@ use codex_utils_absolute_path::AbsolutePathBuf;
|
||||
pub use parser::Hunk;
|
||||
pub use parser::ParseError;
|
||||
use parser::ParseError::*;
|
||||
pub use parser::ParsePatchMode;
|
||||
pub use parser::UpdateFileChunk;
|
||||
pub use parser::parse_patch;
|
||||
use similar::TextDiff;
|
||||
@@ -183,13 +184,14 @@ impl ApplyPatchAction {
|
||||
/// Applies the patch and prints the result to stdout/stderr.
|
||||
pub async fn apply_patch(
|
||||
patch: &str,
|
||||
mode: ParsePatchMode,
|
||||
cwd: &AbsolutePathBuf,
|
||||
stdout: &mut impl std::io::Write,
|
||||
stderr: &mut impl std::io::Write,
|
||||
fs: &dyn ExecutorFileSystem,
|
||||
sandbox: Option<&FileSystemSandboxContext>,
|
||||
) -> Result<(), ApplyPatchError> {
|
||||
let hunks = match parse_patch(patch) {
|
||||
let hunks = match parser::parse_patch(patch, mode) {
|
||||
Ok(source) => source.hunks,
|
||||
Err(e) => {
|
||||
match &e {
|
||||
@@ -640,6 +642,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -700,6 +703,7 @@ mod tests {
|
||||
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&cwd,
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -743,6 +747,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -779,6 +784,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -819,6 +825,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -868,6 +875,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -926,6 +934,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -970,6 +979,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -1013,6 +1023,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -1057,7 +1068,7 @@ mod tests {
|
||||
+QUX"#,
|
||||
path.display()
|
||||
));
|
||||
let patch = parse_patch(&patch).unwrap();
|
||||
let patch = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
|
||||
let update_file_chunks = match patch.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
@@ -1104,7 +1115,7 @@ mod tests {
|
||||
path.display()
|
||||
));
|
||||
|
||||
let patch = parse_patch(&patch).unwrap();
|
||||
let patch = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
let chunks = match patch.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
@@ -1145,7 +1156,7 @@ mod tests {
|
||||
path.display()
|
||||
));
|
||||
|
||||
let patch = parse_patch(&patch).unwrap();
|
||||
let patch = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
let chunks = match patch.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
@@ -1184,7 +1195,7 @@ mod tests {
|
||||
path.display()
|
||||
));
|
||||
|
||||
let patch = parse_patch(&patch).unwrap();
|
||||
let patch = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
let chunks = match patch.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
@@ -1234,7 +1245,7 @@ mod tests {
|
||||
let patch = wrap_patch(&patch_body);
|
||||
|
||||
// Extract chunks then build the unified diff.
|
||||
let parsed = parse_patch(&patch).unwrap();
|
||||
let parsed = parse_patch(&patch, ParsePatchMode::Legacy).unwrap();
|
||||
let chunks = match parsed.hunks.as_slice() {
|
||||
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
||||
_ => panic!("Expected a single UpdateFile hunk"),
|
||||
@@ -1269,6 +1280,7 @@ mod tests {
|
||||
let mut stderr = Vec::new();
|
||||
apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
@@ -1309,6 +1321,7 @@ g
|
||||
let mut stderr = Vec::new();
|
||||
let result = apply_patch(
|
||||
&patch,
|
||||
ParsePatchMode::Legacy,
|
||||
&AbsolutePathBuf::from_absolute_path(dir.path()).unwrap(),
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
|
||||
@@ -123,7 +123,25 @@ pub struct UpdateFileChunk {
|
||||
pub is_end_of_file: bool,
|
||||
}
|
||||
|
||||
pub fn parse_patch(patch: &str) -> Result<ApplyPatchArgs, ParseError> {
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum ParsePatchMode {
|
||||
Legacy,
|
||||
Streaming,
|
||||
}
|
||||
|
||||
pub fn parse_patch(patch: &str, mode: ParsePatchMode) -> Result<ApplyPatchArgs, ParseError> {
|
||||
if mode == ParsePatchMode::Streaming {
|
||||
let patch = normalize_patch_text(patch)?;
|
||||
let mut parser = crate::streaming_parser::StreamingPatchParser::default();
|
||||
parser.push_delta(&patch)?;
|
||||
let hunks = parser.finish()?;
|
||||
return Ok(ApplyPatchArgs {
|
||||
hunks,
|
||||
patch,
|
||||
workdir: None,
|
||||
});
|
||||
}
|
||||
|
||||
let mode = if PARSE_IN_STRICT_MODE {
|
||||
ParseMode::Strict
|
||||
} else {
|
||||
@@ -132,6 +150,12 @@ pub fn parse_patch(patch: &str) -> Result<ApplyPatchArgs, ParseError> {
|
||||
parse_patch_text(patch, mode)
|
||||
}
|
||||
|
||||
fn normalize_patch_text(patch: &str) -> Result<String, ParseError> {
|
||||
let lines: Vec<&str> = patch.trim().lines().collect();
|
||||
let (patch_lines, _) = check_patch_boundaries_lenient(&lines)?;
|
||||
Ok(patch_lines.join("\n"))
|
||||
}
|
||||
|
||||
enum ParseMode {
|
||||
/// Parse the patch text argument as is.
|
||||
Strict,
|
||||
|
||||
@@ -67,6 +67,7 @@ pub fn run_main() -> i32 {
|
||||
};
|
||||
match runtime.block_on(crate::apply_patch(
|
||||
&patch_arg,
|
||||
crate::ParsePatchMode::Legacy,
|
||||
&cwd,
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
|
||||
@@ -116,6 +116,7 @@ pub fn arg0_dispatch() -> Option<Arg0PathEntryGuard> {
|
||||
};
|
||||
match runtime.block_on(codex_apply_patch::apply_patch(
|
||||
&patch_arg,
|
||||
codex_apply_patch::ParsePatchMode::Legacy,
|
||||
&cwd,
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
|
||||
@@ -364,6 +364,9 @@
|
||||
"apply_patch_streaming_events": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"apply_patch_streaming_parser": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"apps": {
|
||||
"type": "boolean"
|
||||
},
|
||||
@@ -3853,6 +3856,9 @@
|
||||
"apply_patch_streaming_events": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"apply_patch_streaming_parser": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"apps": {
|
||||
"type": "boolean"
|
||||
},
|
||||
|
||||
@@ -35,6 +35,7 @@ use crate::tools::sandboxing::ToolCtx;
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_apply_patch::ApplyPatchFileChange;
|
||||
use codex_apply_patch::Hunk;
|
||||
use codex_apply_patch::ParsePatchMode;
|
||||
use codex_apply_patch::StreamingPatchParser;
|
||||
use codex_exec_server::ExecutorFileSystem;
|
||||
use codex_features::Feature;
|
||||
@@ -373,8 +374,14 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
.environment
|
||||
.is_remote()
|
||||
.then(|| turn.file_system_sandbox_context(/*additional_permissions*/ None));
|
||||
let parse_mode = if turn.features.enabled(Feature::ApplyPatchStreamingParser) {
|
||||
ParsePatchMode::Streaming
|
||||
} else {
|
||||
ParsePatchMode::Legacy
|
||||
};
|
||||
match codex_apply_patch::maybe_parse_apply_patch_verified(
|
||||
&command,
|
||||
parse_mode,
|
||||
&cwd,
|
||||
fs.as_ref(),
|
||||
sandbox.as_ref(),
|
||||
@@ -405,6 +412,7 @@ impl ToolHandler for ApplyPatchHandler {
|
||||
|
||||
let req = ApplyPatchRequest {
|
||||
action: apply.action,
|
||||
parse_mode,
|
||||
file_paths,
|
||||
changes,
|
||||
exec_approval_requirement: apply.exec_approval_requirement,
|
||||
@@ -479,8 +487,19 @@ pub(crate) async fn intercept_apply_patch(
|
||||
.primary()
|
||||
.filter(|env| env.environment.is_remote())
|
||||
.map(|_| turn.file_system_sandbox_context(/*additional_permissions*/ None));
|
||||
match codex_apply_patch::maybe_parse_apply_patch_verified(command, cwd, fs, sandbox.as_ref())
|
||||
.await
|
||||
let parse_mode = if turn.features.enabled(Feature::ApplyPatchStreamingParser) {
|
||||
ParsePatchMode::Streaming
|
||||
} else {
|
||||
ParsePatchMode::Legacy
|
||||
};
|
||||
match codex_apply_patch::maybe_parse_apply_patch_verified(
|
||||
command,
|
||||
parse_mode,
|
||||
cwd,
|
||||
fs,
|
||||
sandbox.as_ref(),
|
||||
)
|
||||
.await
|
||||
{
|
||||
codex_apply_patch::MaybeApplyPatchVerified::Body(changes) => {
|
||||
session
|
||||
@@ -513,6 +532,7 @@ pub(crate) async fn intercept_apply_patch(
|
||||
|
||||
let req = ApplyPatchRequest {
|
||||
action: apply.action,
|
||||
parse_mode,
|
||||
file_paths: approval_keys,
|
||||
changes,
|
||||
exec_approval_requirement: apply.exec_approval_requirement,
|
||||
|
||||
@@ -222,6 +222,7 @@ async fn approval_keys_include_move_destination() {
|
||||
let argv = vec!["apply_patch".to_string(), patch.to_string()];
|
||||
let action = match codex_apply_patch::maybe_parse_apply_patch_verified(
|
||||
&argv,
|
||||
codex_apply_patch::ParsePatchMode::Legacy,
|
||||
&cwd,
|
||||
LOCAL_FS.as_ref(),
|
||||
/*sandbox*/ None,
|
||||
|
||||
@@ -18,6 +18,7 @@ use crate::tools::sandboxing::ToolError;
|
||||
use crate::tools::sandboxing::ToolRuntime;
|
||||
use crate::tools::sandboxing::with_cached_approval;
|
||||
use codex_apply_patch::ApplyPatchAction;
|
||||
use codex_apply_patch::ParsePatchMode;
|
||||
use codex_exec_server::FileSystemSandboxContext;
|
||||
use codex_protocol::error::CodexErr;
|
||||
use codex_protocol::error::SandboxErr;
|
||||
@@ -38,6 +39,7 @@ use std::time::Instant;
|
||||
#[derive(Debug)]
|
||||
pub struct ApplyPatchRequest {
|
||||
pub action: ApplyPatchAction,
|
||||
pub parse_mode: ParsePatchMode,
|
||||
pub file_paths: Vec<AbsolutePathBuf>,
|
||||
pub changes: std::collections::HashMap<PathBuf, FileChange>,
|
||||
pub exec_approval_requirement: ExecApprovalRequirement,
|
||||
@@ -201,6 +203,7 @@ impl ToolRuntime<ApplyPatchRequest, ExecToolCallOutput> for ApplyPatchRuntime {
|
||||
let mut stderr = Vec::new();
|
||||
let result = codex_apply_patch::apply_patch(
|
||||
&req.action.patch,
|
||||
req.parse_mode,
|
||||
&req.action.cwd,
|
||||
&mut stdout,
|
||||
&mut stderr,
|
||||
|
||||
@@ -50,6 +50,7 @@ fn guardian_review_request_includes_patch_context() {
|
||||
let expected_patch = action.patch.clone();
|
||||
let request = ApplyPatchRequest {
|
||||
action,
|
||||
parse_mode: ParsePatchMode::Legacy,
|
||||
file_paths: vec![path.clone()],
|
||||
changes: HashMap::from([(
|
||||
path.to_path_buf(),
|
||||
@@ -88,6 +89,7 @@ fn permission_request_payload_uses_apply_patch_hook_name_and_aliases() {
|
||||
let expected_patch = action.patch.clone();
|
||||
let req = ApplyPatchRequest {
|
||||
action,
|
||||
parse_mode: ParsePatchMode::Legacy,
|
||||
file_paths: vec![path],
|
||||
changes: HashMap::new(),
|
||||
exec_approval_requirement: ExecApprovalRequirement::NeedsApproval {
|
||||
@@ -127,6 +129,7 @@ fn file_system_sandbox_context_uses_active_attempt() {
|
||||
};
|
||||
let req = ApplyPatchRequest {
|
||||
action: ApplyPatchAction::new_add_for_test(&path, "hello".to_string()),
|
||||
parse_mode: ParsePatchMode::Legacy,
|
||||
file_paths: vec![path.clone()],
|
||||
changes: HashMap::new(),
|
||||
exec_approval_requirement: ExecApprovalRequirement::Skip {
|
||||
@@ -184,6 +187,7 @@ fn no_sandbox_attempt_has_no_file_system_context() {
|
||||
.abs();
|
||||
let req = ApplyPatchRequest {
|
||||
action: ApplyPatchAction::new_add_for_test(&path, "hello".to_string()),
|
||||
parse_mode: ParsePatchMode::Legacy,
|
||||
file_paths: vec![path.clone()],
|
||||
changes: HashMap::new(),
|
||||
exec_approval_requirement: ExecApprovalRequirement::Skip {
|
||||
|
||||
@@ -219,6 +219,51 @@ D delete.txt
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn apply_patch_cli_uses_streaming_parser_when_feature_enabled() -> Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
|
||||
let harness = apply_patch_harness_with(|builder| {
|
||||
builder.with_model("gpt-5.4").with_config(|config| {
|
||||
config
|
||||
.features
|
||||
.enable(Feature::ApplyPatchStreamingParser)
|
||||
.expect("enable apply_patch streaming parser");
|
||||
config.suppress_unstable_features_warning = true;
|
||||
})
|
||||
})
|
||||
.await?;
|
||||
|
||||
let patch = "*** Begin Patch\n*** Add File: streaming-parser.txt\n+enabled\n*** End Patch";
|
||||
let call_id = "apply-streaming-parser";
|
||||
mount_apply_patch(
|
||||
&harness,
|
||||
call_id,
|
||||
patch,
|
||||
"done",
|
||||
ApplyPatchModelOutput::Function,
|
||||
)
|
||||
.await;
|
||||
|
||||
harness
|
||||
.submit("please apply streaming parser patch")
|
||||
.await?;
|
||||
|
||||
let out = harness
|
||||
.apply_patch_output(call_id, ApplyPatchModelOutput::Function)
|
||||
.await;
|
||||
assert_regex_match(
|
||||
r"(?s)^Exit code: 0.*Success\. Updated the following files:\nA streaming-parser\.txt\n?$",
|
||||
&out,
|
||||
);
|
||||
assert_eq!(
|
||||
harness.read_file_text("streaming-parser.txt").await?,
|
||||
"enabled\n"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
#[test_case(ApplyPatchModelOutput::Freeform)]
|
||||
#[test_case(ApplyPatchModelOutput::Function)]
|
||||
|
||||
@@ -99,6 +99,8 @@ pub enum Feature {
|
||||
ApplyPatchFreeform,
|
||||
/// Stream structured progress while apply_patch input is being generated.
|
||||
ApplyPatchStreamingEvents,
|
||||
/// Use the streaming apply_patch parser for completed patch verification and application.
|
||||
ApplyPatchStreamingParser,
|
||||
/// Allow exec tools to request additional permissions while staying sandboxed.
|
||||
ExecPermissionApprovals,
|
||||
/// Expose the built-in request_permissions tool.
|
||||
@@ -815,6 +817,12 @@ pub const FEATURES: &[FeatureSpec] = &[
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ApplyPatchStreamingParser,
|
||||
key: "apply_patch_streaming_parser",
|
||||
stage: Stage::UnderDevelopment,
|
||||
default_enabled: false,
|
||||
},
|
||||
FeatureSpec {
|
||||
id: Feature::ExecPermissionApprovals,
|
||||
key: "exec_permission_approvals",
|
||||
|
||||
@@ -110,6 +110,19 @@ fn request_permissions_is_under_development() {
|
||||
assert_eq!(Feature::ExecPermissionApprovals.default_enabled(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apply_patch_streaming_parser_is_under_development() {
|
||||
assert_eq!(
|
||||
feature_for_key("apply_patch_streaming_parser"),
|
||||
Some(Feature::ApplyPatchStreamingParser)
|
||||
);
|
||||
assert_eq!(
|
||||
Feature::ApplyPatchStreamingParser.stage(),
|
||||
Stage::UnderDevelopment
|
||||
);
|
||||
assert_eq!(Feature::ApplyPatchStreamingParser.default_enabled(), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn request_permissions_tool_is_under_development() {
|
||||
assert_eq!(
|
||||
|
||||
Reference in New Issue
Block a user