feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
mod parser;
|
|
|
|
|
|
mod seek_sequence;
|
|
|
|
|
|
|
|
|
|
|
|
use std::collections::HashMap;
|
|
|
|
|
|
use std::path::Path;
|
|
|
|
|
|
use std::path::PathBuf;
|
|
|
|
|
|
|
|
|
|
|
|
use anyhow::Context;
|
|
|
|
|
|
use anyhow::Error;
|
|
|
|
|
|
use anyhow::Result;
|
|
|
|
|
|
pub use parser::parse_patch;
|
|
|
|
|
|
pub use parser::Hunk;
|
|
|
|
|
|
pub use parser::ParseError;
|
|
|
|
|
|
use parser::ParseError::*;
|
|
|
|
|
|
use parser::UpdateFileChunk;
|
|
|
|
|
|
use similar::TextDiff;
|
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
|
use tree_sitter::Parser;
|
|
|
|
|
|
use tree_sitter_bash::LANGUAGE as BASH;
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
|
|
pub enum ApplyPatchError {
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
ParseError(#[from] ParseError),
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
IoError(#[from] IoError),
|
|
|
|
|
|
/// Error that occurs while computing replacements when applying patch chunks
|
|
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
ComputeReplacements(String),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<std::io::Error> for ApplyPatchError {
|
|
|
|
|
|
fn from(err: std::io::Error) -> Self {
|
|
|
|
|
|
ApplyPatchError::IoError(IoError {
|
|
|
|
|
|
context: "I/O error".to_string(),
|
|
|
|
|
|
source: err,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Error)]
|
|
|
|
|
|
#[error("{context}: {source}")]
|
|
|
|
|
|
pub struct IoError {
|
|
|
|
|
|
context: String,
|
|
|
|
|
|
#[source]
|
|
|
|
|
|
source: std::io::Error,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub enum MaybeApplyPatch {
|
|
|
|
|
|
Body(Vec<Hunk>),
|
|
|
|
|
|
ShellParseError(Error),
|
|
|
|
|
|
PatchParseError(ParseError),
|
|
|
|
|
|
NotApplyPatch,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn maybe_parse_apply_patch(argv: &[String]) -> MaybeApplyPatch {
|
|
|
|
|
|
match argv {
|
|
|
|
|
|
[cmd, body] if cmd == "apply_patch" => match parse_patch(body) {
|
|
|
|
|
|
Ok(hunks) => MaybeApplyPatch::Body(hunks),
|
|
|
|
|
|
Err(e) => MaybeApplyPatch::PatchParseError(e),
|
|
|
|
|
|
},
|
|
|
|
|
|
[bash, flag, script]
|
|
|
|
|
|
if bash == "bash"
|
|
|
|
|
|
&& flag == "-lc"
|
|
|
|
|
|
&& script.trim_start().starts_with("apply_patch") =>
|
|
|
|
|
|
{
|
|
|
|
|
|
match extract_heredoc_body_from_apply_patch_command(script) {
|
|
|
|
|
|
Ok(body) => match parse_patch(&body) {
|
|
|
|
|
|
Ok(hunks) => MaybeApplyPatch::Body(hunks),
|
|
|
|
|
|
Err(e) => MaybeApplyPatch::PatchParseError(e),
|
|
|
|
|
|
},
|
|
|
|
|
|
Err(e) => MaybeApplyPatch::ShellParseError(e),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ => MaybeApplyPatch::NotApplyPatch,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub enum ApplyPatchFileChange {
|
|
|
|
|
|
Add {
|
|
|
|
|
|
content: String,
|
|
|
|
|
|
},
|
|
|
|
|
|
Delete,
|
|
|
|
|
|
Update {
|
|
|
|
|
|
unified_diff: String,
|
|
|
|
|
|
move_path: Option<PathBuf>,
|
2025-04-28 21:15:41 -07:00
|
|
|
|
/// new_content that will result after the unified_diff is applied.
|
|
|
|
|
|
new_content: String,
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
},
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub enum MaybeApplyPatchVerified {
|
|
|
|
|
|
/// `argv` corresponded to an `apply_patch` invocation, and these are the
|
|
|
|
|
|
/// resulting proposed file changes.
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
Body(ApplyPatchAction),
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
/// `argv` could not be parsed to determine whether it corresponds to an
|
|
|
|
|
|
/// `apply_patch` invocation.
|
|
|
|
|
|
ShellParseError(Error),
|
|
|
|
|
|
/// `argv` corresponded to an `apply_patch` invocation, but it could not
|
|
|
|
|
|
/// be fulfilled due to the specified error.
|
|
|
|
|
|
CorrectnessError(ApplyPatchError),
|
|
|
|
|
|
/// `argv` decidedly did not correspond to an `apply_patch` invocation.
|
|
|
|
|
|
NotApplyPatch,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
/// ApplyPatchAction is the result of parsing an `apply_patch` command. By
|
|
|
|
|
|
/// construction, all paths should be absolute paths.
|
|
|
|
|
|
pub struct ApplyPatchAction {
|
|
|
|
|
|
changes: HashMap<PathBuf, ApplyPatchFileChange>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl ApplyPatchAction {
|
|
|
|
|
|
pub fn is_empty(&self) -> bool {
|
|
|
|
|
|
self.changes.is_empty()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the changes that would be made by applying the patch.
|
|
|
|
|
|
pub fn changes(&self) -> &HashMap<PathBuf, ApplyPatchFileChange> {
|
|
|
|
|
|
&self.changes
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Should be used exclusively for testing. (Not worth the overhead of
|
|
|
|
|
|
/// creating a feature flag for this.)
|
|
|
|
|
|
pub fn new_add_for_test(path: &Path, content: String) -> Self {
|
|
|
|
|
|
if !path.is_absolute() {
|
|
|
|
|
|
panic!("path must be absolute");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let changes = HashMap::from([(path.to_path_buf(), ApplyPatchFileChange::Add { content })]);
|
|
|
|
|
|
Self { changes }
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// cwd must be an absolute path so that we can resolve relative paths in the
|
|
|
|
|
|
/// patch.
|
|
|
|
|
|
pub fn maybe_parse_apply_patch_verified(argv: &[String], cwd: &Path) -> MaybeApplyPatchVerified {
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
match maybe_parse_apply_patch(argv) {
|
|
|
|
|
|
MaybeApplyPatch::Body(hunks) => {
|
|
|
|
|
|
let mut changes = HashMap::new();
|
|
|
|
|
|
for hunk in hunks {
|
|
|
|
|
|
match hunk {
|
|
|
|
|
|
Hunk::AddFile { path, contents } => {
|
|
|
|
|
|
changes.insert(
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
cwd.join(path),
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
ApplyPatchFileChange::Add {
|
|
|
|
|
|
content: contents.clone(),
|
|
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
Hunk::DeleteFile { path } => {
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
changes.insert(cwd.join(path), ApplyPatchFileChange::Delete);
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
Hunk::UpdateFile {
|
|
|
|
|
|
path,
|
|
|
|
|
|
move_path,
|
|
|
|
|
|
chunks,
|
|
|
|
|
|
} => {
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff,
|
|
|
|
|
|
content: contents,
|
|
|
|
|
|
} = match unified_diff_from_chunks(&path, &chunks) {
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
Ok(diff) => diff,
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
return MaybeApplyPatchVerified::CorrectnessError(e);
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
changes.insert(
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
cwd.join(path),
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
ApplyPatchFileChange::Update {
|
|
|
|
|
|
unified_diff,
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
move_path: move_path.map(|p| cwd.join(p)),
|
2025-04-28 21:15:41 -07:00
|
|
|
|
new_content: contents,
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
},
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
fix: ensure apply_patch resolves relative paths against workdir or project cwd (#810)
https://github.com/openai/codex/pull/800 kicked off some work to be more
disciplined about honoring the `cwd` param passed in rather than
assuming `std::env::current_dir()` as the `cwd`. As part of this, we
need to ensure `apply_patch` calls honor the appropriate `cwd` as well,
which is significant if the paths in the `apply_patch` arg are not
absolute paths themselves. Failing that:
- The `apply_patch` function call can contain an optional`workdir`
param, so:
- If specified and is an absolute path, it should be used to resolve
relative paths
- If specified and is a relative path, should be resolved against
`Config.cwd` and then any relative paths will be resolved against the
result
- If `workdir` is not specified on the function call, relative paths
should be resolved against `Config.cwd`
Note that we had a similar issue in the TypeScript CLI that was fixed in
https://github.com/openai/codex/pull/556.
As part of the fix, this PR introduces `ApplyPatchAction` so clients can
deal with that instead of the raw `HashMap<PathBuf,
ApplyPatchFileChange>`. This enables us to enforce, by construction,
that all paths contained in the `ApplyPatchAction` are absolute paths.
2025-05-04 12:32:51 -07:00
|
|
|
|
MaybeApplyPatchVerified::Body(ApplyPatchAction { changes })
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
MaybeApplyPatch::ShellParseError(e) => MaybeApplyPatchVerified::ShellParseError(e),
|
|
|
|
|
|
MaybeApplyPatch::PatchParseError(e) => MaybeApplyPatchVerified::CorrectnessError(e.into()),
|
|
|
|
|
|
MaybeApplyPatch::NotApplyPatch => MaybeApplyPatchVerified::NotApplyPatch,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Attempts to extract a heredoc_body object from a string bash command like:
|
|
|
|
|
|
/// Optimistically
|
|
|
|
|
|
///
|
|
|
|
|
|
/// ```bash
|
|
|
|
|
|
/// bash -lc 'apply_patch <<EOF\n***Begin Patch\n...EOF'
|
|
|
|
|
|
/// ```
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Arguments
|
|
|
|
|
|
///
|
|
|
|
|
|
/// * `src` - A string slice that holds the full command
|
|
|
|
|
|
///
|
|
|
|
|
|
/// # Returns
|
|
|
|
|
|
///
|
|
|
|
|
|
/// This function returns a `Result` which is:
|
|
|
|
|
|
///
|
|
|
|
|
|
/// * `Ok(String)` - The heredoc body if the extraction is successful.
|
|
|
|
|
|
/// * `Err(anyhow::Error)` - An error if the extraction fails.
|
|
|
|
|
|
///
|
|
|
|
|
|
fn extract_heredoc_body_from_apply_patch_command(src: &str) -> anyhow::Result<String> {
|
|
|
|
|
|
if !src.trim_start().starts_with("apply_patch") {
|
|
|
|
|
|
anyhow::bail!("expected command to start with 'apply_patch'");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let lang = BASH.into();
|
|
|
|
|
|
let mut parser = Parser::new();
|
|
|
|
|
|
parser.set_language(&lang).expect("load bash grammar");
|
|
|
|
|
|
let tree = parser
|
|
|
|
|
|
.parse(src, None)
|
|
|
|
|
|
.ok_or_else(|| anyhow::anyhow!("failed to parse patch into AST"))?;
|
|
|
|
|
|
|
|
|
|
|
|
let bytes = src.as_bytes();
|
|
|
|
|
|
let mut c = tree.root_node().walk();
|
|
|
|
|
|
|
|
|
|
|
|
loop {
|
|
|
|
|
|
let node = c.node();
|
|
|
|
|
|
if node.kind() == "heredoc_body" {
|
|
|
|
|
|
let text = node.utf8_text(bytes).unwrap();
|
|
|
|
|
|
return Ok(text.trim_end_matches('\n').to_owned());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if c.goto_first_child() {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
while !c.goto_next_sibling() {
|
|
|
|
|
|
if !c.goto_parent() {
|
|
|
|
|
|
anyhow::bail!("expected to find heredoc_body in patch candidate");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Applies the patch and prints the result to stdout/stderr.
|
|
|
|
|
|
pub fn apply_patch(
|
|
|
|
|
|
patch: &str,
|
|
|
|
|
|
stdout: &mut impl std::io::Write,
|
|
|
|
|
|
stderr: &mut impl std::io::Write,
|
|
|
|
|
|
) -> Result<(), ApplyPatchError> {
|
|
|
|
|
|
let hunks = match parse_patch(patch) {
|
|
|
|
|
|
Ok(hunks) => hunks,
|
|
|
|
|
|
Err(e) => {
|
|
|
|
|
|
match &e {
|
|
|
|
|
|
InvalidPatchError(message) => {
|
|
|
|
|
|
writeln!(stderr, "Invalid patch: {message}").map_err(ApplyPatchError::from)?;
|
|
|
|
|
|
}
|
|
|
|
|
|
InvalidHunkError {
|
|
|
|
|
|
message,
|
|
|
|
|
|
line_number,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
writeln!(
|
|
|
|
|
|
stderr,
|
|
|
|
|
|
"Invalid patch hunk on line {line_number}: {message}"
|
|
|
|
|
|
)
|
|
|
|
|
|
.map_err(ApplyPatchError::from)?;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
return Err(ApplyPatchError::ParseError(e));
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
apply_hunks(&hunks, stdout, stderr)?;
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Applies hunks and continues to update stdout/stderr
|
|
|
|
|
|
pub fn apply_hunks(
|
|
|
|
|
|
hunks: &[Hunk],
|
|
|
|
|
|
stdout: &mut impl std::io::Write,
|
|
|
|
|
|
stderr: &mut impl std::io::Write,
|
|
|
|
|
|
) -> Result<(), ApplyPatchError> {
|
|
|
|
|
|
let _existing_paths: Vec<&Path> = hunks
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.filter_map(|hunk| match hunk {
|
|
|
|
|
|
Hunk::AddFile { .. } => {
|
|
|
|
|
|
// The file is being added, so it doesn't exist yet.
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
Hunk::DeleteFile { path } => Some(path.as_path()),
|
|
|
|
|
|
Hunk::UpdateFile {
|
|
|
|
|
|
path, move_path, ..
|
|
|
|
|
|
} => match move_path {
|
|
|
|
|
|
Some(move_path) => {
|
|
|
|
|
|
if std::fs::metadata(move_path)
|
|
|
|
|
|
.map(|m| m.is_file())
|
|
|
|
|
|
.unwrap_or(false)
|
|
|
|
|
|
{
|
|
|
|
|
|
Some(move_path.as_path())
|
|
|
|
|
|
} else {
|
|
|
|
|
|
None
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
None => Some(path.as_path()),
|
|
|
|
|
|
},
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect::<Vec<&Path>>();
|
|
|
|
|
|
|
|
|
|
|
|
// Delegate to a helper that applies each hunk to the filesystem.
|
|
|
|
|
|
match apply_hunks_to_files(hunks) {
|
|
|
|
|
|
Ok(affected) => {
|
|
|
|
|
|
print_summary(&affected, stdout).map_err(ApplyPatchError::from)?;
|
|
|
|
|
|
}
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
|
writeln!(stderr, "{err:?}").map_err(ApplyPatchError::from)?;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Applies each parsed patch hunk to the filesystem.
|
|
|
|
|
|
/// Returns an error if any of the changes could not be applied.
|
|
|
|
|
|
/// Tracks file paths affected by applying a patch.
|
|
|
|
|
|
pub struct AffectedPaths {
|
|
|
|
|
|
pub added: Vec<PathBuf>,
|
|
|
|
|
|
pub modified: Vec<PathBuf>,
|
|
|
|
|
|
pub deleted: Vec<PathBuf>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Apply the hunks to the filesystem, returning which files were added, modified, or deleted.
|
|
|
|
|
|
/// Returns an error if the patch could not be applied.
|
|
|
|
|
|
fn apply_hunks_to_files(hunks: &[Hunk]) -> anyhow::Result<AffectedPaths> {
|
|
|
|
|
|
if hunks.is_empty() {
|
|
|
|
|
|
anyhow::bail!("No files were modified.");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let mut added: Vec<PathBuf> = Vec::new();
|
|
|
|
|
|
let mut modified: Vec<PathBuf> = Vec::new();
|
|
|
|
|
|
let mut deleted: Vec<PathBuf> = Vec::new();
|
|
|
|
|
|
for hunk in hunks {
|
|
|
|
|
|
match hunk {
|
|
|
|
|
|
Hunk::AddFile { path, contents } => {
|
|
|
|
|
|
if let Some(parent) = path.parent() {
|
|
|
|
|
|
if !parent.as_os_str().is_empty() {
|
|
|
|
|
|
std::fs::create_dir_all(parent).with_context(|| {
|
|
|
|
|
|
format!("Failed to create parent directories for {}", path.display())
|
|
|
|
|
|
})?;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
std::fs::write(path, contents)
|
|
|
|
|
|
.with_context(|| format!("Failed to write file {}", path.display()))?;
|
|
|
|
|
|
added.push(path.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
Hunk::DeleteFile { path } => {
|
|
|
|
|
|
std::fs::remove_file(path)
|
|
|
|
|
|
.with_context(|| format!("Failed to delete file {}", path.display()))?;
|
|
|
|
|
|
deleted.push(path.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
Hunk::UpdateFile {
|
|
|
|
|
|
path,
|
|
|
|
|
|
move_path,
|
|
|
|
|
|
chunks,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let AppliedPatch { new_contents, .. } =
|
|
|
|
|
|
derive_new_contents_from_chunks(path, chunks)?;
|
|
|
|
|
|
if let Some(dest) = move_path {
|
|
|
|
|
|
if let Some(parent) = dest.parent() {
|
|
|
|
|
|
if !parent.as_os_str().is_empty() {
|
|
|
|
|
|
std::fs::create_dir_all(parent).with_context(|| {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"Failed to create parent directories for {}",
|
|
|
|
|
|
dest.display()
|
|
|
|
|
|
)
|
|
|
|
|
|
})?;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
std::fs::write(dest, new_contents)
|
|
|
|
|
|
.with_context(|| format!("Failed to write file {}", dest.display()))?;
|
|
|
|
|
|
std::fs::remove_file(path)
|
|
|
|
|
|
.with_context(|| format!("Failed to remove original {}", path.display()))?;
|
|
|
|
|
|
modified.push(dest.clone());
|
|
|
|
|
|
} else {
|
|
|
|
|
|
std::fs::write(path, new_contents)
|
|
|
|
|
|
.with_context(|| format!("Failed to write file {}", path.display()))?;
|
|
|
|
|
|
modified.push(path.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Ok(AffectedPaths {
|
|
|
|
|
|
added,
|
|
|
|
|
|
modified,
|
|
|
|
|
|
deleted,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct AppliedPatch {
|
|
|
|
|
|
original_contents: String,
|
|
|
|
|
|
new_contents: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Return *only* the new file contents (joined into a single `String`) after
|
|
|
|
|
|
/// applying the chunks to the file at `path`.
|
|
|
|
|
|
fn derive_new_contents_from_chunks(
|
|
|
|
|
|
path: &Path,
|
|
|
|
|
|
chunks: &[UpdateFileChunk],
|
|
|
|
|
|
) -> std::result::Result<AppliedPatch, ApplyPatchError> {
|
|
|
|
|
|
let original_contents = match std::fs::read_to_string(path) {
|
|
|
|
|
|
Ok(contents) => contents,
|
|
|
|
|
|
Err(err) => {
|
|
|
|
|
|
return Err(ApplyPatchError::IoError(IoError {
|
|
|
|
|
|
context: format!("Failed to read file to update {}", path.display()),
|
|
|
|
|
|
source: err,
|
|
|
|
|
|
}))
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let mut original_lines: Vec<String> = original_contents
|
|
|
|
|
|
.split('\n')
|
|
|
|
|
|
.map(|s| s.to_string())
|
|
|
|
|
|
.collect();
|
|
|
|
|
|
|
|
|
|
|
|
// Drop the trailing empty element that results from the final newline so
|
|
|
|
|
|
// that line counts match the behaviour of standard `diff`.
|
|
|
|
|
|
if original_lines.last().is_some_and(|s| s.is_empty()) {
|
|
|
|
|
|
original_lines.pop();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let replacements = compute_replacements(&original_lines, path, chunks)?;
|
|
|
|
|
|
let new_lines = apply_replacements(original_lines, &replacements);
|
|
|
|
|
|
let mut new_lines = new_lines;
|
|
|
|
|
|
if !new_lines.last().is_some_and(|s| s.is_empty()) {
|
|
|
|
|
|
new_lines.push(String::new());
|
|
|
|
|
|
}
|
|
|
|
|
|
let new_contents = new_lines.join("\n");
|
|
|
|
|
|
Ok(AppliedPatch {
|
|
|
|
|
|
original_contents,
|
|
|
|
|
|
new_contents,
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Compute a list of replacements needed to transform `original_lines` into the
|
|
|
|
|
|
/// new lines, given the patch `chunks`. Each replacement is returned as
|
|
|
|
|
|
/// `(start_index, old_len, new_lines)`.
|
|
|
|
|
|
fn compute_replacements(
|
|
|
|
|
|
original_lines: &[String],
|
|
|
|
|
|
path: &Path,
|
|
|
|
|
|
chunks: &[UpdateFileChunk],
|
|
|
|
|
|
) -> std::result::Result<Vec<(usize, usize, Vec<String>)>, ApplyPatchError> {
|
|
|
|
|
|
let mut replacements: Vec<(usize, usize, Vec<String>)> = Vec::new();
|
|
|
|
|
|
let mut line_index: usize = 0;
|
|
|
|
|
|
|
|
|
|
|
|
for chunk in chunks {
|
|
|
|
|
|
// If a chunk has a `change_context`, we use seek_sequence to find it, then
|
|
|
|
|
|
// adjust our `line_index` to continue from there.
|
|
|
|
|
|
if let Some(ctx_line) = &chunk.change_context {
|
|
|
|
|
|
if let Some(idx) =
|
|
|
|
|
|
seek_sequence::seek_sequence(original_lines, &[ctx_line.clone()], line_index, false)
|
|
|
|
|
|
{
|
|
|
|
|
|
line_index = idx + 1;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return Err(ApplyPatchError::ComputeReplacements(format!(
|
|
|
|
|
|
"Failed to find context '{}' in {}",
|
|
|
|
|
|
ctx_line,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
)));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if chunk.old_lines.is_empty() {
|
|
|
|
|
|
// Pure addition (no old lines). We'll add them at the end or just
|
|
|
|
|
|
// before the final empty line if one exists.
|
|
|
|
|
|
let insertion_idx = if original_lines.last().is_some_and(|s| s.is_empty()) {
|
|
|
|
|
|
original_lines.len() - 1
|
|
|
|
|
|
} else {
|
|
|
|
|
|
original_lines.len()
|
|
|
|
|
|
};
|
|
|
|
|
|
replacements.push((insertion_idx, 0, chunk.new_lines.clone()));
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Otherwise, try to match the existing lines in the file with the old lines
|
|
|
|
|
|
// from the chunk. If found, schedule that region for replacement.
|
|
|
|
|
|
// Attempt to locate the `old_lines` verbatim within the file. In many
|
|
|
|
|
|
// real‑world diffs the last element of `old_lines` is an *empty* string
|
|
|
|
|
|
// representing the terminating newline of the region being replaced.
|
|
|
|
|
|
// This sentinel is not present in `original_lines` because we strip the
|
|
|
|
|
|
// trailing empty slice emitted by `split('\n')`. If a direct search
|
|
|
|
|
|
// fails and the pattern ends with an empty string, retry without that
|
|
|
|
|
|
// final element so that modifications touching the end‑of‑file can be
|
|
|
|
|
|
// located reliably.
|
|
|
|
|
|
|
|
|
|
|
|
let mut pattern: &[String] = &chunk.old_lines;
|
|
|
|
|
|
let mut found =
|
|
|
|
|
|
seek_sequence::seek_sequence(original_lines, pattern, line_index, chunk.is_end_of_file);
|
|
|
|
|
|
|
|
|
|
|
|
let mut new_slice: &[String] = &chunk.new_lines;
|
|
|
|
|
|
|
|
|
|
|
|
if found.is_none() && pattern.last().is_some_and(|s| s.is_empty()) {
|
|
|
|
|
|
// Retry without the trailing empty line which represents the final
|
|
|
|
|
|
// newline in the file.
|
|
|
|
|
|
pattern = &pattern[..pattern.len() - 1];
|
|
|
|
|
|
if new_slice.last().is_some_and(|s| s.is_empty()) {
|
|
|
|
|
|
new_slice = &new_slice[..new_slice.len() - 1];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
found = seek_sequence::seek_sequence(
|
|
|
|
|
|
original_lines,
|
|
|
|
|
|
pattern,
|
|
|
|
|
|
line_index,
|
|
|
|
|
|
chunk.is_end_of_file,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if let Some(start_idx) = found {
|
|
|
|
|
|
replacements.push((start_idx, pattern.len(), new_slice.to_vec()));
|
|
|
|
|
|
line_index = start_idx + pattern.len();
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return Err(ApplyPatchError::ComputeReplacements(format!(
|
|
|
|
|
|
"Failed to find expected lines {:?} in {}",
|
|
|
|
|
|
chunk.old_lines,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
)));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Ok(replacements)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Apply the `(start_index, old_len, new_lines)` replacements to `original_lines`,
|
|
|
|
|
|
/// returning the modified file contents as a vector of lines.
|
|
|
|
|
|
fn apply_replacements(
|
|
|
|
|
|
mut lines: Vec<String>,
|
|
|
|
|
|
replacements: &[(usize, usize, Vec<String>)],
|
|
|
|
|
|
) -> Vec<String> {
|
|
|
|
|
|
// We must apply replacements in descending order so that earlier replacements
|
|
|
|
|
|
// don't shift the positions of later ones.
|
|
|
|
|
|
for (start_idx, old_len, new_segment) in replacements.iter().rev() {
|
|
|
|
|
|
let start_idx = *start_idx;
|
|
|
|
|
|
let old_len = *old_len;
|
|
|
|
|
|
|
|
|
|
|
|
// Remove old lines.
|
|
|
|
|
|
for _ in 0..old_len {
|
|
|
|
|
|
if start_idx < lines.len() {
|
|
|
|
|
|
lines.remove(start_idx);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Insert new lines.
|
|
|
|
|
|
for (offset, new_line) in new_segment.iter().enumerate() {
|
|
|
|
|
|
lines.insert(start_idx + offset, new_line.clone());
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lines
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-28 21:15:41 -07:00
|
|
|
|
/// Intended result of a file update for apply_patch.
|
|
|
|
|
|
#[derive(Debug, Eq, PartialEq)]
|
|
|
|
|
|
pub struct ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff: String,
|
|
|
|
|
|
content: String,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
pub fn unified_diff_from_chunks(
|
|
|
|
|
|
path: &Path,
|
|
|
|
|
|
chunks: &[UpdateFileChunk],
|
2025-04-28 21:15:41 -07:00
|
|
|
|
) -> std::result::Result<ApplyPatchFileUpdate, ApplyPatchError> {
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
unified_diff_from_chunks_with_context(path, chunks, 1)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub fn unified_diff_from_chunks_with_context(
|
|
|
|
|
|
path: &Path,
|
|
|
|
|
|
chunks: &[UpdateFileChunk],
|
|
|
|
|
|
context: usize,
|
2025-04-28 21:15:41 -07:00
|
|
|
|
) -> std::result::Result<ApplyPatchFileUpdate, ApplyPatchError> {
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
let AppliedPatch {
|
|
|
|
|
|
original_contents,
|
|
|
|
|
|
new_contents,
|
|
|
|
|
|
} = derive_new_contents_from_chunks(path, chunks)?;
|
|
|
|
|
|
let text_diff = TextDiff::from_lines(&original_contents, &new_contents);
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let unified_diff = text_diff.unified_diff().context_radius(context).to_string();
|
|
|
|
|
|
Ok(ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff,
|
|
|
|
|
|
content: new_contents,
|
|
|
|
|
|
})
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Print the summary of changes in git-style format.
|
|
|
|
|
|
/// Write a summary of changes to the given writer.
|
|
|
|
|
|
pub fn print_summary(
|
|
|
|
|
|
affected: &AffectedPaths,
|
|
|
|
|
|
out: &mut impl std::io::Write,
|
|
|
|
|
|
) -> std::io::Result<()> {
|
|
|
|
|
|
writeln!(out, "Success. Updated the following files:")?;
|
|
|
|
|
|
for path in &affected.added {
|
|
|
|
|
|
writeln!(out, "A {}", path.display())?;
|
|
|
|
|
|
}
|
|
|
|
|
|
for path in &affected.modified {
|
|
|
|
|
|
writeln!(out, "M {}", path.display())?;
|
|
|
|
|
|
}
|
|
|
|
|
|
for path in &affected.deleted {
|
|
|
|
|
|
writeln!(out, "D {}", path.display())?;
|
|
|
|
|
|
}
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
use super::*;
|
|
|
|
|
|
use pretty_assertions::assert_eq;
|
|
|
|
|
|
use std::fs;
|
|
|
|
|
|
use tempfile::tempdir;
|
|
|
|
|
|
|
|
|
|
|
|
/// Helper to construct a patch with the given body.
|
|
|
|
|
|
fn wrap_patch(body: &str) -> String {
|
|
|
|
|
|
format!("*** Begin Patch\n{}\n*** End Patch", body)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn strs_to_strings(strs: &[&str]) -> Vec<String> {
|
|
|
|
|
|
strs.iter().map(|s| s.to_string()).collect()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_literal() {
|
|
|
|
|
|
let args = strs_to_strings(&[
|
|
|
|
|
|
"apply_patch",
|
|
|
|
|
|
r#"*** Begin Patch
|
|
|
|
|
|
*** Add File: foo
|
|
|
|
|
|
+hi
|
|
|
|
|
|
*** End Patch
|
|
|
|
|
|
"#,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
match maybe_parse_apply_patch(&args) {
|
|
|
|
|
|
MaybeApplyPatch::Body(hunks) => {
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
hunks,
|
|
|
|
|
|
vec![Hunk::AddFile {
|
|
|
|
|
|
path: PathBuf::from("foo"),
|
|
|
|
|
|
contents: "hi\n".to_string()
|
|
|
|
|
|
}]
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
result => panic!("expected MaybeApplyPatch::Body got {:?}", result),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_heredoc() {
|
|
|
|
|
|
let args = strs_to_strings(&[
|
|
|
|
|
|
"bash",
|
|
|
|
|
|
"-lc",
|
|
|
|
|
|
r#"apply_patch <<'PATCH'
|
|
|
|
|
|
*** Begin Patch
|
|
|
|
|
|
*** Add File: foo
|
|
|
|
|
|
+hi
|
|
|
|
|
|
*** End Patch
|
|
|
|
|
|
PATCH"#,
|
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
|
|
match maybe_parse_apply_patch(&args) {
|
|
|
|
|
|
MaybeApplyPatch::Body(hunks) => {
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
hunks,
|
|
|
|
|
|
vec![Hunk::AddFile {
|
|
|
|
|
|
path: PathBuf::from("foo"),
|
|
|
|
|
|
contents: "hi\n".to_string()
|
|
|
|
|
|
}]
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
result => panic!("expected MaybeApplyPatch::Body got {:?}", result),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_add_file_hunk_creates_file_with_contents() {
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("add.txt");
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Add File: {}
|
|
|
|
|
|
+ab
|
|
|
|
|
|
+cd"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
// Verify expected stdout and stderr outputs.
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let stderr_str = String::from_utf8(stderr).unwrap();
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nA {}\n",
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
assert_eq!(stderr_str, "");
|
|
|
|
|
|
let contents = fs::read_to_string(path).unwrap();
|
|
|
|
|
|
assert_eq!(contents, "ab\ncd\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_delete_file_hunk_removes_file() {
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("del.txt");
|
|
|
|
|
|
fs::write(&path, "x").unwrap();
|
|
|
|
|
|
let patch = wrap_patch(&format!("*** Delete File: {}", path.display()));
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let stderr_str = String::from_utf8(stderr).unwrap();
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nD {}\n",
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
assert_eq!(stderr_str, "");
|
|
|
|
|
|
assert!(!path.exists());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_update_file_hunk_modifies_content() {
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("update.txt");
|
|
|
|
|
|
fs::write(&path, "foo\nbar\n").unwrap();
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
foo
|
|
|
|
|
|
-bar
|
|
|
|
|
|
+baz"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
// Validate modified file contents and expected stdout/stderr.
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let stderr_str = String::from_utf8(stderr).unwrap();
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nM {}\n",
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
assert_eq!(stderr_str, "");
|
|
|
|
|
|
let contents = fs::read_to_string(&path).unwrap();
|
|
|
|
|
|
assert_eq!(contents, "foo\nbaz\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_update_file_hunk_can_move_file() {
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let src = dir.path().join("src.txt");
|
|
|
|
|
|
let dest = dir.path().join("dst.txt");
|
|
|
|
|
|
fs::write(&src, "line\n").unwrap();
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
*** Move to: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
-line
|
|
|
|
|
|
+line2"#,
|
|
|
|
|
|
src.display(),
|
|
|
|
|
|
dest.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
// Validate move semantics and expected stdout/stderr.
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let stderr_str = String::from_utf8(stderr).unwrap();
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nM {}\n",
|
|
|
|
|
|
dest.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
assert_eq!(stderr_str, "");
|
|
|
|
|
|
assert!(!src.exists());
|
|
|
|
|
|
let contents = fs::read_to_string(&dest).unwrap();
|
|
|
|
|
|
assert_eq!(contents, "line2\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// Verify that a single `Update File` hunk with multiple change chunks can update different
|
|
|
|
|
|
/// parts of a file and that the file is listed only once in the summary.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_multiple_update_chunks_apply_to_single_file() {
|
|
|
|
|
|
// Start with a file containing four lines.
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("multi.txt");
|
|
|
|
|
|
fs::write(&path, "foo\nbar\nbaz\nqux\n").unwrap();
|
|
|
|
|
|
// Construct an update patch with two separate change chunks.
|
|
|
|
|
|
// The first chunk uses the line `foo` as context and transforms `bar` into `BAR`.
|
|
|
|
|
|
// The second chunk uses `baz` as context and transforms `qux` into `QUX`.
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
foo
|
|
|
|
|
|
-bar
|
|
|
|
|
|
+BAR
|
|
|
|
|
|
@@
|
|
|
|
|
|
baz
|
|
|
|
|
|
-qux
|
|
|
|
|
|
+QUX"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let stderr_str = String::from_utf8(stderr).unwrap();
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nM {}\n",
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
assert_eq!(stderr_str, "");
|
|
|
|
|
|
let contents = fs::read_to_string(&path).unwrap();
|
|
|
|
|
|
assert_eq!(contents, "foo\nBAR\nbaz\nQUX\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// A more involved `Update File` hunk that exercises additions, deletions and
|
|
|
|
|
|
/// replacements in separate chunks that appear in non‑adjacent parts of the
|
|
|
|
|
|
/// file. Verifies that all edits are applied and that the summary lists the
|
|
|
|
|
|
/// file only once.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_update_file_hunk_interleaved_changes() {
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("interleaved.txt");
|
|
|
|
|
|
|
|
|
|
|
|
// Original file: six numbered lines.
|
|
|
|
|
|
fs::write(&path, "a\nb\nc\nd\ne\nf\n").unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// Patch performs:
|
|
|
|
|
|
// • Replace `b` → `B`
|
|
|
|
|
|
// • Replace `e` → `E` (using surrounding context)
|
|
|
|
|
|
// • Append new line `g` at the end‑of‑file
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
a
|
|
|
|
|
|
-b
|
|
|
|
|
|
+B
|
|
|
|
|
|
@@
|
|
|
|
|
|
c
|
|
|
|
|
|
d
|
|
|
|
|
|
-e
|
|
|
|
|
|
+E
|
|
|
|
|
|
@@
|
|
|
|
|
|
f
|
|
|
|
|
|
+g
|
|
|
|
|
|
*** End of File"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let stderr_str = String::from_utf8(stderr).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nM {}\n",
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
assert_eq!(stderr_str, "");
|
|
|
|
|
|
|
|
|
|
|
|
let contents = fs::read_to_string(&path).unwrap();
|
|
|
|
|
|
assert_eq!(contents, "a\nB\nc\nd\nE\nf\ng\n");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-25 16:01:58 -07:00
|
|
|
|
/// Ensure that patches authored with ASCII characters can update lines that
|
|
|
|
|
|
/// contain typographic Unicode punctuation (e.g. EN DASH, NON-BREAKING
|
|
|
|
|
|
/// HYPHEN). Historically `git apply` succeeds in such scenarios but our
|
|
|
|
|
|
/// internal matcher failed requiring an exact byte-for-byte match. The
|
|
|
|
|
|
/// fuzzy-matching pass that normalises common punctuation should now bridge
|
|
|
|
|
|
/// the gap.
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_update_line_with_unicode_dash() {
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("unicode.py");
|
|
|
|
|
|
|
|
|
|
|
|
// Original line contains EN DASH (\u{2013}) and NON-BREAKING HYPHEN (\u{2011}).
|
|
|
|
|
|
let original = "import asyncio # local import \u{2013} avoids top\u{2011}level dep\n";
|
|
|
|
|
|
std::fs::write(&path, original).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// Patch uses plain ASCII dash / hyphen.
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
-import asyncio # local import - avoids top-level dep
|
|
|
|
|
|
+import asyncio # HELLO"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// File should now contain the replaced comment.
|
|
|
|
|
|
let expected = "import asyncio # HELLO\n";
|
|
|
|
|
|
let contents = std::fs::read_to_string(&path).unwrap();
|
|
|
|
|
|
assert_eq!(contents, expected);
|
|
|
|
|
|
|
|
|
|
|
|
// Ensure success summary lists the file as modified.
|
|
|
|
|
|
let stdout_str = String::from_utf8(stdout).unwrap();
|
|
|
|
|
|
let expected_out = format!(
|
|
|
|
|
|
"Success. Updated the following files:\nM {}\n",
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
assert_eq!(stdout_str, expected_out);
|
|
|
|
|
|
|
|
|
|
|
|
// No stderr expected.
|
|
|
|
|
|
assert_eq!(String::from_utf8(stderr).unwrap(), "");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_unified_diff() {
|
|
|
|
|
|
// Start with a file containing four lines.
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("multi.txt");
|
|
|
|
|
|
fs::write(&path, "foo\nbar\nbaz\nqux\n").unwrap();
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
foo
|
|
|
|
|
|
-bar
|
|
|
|
|
|
+BAR
|
|
|
|
|
|
@@
|
|
|
|
|
|
baz
|
|
|
|
|
|
-qux
|
|
|
|
|
|
+QUX"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
let patch = parse_patch(&patch).unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let update_file_chunks = match patch.as_slice() {
|
|
|
|
|
|
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
|
|
|
|
|
_ => panic!("Expected a single UpdateFile hunk"),
|
|
|
|
|
|
};
|
|
|
|
|
|
let diff = unified_diff_from_chunks(&path, update_file_chunks).unwrap();
|
|
|
|
|
|
let expected_diff = r#"@@ -1,4 +1,4 @@
|
|
|
|
|
|
foo
|
|
|
|
|
|
-bar
|
|
|
|
|
|
+BAR
|
|
|
|
|
|
baz
|
|
|
|
|
|
-qux
|
|
|
|
|
|
+QUX
|
|
|
|
|
|
"#;
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let expected = ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff: expected_diff.to_string(),
|
|
|
|
|
|
content: "foo\nBAR\nbaz\nQUX\n".to_string(),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(expected, diff);
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_unified_diff_first_line_replacement() {
|
|
|
|
|
|
// Replace the very first line of the file.
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("first.txt");
|
|
|
|
|
|
fs::write(&path, "foo\nbar\nbaz\n").unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
-foo
|
|
|
|
|
|
+FOO
|
|
|
|
|
|
bar
|
|
|
|
|
|
"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
let patch = parse_patch(&patch).unwrap();
|
|
|
|
|
|
let chunks = match patch.as_slice() {
|
|
|
|
|
|
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
|
|
|
|
|
_ => panic!("Expected a single UpdateFile hunk"),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
|
|
|
|
|
|
let expected_diff = r#"@@ -1,2 +1,2 @@
|
|
|
|
|
|
-foo
|
|
|
|
|
|
+FOO
|
|
|
|
|
|
bar
|
|
|
|
|
|
"#;
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let expected = ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff: expected_diff.to_string(),
|
|
|
|
|
|
content: "FOO\nbar\nbaz\n".to_string(),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(expected, diff);
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_unified_diff_last_line_replacement() {
|
|
|
|
|
|
// Replace the very last line of the file.
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("last.txt");
|
|
|
|
|
|
fs::write(&path, "foo\nbar\nbaz\n").unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
foo
|
|
|
|
|
|
bar
|
|
|
|
|
|
-baz
|
|
|
|
|
|
+BAZ
|
|
|
|
|
|
"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
let patch = parse_patch(&patch).unwrap();
|
|
|
|
|
|
let chunks = match patch.as_slice() {
|
|
|
|
|
|
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
|
|
|
|
|
_ => panic!("Expected a single UpdateFile hunk"),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
|
|
|
|
|
|
let expected_diff = r#"@@ -2,2 +2,2 @@
|
|
|
|
|
|
bar
|
|
|
|
|
|
-baz
|
|
|
|
|
|
+BAZ
|
|
|
|
|
|
"#;
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let expected = ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff: expected_diff.to_string(),
|
|
|
|
|
|
content: "foo\nbar\nBAZ\n".to_string(),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(expected, diff);
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_unified_diff_insert_at_eof() {
|
|
|
|
|
|
// Insert a new line at end‑of‑file.
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("insert.txt");
|
|
|
|
|
|
fs::write(&path, "foo\nbar\nbaz\n").unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
let patch = wrap_patch(&format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
+quux
|
|
|
|
|
|
*** End of File
|
|
|
|
|
|
"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
));
|
|
|
|
|
|
|
|
|
|
|
|
let patch = parse_patch(&patch).unwrap();
|
|
|
|
|
|
let chunks = match patch.as_slice() {
|
|
|
|
|
|
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
|
|
|
|
|
_ => panic!("Expected a single UpdateFile hunk"),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
|
|
|
|
|
|
let expected_diff = r#"@@ -3 +3,2 @@
|
|
|
|
|
|
baz
|
|
|
|
|
|
+quux
|
|
|
|
|
|
"#;
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let expected = ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff: expected_diff.to_string(),
|
|
|
|
|
|
content: "foo\nbar\nbaz\nquux\n".to_string(),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(expected, diff);
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn test_unified_diff_interleaved_changes() {
|
|
|
|
|
|
// Original file with six lines.
|
|
|
|
|
|
let dir = tempdir().unwrap();
|
|
|
|
|
|
let path = dir.path().join("interleaved.txt");
|
|
|
|
|
|
fs::write(&path, "a\nb\nc\nd\ne\nf\n").unwrap();
|
|
|
|
|
|
|
|
|
|
|
|
// Patch replaces two separate lines and appends a new one at EOF using
|
|
|
|
|
|
// three distinct chunks.
|
|
|
|
|
|
let patch_body = format!(
|
|
|
|
|
|
r#"*** Update File: {}
|
|
|
|
|
|
@@
|
|
|
|
|
|
a
|
|
|
|
|
|
-b
|
|
|
|
|
|
+B
|
|
|
|
|
|
@@
|
|
|
|
|
|
d
|
|
|
|
|
|
-e
|
|
|
|
|
|
+E
|
|
|
|
|
|
@@
|
|
|
|
|
|
f
|
|
|
|
|
|
+g
|
|
|
|
|
|
*** End of File"#,
|
|
|
|
|
|
path.display()
|
|
|
|
|
|
);
|
|
|
|
|
|
let patch = wrap_patch(&patch_body);
|
|
|
|
|
|
|
|
|
|
|
|
// Extract chunks then build the unified diff.
|
|
|
|
|
|
let parsed = parse_patch(&patch).unwrap();
|
|
|
|
|
|
let chunks = match parsed.as_slice() {
|
|
|
|
|
|
[Hunk::UpdateFile { chunks, .. }] => chunks,
|
|
|
|
|
|
_ => panic!("Expected a single UpdateFile hunk"),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
let diff = unified_diff_from_chunks(&path, chunks).unwrap();
|
|
|
|
|
|
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let expected_diff = r#"@@ -1,6 +1,7 @@
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
a
|
|
|
|
|
|
-b
|
|
|
|
|
|
+B
|
|
|
|
|
|
c
|
|
|
|
|
|
d
|
|
|
|
|
|
-e
|
|
|
|
|
|
+E
|
|
|
|
|
|
f
|
|
|
|
|
|
+g
|
|
|
|
|
|
"#;
|
|
|
|
|
|
|
2025-04-28 21:15:41 -07:00
|
|
|
|
let expected = ApplyPatchFileUpdate {
|
|
|
|
|
|
unified_diff: expected_diff.to_string(),
|
|
|
|
|
|
content: "a\nB\nc\nd\nE\nf\ng\n".to_string(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
|
assert_eq!(expected, diff);
|
|
|
|
|
|
|
|
|
|
|
|
let mut stdout = Vec::new();
|
|
|
|
|
|
let mut stderr = Vec::new();
|
|
|
|
|
|
apply_patch(&patch, &mut stdout, &mut stderr).unwrap();
|
|
|
|
|
|
let contents = fs::read_to_string(path).unwrap();
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
contents,
|
|
|
|
|
|
r#"a
|
|
|
|
|
|
B
|
|
|
|
|
|
c
|
|
|
|
|
|
d
|
|
|
|
|
|
E
|
|
|
|
|
|
f
|
|
|
|
|
|
g
|
|
|
|
|
|
"#
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|