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
|
|
|
|
use std::io::stdin;
|
|
|
|
|
|
use std::io::stdout;
|
|
|
|
|
|
use std::io::Write;
|
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
|
|
|
|
|
|
|
use codex_core::config::Config;
|
2025-04-27 21:47:50 -07:00
|
|
|
|
use codex_core::config::ConfigOverrides;
|
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
|
|
|
|
use codex_core::protocol;
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
use codex_core::protocol::AskForApproval;
|
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
|
|
|
|
use codex_core::protocol::FileChange;
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
use codex_core::protocol::SandboxPolicy;
|
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
|
|
|
|
use codex_core::util::is_inside_git_repo;
|
|
|
|
|
|
use codex_core::util::notify_on_sigint;
|
|
|
|
|
|
use codex_core::Codex;
|
|
|
|
|
|
use owo_colors::OwoColorize;
|
|
|
|
|
|
use owo_colors::Style;
|
|
|
|
|
|
use tokio::io::AsyncBufReadExt;
|
|
|
|
|
|
use tokio::io::BufReader;
|
|
|
|
|
|
use tokio::io::Lines;
|
|
|
|
|
|
use tokio::io::Stdin;
|
|
|
|
|
|
use tokio::sync::Notify;
|
|
|
|
|
|
use tracing::debug;
|
|
|
|
|
|
use tracing_subscriber::EnvFilter;
|
|
|
|
|
|
|
|
|
|
|
|
mod cli;
|
|
|
|
|
|
pub use cli::Cli;
|
|
|
|
|
|
|
|
|
|
|
|
/// Initialize the global logger once at startup based on the `--verbose` flag.
|
|
|
|
|
|
fn init_logger(verbose: u8, allow_ansi: bool) {
|
|
|
|
|
|
// Map -v occurrences to explicit log levels:
|
|
|
|
|
|
// 0 → warn (default)
|
|
|
|
|
|
// 1 → info
|
|
|
|
|
|
// 2 → debug
|
|
|
|
|
|
// ≥3 → trace
|
|
|
|
|
|
|
|
|
|
|
|
let default_level = match verbose {
|
|
|
|
|
|
0 => "warn",
|
|
|
|
|
|
1 => "info",
|
|
|
|
|
|
2 => "codex=debug",
|
|
|
|
|
|
_ => "codex=trace",
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// Only initialize the logger once – repeated calls are ignored. `try_init` will return an
|
|
|
|
|
|
// error if another crate (like tests) initialized it first, which we can safely ignore.
|
|
|
|
|
|
// By default `tracing_subscriber::fmt()` writes formatted logs to stderr. That is fine when
|
|
|
|
|
|
// running the CLI manually but in our smoke tests we capture **stdout** (via `assert_cmd`) and
|
|
|
|
|
|
// ignore stderr. As a result none of the `tracing::info!` banners or warnings show up in the
|
|
|
|
|
|
// recorded output making it much harder to debug live runs.
|
|
|
|
|
|
|
|
|
|
|
|
// Switch the logger's writer to stdout so both human runs and the integration tests see the
|
|
|
|
|
|
// same stream. Disable ANSI colors because the binary already prints plain text and color
|
|
|
|
|
|
// escape codes make predicate matching brittle.
|
|
|
|
|
|
let _ = tracing_subscriber::fmt()
|
|
|
|
|
|
.with_env_filter(
|
|
|
|
|
|
EnvFilter::try_from_default_env()
|
|
|
|
|
|
.or_else(|_| EnvFilter::try_new(default_level))
|
|
|
|
|
|
.unwrap(),
|
|
|
|
|
|
)
|
|
|
|
|
|
.with_ansi(allow_ansi)
|
|
|
|
|
|
.with_writer(std::io::stdout)
|
|
|
|
|
|
.try_init();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub async fn run_main(cli: Cli) -> anyhow::Result<()> {
|
|
|
|
|
|
let ctrl_c = notify_on_sigint();
|
|
|
|
|
|
|
|
|
|
|
|
// Abort early when the user runs Codex outside a Git repository unless
|
|
|
|
|
|
// they explicitly acknowledged the risks with `--allow-no-git-exec`.
|
|
|
|
|
|
if !cli.allow_no_git_exec && !is_inside_git_repo() {
|
|
|
|
|
|
eprintln!(
|
|
|
|
|
|
"We recommend running codex inside a git repository. \
|
|
|
|
|
|
If you understand the risks, you can proceed with \
|
|
|
|
|
|
`--allow-no-git-exec`."
|
|
|
|
|
|
);
|
|
|
|
|
|
std::process::exit(1);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Initialize logging before any other work so early errors are captured.
|
|
|
|
|
|
init_logger(cli.verbose, !cli.no_ansi);
|
|
|
|
|
|
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
let (sandbox_policy, approval_policy) = if cli.full_auto {
|
|
|
|
|
|
(
|
|
|
|
|
|
Some(SandboxPolicy::new_full_auto_policy()),
|
|
|
|
|
|
Some(AskForApproval::OnFailure),
|
|
|
|
|
|
)
|
|
|
|
|
|
} else {
|
2025-04-29 18:42:52 -07:00
|
|
|
|
let sandbox_policy = cli.sandbox.permissions.clone().map(Into::into);
|
|
|
|
|
|
(sandbox_policy, cli.approval_policy.map(Into::into))
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-04-27 21:47:50 -07:00
|
|
|
|
// Load config file and apply CLI overrides (model & approval policy)
|
|
|
|
|
|
let overrides = ConfigOverrides {
|
|
|
|
|
|
model: cli.model.clone(),
|
fix: overhaul SandboxPolicy and config loading in Rust (#732)
Previous to this PR, `SandboxPolicy` was a bit difficult to work with:
https://github.com/openai/codex/blob/237f8a11e11fdcc793a09e787e48215676d9b95b/codex-rs/core/src/protocol.rs#L98-L108
Specifically:
* It was an `enum` and therefore options were mutually exclusive as
opposed to additive.
* It defined things in terms of what the agent _could not_ do as opposed
to what they _could_ do. This made things hard to support because we
would prefer to build up a sandbox config by starting with something
extremely restrictive and only granting permissions for things the user
as explicitly allowed.
This PR changes things substantially by redefining the policy in terms
of two concepts:
* A `SandboxPermission` enum that defines permissions that can be
granted to the agent/sandbox.
* A `SandboxPolicy` that internally stores a `Vec<SandboxPermission>`,
but externally exposes a simpler API that can be used to configure
Seatbelt/Landlock.
Previous to this PR, we supported a `--sandbox` flag that effectively
mapped to an enum value in `SandboxPolicy`. Though now that
`SandboxPolicy` is a wrapper around `Vec<SandboxPermission>`, the single
`--sandbox` flag no longer makes sense. While I could have turned it
into a flag that the user can specify multiple times, I think the
current values to use with such a flag are long and potentially messy,
so for the moment, I have dropped support for `--sandbox` altogether and
we can bring it back once we have figured out the naming thing.
Since `--sandbox` is gone, users now have to specify `--full-auto` to
get a sandbox that allows writes in `cwd`. Admittedly, there is no clean
way to specify the equivalent of `--full-auto` in your `config.toml`
right now, so we will have to revisit that, as well.
Because `Config` presents a `SandboxPolicy` field and `SandboxPolicy`
changed considerably, I had to overhaul how config loading works, as
well. There are now two distinct concepts, `ConfigToml` and `Config`:
* `ConfigToml` is the deserialization of `~/.codex/config.toml`. As one
might expect, every field is `Optional` and it is `#[derive(Deserialize,
Default)]`. Consistent use of `Optional` makes it clear what the user
has specified explicitly.
* `Config` is the "normalized config" and is produced by merging
`ConfigToml` with `ConfigOverrides`. Where `ConfigToml` contains a raw
`Option<Vec<SandboxPermission>>`, `Config` presents only the final
`SandboxPolicy`.
The changes to `core/src/exec.rs` and `core/src/linux.rs` merit extra
special attention to ensure we are faithfully mapping the
`SandboxPolicy` to the Seatbelt and Landlock configs, respectively.
Also, take note that `core/src/seatbelt_readonly_policy.sbpl` has been
renamed to `codex-rs/core/src/seatbelt_base_policy.sbpl` and that
`(allow file-read*)` has been removed from the `.sbpl` file as now this
is added to the policy in `core/src/exec.rs` when
`sandbox_policy.has_full_disk_read_access()` is `true`.
2025-04-29 15:01:16 -07:00
|
|
|
|
approval_policy,
|
|
|
|
|
|
sandbox_policy,
|
2025-04-28 15:39:34 -07:00
|
|
|
|
disable_response_storage: if cli.disable_response_storage {
|
|
|
|
|
|
Some(true)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
None
|
|
|
|
|
|
},
|
2025-04-27 21:47:50 -07:00
|
|
|
|
};
|
|
|
|
|
|
let config = Config::load_with_overrides(overrides)?;
|
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
|
|
|
|
|
|
|
|
|
|
codex_main(cli, config, ctrl_c).await
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-04-27 21:47:50 -07:00
|
|
|
|
async fn codex_main(cli: Cli, cfg: Config, ctrl_c: Arc<Notify>) -> anyhow::Result<()> {
|
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 mut builder = Codex::builder();
|
|
|
|
|
|
if let Some(path) = cli.record_submissions {
|
|
|
|
|
|
builder = builder.record_submissions(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
if let Some(path) = cli.record_events {
|
|
|
|
|
|
builder = builder.record_events(path);
|
|
|
|
|
|
}
|
|
|
|
|
|
let codex = builder.spawn(Arc::clone(&ctrl_c))?;
|
|
|
|
|
|
let init_id = random_id();
|
|
|
|
|
|
let init = protocol::Submission {
|
|
|
|
|
|
id: init_id.clone(),
|
|
|
|
|
|
op: protocol::Op::ConfigureSession {
|
2025-04-27 21:47:50 -07:00
|
|
|
|
model: cfg.model,
|
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
|
|
|
|
instructions: cfg.instructions,
|
2025-04-27 21:47:50 -07:00
|
|
|
|
approval_policy: cfg.approval_policy,
|
|
|
|
|
|
sandbox_policy: cfg.sandbox_policy,
|
2025-04-28 15:39:34 -07:00
|
|
|
|
disable_response_storage: cfg.disable_response_storage,
|
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
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
out(
|
|
|
|
|
|
"initializing session",
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::User,
|
|
|
|
|
|
);
|
|
|
|
|
|
codex.submit(init).await?;
|
|
|
|
|
|
|
|
|
|
|
|
// init
|
|
|
|
|
|
loop {
|
|
|
|
|
|
out(
|
|
|
|
|
|
"waiting for session initialization",
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::User,
|
|
|
|
|
|
);
|
|
|
|
|
|
let event = codex.next_event().await?;
|
|
|
|
|
|
if event.id == init_id {
|
|
|
|
|
|
if let protocol::EventMsg::Error { message } = event.msg {
|
|
|
|
|
|
anyhow::bail!("Error during initialization: {message}");
|
|
|
|
|
|
} else {
|
|
|
|
|
|
out(
|
|
|
|
|
|
"session initialized",
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::User,
|
|
|
|
|
|
);
|
|
|
|
|
|
break;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// run loop
|
|
|
|
|
|
let mut reader = InputReader::new(ctrl_c.clone());
|
|
|
|
|
|
loop {
|
2025-04-27 21:47:50 -07:00
|
|
|
|
let text = match &cli.prompt {
|
|
|
|
|
|
Some(input) => input.clone(),
|
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
|
|
|
|
None => match reader.request_input().await? {
|
|
|
|
|
|
Some(input) => input,
|
|
|
|
|
|
None => {
|
|
|
|
|
|
// ctrl + d
|
|
|
|
|
|
println!();
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
if text.trim().is_empty() {
|
|
|
|
|
|
continue;
|
|
|
|
|
|
}
|
|
|
|
|
|
// Interpret certain single‑word commands as immediate termination requests.
|
|
|
|
|
|
let trimmed = text.trim();
|
|
|
|
|
|
if trimmed == "q" {
|
|
|
|
|
|
// Exit gracefully.
|
|
|
|
|
|
println!("Exiting…");
|
|
|
|
|
|
return Ok(());
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let sub = protocol::Submission {
|
|
|
|
|
|
id: random_id(),
|
|
|
|
|
|
op: protocol::Op::UserInput {
|
|
|
|
|
|
items: vec![protocol::InputItem::Text { text }],
|
|
|
|
|
|
},
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
out(
|
|
|
|
|
|
"sending request to model",
|
|
|
|
|
|
MessagePriority::TaskProgress,
|
|
|
|
|
|
MessageActor::User,
|
|
|
|
|
|
);
|
|
|
|
|
|
codex.submit(sub).await?;
|
|
|
|
|
|
|
|
|
|
|
|
// Wait for agent events **or** user interrupts (Ctrl+C).
|
|
|
|
|
|
'inner: loop {
|
|
|
|
|
|
// Listen for either the next agent event **or** a SIGINT notification. Using
|
|
|
|
|
|
// `tokio::select!` allows the user to cancel a long‑running request that would
|
|
|
|
|
|
// otherwise leave the CLI stuck waiting for a server response.
|
|
|
|
|
|
let event = {
|
|
|
|
|
|
let interrupted = ctrl_c.notified();
|
|
|
|
|
|
tokio::select! {
|
|
|
|
|
|
_ = interrupted => {
|
|
|
|
|
|
// Forward an interrupt to the agent so it can abort any in‑flight task.
|
|
|
|
|
|
let _ = codex
|
|
|
|
|
|
.submit(protocol::Submission {
|
|
|
|
|
|
id: random_id(),
|
|
|
|
|
|
op: protocol::Op::Interrupt,
|
|
|
|
|
|
})
|
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
|
|
// Exit the inner loop and return to the main input prompt. The agent
|
|
|
|
|
|
// will emit a `TurnInterrupted` (Error) event which is drained later.
|
|
|
|
|
|
break 'inner;
|
|
|
|
|
|
}
|
|
|
|
|
|
res = codex.next_event() => res?
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
debug!(?event, "Got event");
|
|
|
|
|
|
let id = event.id;
|
|
|
|
|
|
match event.msg {
|
|
|
|
|
|
protocol::EventMsg::Error { message } => {
|
|
|
|
|
|
println!("Error: {message}");
|
|
|
|
|
|
break 'inner;
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::TaskComplete => break 'inner,
|
|
|
|
|
|
protocol::EventMsg::AgentMessage { message } => {
|
|
|
|
|
|
out(&message, MessagePriority::UserMessage, MessageActor::Agent)
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::SessionConfigured { model } => {
|
|
|
|
|
|
debug!(model, "Session initialized");
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::ExecApprovalRequest {
|
|
|
|
|
|
command,
|
|
|
|
|
|
cwd,
|
|
|
|
|
|
reason,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let reason_str = reason
|
|
|
|
|
|
.as_deref()
|
|
|
|
|
|
.map(|r| format!(" [{r}]"))
|
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
|
|
let prompt = format!(
|
|
|
|
|
|
"approve command in {} {}{} (y/N): ",
|
|
|
|
|
|
cwd.display(),
|
|
|
|
|
|
command.join(" "),
|
|
|
|
|
|
reason_str
|
|
|
|
|
|
);
|
|
|
|
|
|
let decision = request_user_approval2(prompt)?;
|
|
|
|
|
|
let sub = protocol::Submission {
|
|
|
|
|
|
id: random_id(),
|
|
|
|
|
|
op: protocol::Op::ExecApproval { id, decision },
|
|
|
|
|
|
};
|
|
|
|
|
|
out(
|
|
|
|
|
|
"submitting command approval",
|
|
|
|
|
|
MessagePriority::TaskProgress,
|
|
|
|
|
|
MessageActor::User,
|
|
|
|
|
|
);
|
|
|
|
|
|
codex.submit(sub).await?;
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::ApplyPatchApprovalRequest {
|
|
|
|
|
|
changes,
|
|
|
|
|
|
reason: _,
|
|
|
|
|
|
grant_root: _,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let file_list = changes
|
|
|
|
|
|
.keys()
|
|
|
|
|
|
.map(|path| path.to_string_lossy().to_string())
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
.join(", ");
|
|
|
|
|
|
let request =
|
|
|
|
|
|
format!("approve apply_patch that will touch? {file_list} (y/N): ");
|
|
|
|
|
|
let decision = request_user_approval2(request)?;
|
|
|
|
|
|
let sub = protocol::Submission {
|
|
|
|
|
|
id: random_id(),
|
|
|
|
|
|
op: protocol::Op::PatchApproval { id, decision },
|
|
|
|
|
|
};
|
|
|
|
|
|
out(
|
|
|
|
|
|
"submitting patch approval",
|
|
|
|
|
|
MessagePriority::UserMessage,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
codex.submit(sub).await?;
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::ExecCommandBegin {
|
|
|
|
|
|
command,
|
|
|
|
|
|
cwd,
|
|
|
|
|
|
call_id: _,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
out(
|
|
|
|
|
|
&format!("running command: '{}' in '{}'", command.join(" "), cwd),
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::ExecCommandEnd {
|
|
|
|
|
|
stdout,
|
|
|
|
|
|
stderr,
|
|
|
|
|
|
exit_code,
|
|
|
|
|
|
call_id: _,
|
|
|
|
|
|
} => {
|
|
|
|
|
|
let msg = if exit_code == 0 {
|
|
|
|
|
|
"command completed (exit 0)".to_string()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Prefer stderr but fall back to stdout if empty.
|
|
|
|
|
|
let err_snippet = if !stderr.trim().is_empty() {
|
|
|
|
|
|
stderr.trim()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
stdout.trim()
|
|
|
|
|
|
};
|
|
|
|
|
|
format!("command failed (exit {exit_code}): {err_snippet}")
|
|
|
|
|
|
};
|
|
|
|
|
|
out(&msg, MessagePriority::BackgroundEvent, MessageActor::Agent);
|
|
|
|
|
|
out(
|
|
|
|
|
|
"sending results to model",
|
|
|
|
|
|
MessagePriority::TaskProgress,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::PatchApplyBegin { changes, .. } => {
|
|
|
|
|
|
// Emit PatchApplyBegin so the front‑end can show progress.
|
|
|
|
|
|
let summary = changes
|
|
|
|
|
|
.iter()
|
|
|
|
|
|
.map(|(path, change)| match change {
|
|
|
|
|
|
FileChange::Add { .. } => format!("A {}", path.display()),
|
|
|
|
|
|
FileChange::Delete => format!("D {}", path.display()),
|
|
|
|
|
|
FileChange::Update { .. } => format!("M {}", path.display()),
|
|
|
|
|
|
})
|
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
|
.join(", ");
|
|
|
|
|
|
|
|
|
|
|
|
out(
|
|
|
|
|
|
&format!("applying patch: {summary}"),
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
protocol::EventMsg::PatchApplyEnd { success, .. } => {
|
|
|
|
|
|
let status = if success { "success" } else { "failed" };
|
|
|
|
|
|
out(
|
|
|
|
|
|
&format!("patch application {status}"),
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
out(
|
|
|
|
|
|
"sending results to model",
|
|
|
|
|
|
MessagePriority::TaskProgress,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
// Broad fallback; if the CLI is unaware of an event type, it will just
|
|
|
|
|
|
// print it as a generic BackgroundEvent.
|
|
|
|
|
|
e => {
|
|
|
|
|
|
out(
|
|
|
|
|
|
&format!("event: {e:?}"),
|
|
|
|
|
|
MessagePriority::BackgroundEvent,
|
|
|
|
|
|
MessageActor::Agent,
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn random_id() -> String {
|
|
|
|
|
|
let id: u64 = rand::random();
|
|
|
|
|
|
id.to_string()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn request_user_approval2(request: String) -> anyhow::Result<protocol::ReviewDecision> {
|
|
|
|
|
|
println!("{}", request);
|
|
|
|
|
|
|
|
|
|
|
|
let mut line = String::new();
|
|
|
|
|
|
stdin().read_line(&mut line)?;
|
|
|
|
|
|
let answer = line.trim().to_ascii_lowercase();
|
|
|
|
|
|
let is_accepted = answer == "y" || answer == "yes";
|
|
|
|
|
|
let decision = if is_accepted {
|
|
|
|
|
|
protocol::ReviewDecision::Approved
|
|
|
|
|
|
} else {
|
|
|
|
|
|
protocol::ReviewDecision::Denied
|
|
|
|
|
|
};
|
|
|
|
|
|
Ok(decision)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
|
|
|
|
enum MessagePriority {
|
|
|
|
|
|
BackgroundEvent,
|
|
|
|
|
|
TaskProgress,
|
|
|
|
|
|
UserMessage,
|
|
|
|
|
|
}
|
|
|
|
|
|
enum MessageActor {
|
|
|
|
|
|
Agent,
|
|
|
|
|
|
User,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl From<MessageActor> for String {
|
|
|
|
|
|
fn from(actor: MessageActor) -> Self {
|
|
|
|
|
|
match actor {
|
|
|
|
|
|
MessageActor::Agent => "codex".to_string(),
|
|
|
|
|
|
MessageActor::User => "user".to_string(),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn out(msg: &str, priority: MessagePriority, actor: MessageActor) {
|
|
|
|
|
|
let actor: String = actor.into();
|
|
|
|
|
|
let style = match priority {
|
|
|
|
|
|
MessagePriority::BackgroundEvent => Style::new().fg_rgb::<127, 127, 127>(),
|
|
|
|
|
|
MessagePriority::TaskProgress => Style::new().fg_rgb::<200, 200, 200>(),
|
|
|
|
|
|
MessagePriority::UserMessage => Style::new().white(),
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
println!("{}> {}", actor.bold(), msg.style(style));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct InputReader {
|
|
|
|
|
|
reader: Lines<BufReader<Stdin>>,
|
|
|
|
|
|
ctrl_c: Arc<Notify>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl InputReader {
|
|
|
|
|
|
pub fn new(ctrl_c: Arc<Notify>) -> Self {
|
|
|
|
|
|
Self {
|
|
|
|
|
|
reader: BufReader::new(tokio::io::stdin()).lines(),
|
|
|
|
|
|
ctrl_c,
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
pub async fn request_input(&mut self) -> std::io::Result<Option<String>> {
|
|
|
|
|
|
print!("user> ");
|
|
|
|
|
|
stdout().flush()?;
|
|
|
|
|
|
let interrupted = self.ctrl_c.notified();
|
|
|
|
|
|
tokio::select! {
|
|
|
|
|
|
line = self.reader.next_line() => {
|
|
|
|
|
|
match line? {
|
|
|
|
|
|
Some(input) => Ok(Some(input.trim().to_string())),
|
|
|
|
|
|
None => Ok(None),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
_ = interrupted => {
|
|
|
|
|
|
println!();
|
|
|
|
|
|
Ok(Some(String::new()))
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|