2025-09-14 14:38:26 -07:00
|
|
|
|
use crate::exec::ExecToolCallOutput;
|
2025-09-11 18:01:25 -04:00
|
|
|
|
use crate::token_data::KnownPlan;
|
|
|
|
|
|
use crate::token_data::PlanType;
|
2025-10-09 17:01:01 +01:00
|
|
|
|
use crate::truncate::truncate_middle;
|
2025-10-17 17:39:37 -07:00
|
|
|
|
use chrono::DateTime;
|
|
|
|
|
|
use chrono::Utc;
|
2025-10-17 11:52:57 -07:00
|
|
|
|
use codex_async_utils::CancelErr;
|
fix: remove mcp-types from app server protocol (#4537)
We continue the separation between `codex app-server` and `codex
mcp-server`.
In particular, we introduce a new crate, `codex-app-server-protocol`,
and migrate `codex-rs/protocol/src/mcp_protocol.rs` into it, renaming it
`codex-rs/app-server-protocol/src/protocol.rs`.
Because `ConversationId` was defined in `mcp_protocol.rs`, we move it
into its own file, `codex-rs/protocol/src/conversation_id.rs`, and
because it is referenced in a ton of places, we have to touch a lot of
files as part of this PR.
We also decide to get away from proper JSON-RPC 2.0 semantics, so we
also introduce `codex-rs/app-server-protocol/src/jsonrpc_lite.rs`, which
is basically the same `JSONRPCMessage` type defined in `mcp-types`
except with all of the `"jsonrpc": "2.0"` removed.
Getting rid of `"jsonrpc": "2.0"` makes our serialization logic
considerably simpler, as we can lean heavier on serde to serialize
directly into the wire format that we use now.
2025-09-30 19:16:26 -07:00
|
|
|
|
use codex_protocol::ConversationId;
|
2025-09-23 15:56:34 -07:00
|
|
|
|
use codex_protocol::protocol::RateLimitSnapshot;
|
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 reqwest::StatusCode;
|
|
|
|
|
|
use serde_json;
|
|
|
|
|
|
use std::io;
|
2025-08-13 15:43:54 -07:00
|
|
|
|
use std::time::Duration;
|
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 thiserror::Error;
|
|
|
|
|
|
use tokio::task::JoinError;
|
|
|
|
|
|
|
|
|
|
|
|
pub type Result<T> = std::result::Result<T, CodexErr>;
|
|
|
|
|
|
|
2025-10-09 17:01:01 +01:00
|
|
|
|
/// Limit UI error messages to a reasonable size while keeping useful context.
|
|
|
|
|
|
const ERROR_MESSAGE_UI_MAX_BYTES: usize = 2 * 1024; // 4 KiB
|
|
|
|
|
|
|
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(Error, Debug)]
|
|
|
|
|
|
pub enum SandboxErr {
|
|
|
|
|
|
/// Error from sandbox execution
|
2025-09-14 14:38:26 -07:00
|
|
|
|
#[error(
|
|
|
|
|
|
"sandbox denied exec error, exit code: {}, stdout: {}, stderr: {}",
|
|
|
|
|
|
.output.exit_code, .output.stdout.text, .output.stderr.text
|
|
|
|
|
|
)]
|
|
|
|
|
|
Denied { output: Box<ExecToolCallOutput> },
|
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
|
|
|
|
|
|
|
|
|
|
/// Error from linux seccomp filter setup
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
|
#[error("seccomp setup error")]
|
|
|
|
|
|
SeccompInstall(#[from] seccompiler::Error),
|
|
|
|
|
|
|
|
|
|
|
|
/// Error from linux seccomp backend
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
|
#[error("seccomp backend error")]
|
|
|
|
|
|
SeccompBackend(#[from] seccompiler::BackendError),
|
|
|
|
|
|
|
2025-04-25 12:56:20 -07:00
|
|
|
|
/// Command timed out
|
|
|
|
|
|
#[error("command timed out")]
|
2025-09-14 14:38:26 -07:00
|
|
|
|
Timeout { output: Box<ExecToolCallOutput> },
|
2025-04-25 12:56:20 -07:00
|
|
|
|
|
|
|
|
|
|
/// Command was killed by a signal
|
|
|
|
|
|
#[error("command was killed by a signal")]
|
|
|
|
|
|
Signal(i32),
|
|
|
|
|
|
|
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
|
|
|
|
/// Error from linux landlock
|
|
|
|
|
|
#[error("Landlock was not able to fully enforce all sandbox rules")]
|
|
|
|
|
|
LandlockRestrict,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Error, Debug)]
|
|
|
|
|
|
pub enum CodexErr {
|
2025-10-17 11:52:57 -07:00
|
|
|
|
#[error("turn aborted")]
|
|
|
|
|
|
TurnAborted,
|
|
|
|
|
|
|
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
|
|
|
|
/// Returned by ResponsesClient when the SSE stream disconnects or errors out **after** the HTTP
|
|
|
|
|
|
/// handshake has succeeded but **before** it finished emitting `response.completed`.
|
|
|
|
|
|
///
|
|
|
|
|
|
/// The Session loop treats this as a transient error and will automatically retry the turn.
|
2025-08-13 15:43:54 -07:00
|
|
|
|
///
|
|
|
|
|
|
/// Optionally includes the requested delay before retrying the turn.
|
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
|
|
|
|
#[error("stream disconnected before completion: {0}")]
|
2025-08-13 15:43:54 -07:00
|
|
|
|
Stream(String, Option<Duration>),
|
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
|
|
|
|
|
2025-10-04 18:40:06 -07:00
|
|
|
|
#[error(
|
|
|
|
|
|
"Codex ran out of room in the model's context window. Start a new conversation or clear earlier history before retrying."
|
|
|
|
|
|
)]
|
|
|
|
|
|
ContextWindowExceeded,
|
|
|
|
|
|
|
chore: introduce ConversationManager as a clearinghouse for all conversations (#2240)
This PR does two things because after I got deep into the first one I
started pulling on the thread to the second:
- Makes `ConversationManager` the place where all in-memory
conversations are created and stored. Previously, `MessageProcessor` in
the `codex-mcp-server` crate was doing this via its `session_map`, but
this is something that should be done in `codex-core`.
- It unwinds the `ctrl_c: tokio::sync::Notify` that was threaded
throughout our code. I think this made sense at one time, but now that
we handle Ctrl-C within the TUI and have a proper `Op::Interrupt` event,
I don't think this was quite right, so I removed it. For `codex exec`
and `codex proto`, we now use `tokio::signal::ctrl_c()` directly, but we
no longer make `Notify` a field of `Codex` or `CodexConversation`.
Changes of note:
- Adds the files `conversation_manager.rs` and `codex_conversation.rs`
to `codex-core`.
- `Codex` and `CodexSpawnOk` are no longer exported from `codex-core`:
other crates must use `CodexConversation` instead (which is created via
`ConversationManager`).
- `core/src/codex_wrapper.rs` has been deleted in favor of
`ConversationManager`.
- `ConversationManager::new_conversation()` returns `NewConversation`,
which is in line with the `new_conversation` tool we want to add to the
MCP server. Note `NewConversation` includes `SessionConfiguredEvent`, so
we eliminate checks in cases like `codex-rs/core/tests/client.rs` to
verify `SessionConfiguredEvent` is the first event because that is now
internal to `ConversationManager`.
- Quite a bit of code was deleted from
`codex-rs/mcp-server/src/message_processor.rs` since it no longer has to
manage multiple conversations itself: it goes through
`ConversationManager` instead.
- `core/tests/live_agent.rs` has been deleted because I had to update a
bunch of tests and all the tests in here were ignored, and I don't think
anyone ever ran them, so this was just technical debt, at this point.
- Removed `notify_on_sigint()` from `util.rs` (and in a follow-up, I
hope to refactor the blandly-named `util.rs` into more descriptive
files).
- In general, I started replacing local variables named `codex` as
`conversation`, where appropriate, though admittedly I didn't do it
through all the integration tests because that would have added a lot of
noise to this PR.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2240).
* #2264
* #2263
* __->__ #2240
2025-08-13 13:38:18 -07:00
|
|
|
|
#[error("no conversation with id: {0}")]
|
2025-09-07 20:22:25 -07:00
|
|
|
|
ConversationNotFound(ConversationId),
|
chore: introduce ConversationManager as a clearinghouse for all conversations (#2240)
This PR does two things because after I got deep into the first one I
started pulling on the thread to the second:
- Makes `ConversationManager` the place where all in-memory
conversations are created and stored. Previously, `MessageProcessor` in
the `codex-mcp-server` crate was doing this via its `session_map`, but
this is something that should be done in `codex-core`.
- It unwinds the `ctrl_c: tokio::sync::Notify` that was threaded
throughout our code. I think this made sense at one time, but now that
we handle Ctrl-C within the TUI and have a proper `Op::Interrupt` event,
I don't think this was quite right, so I removed it. For `codex exec`
and `codex proto`, we now use `tokio::signal::ctrl_c()` directly, but we
no longer make `Notify` a field of `Codex` or `CodexConversation`.
Changes of note:
- Adds the files `conversation_manager.rs` and `codex_conversation.rs`
to `codex-core`.
- `Codex` and `CodexSpawnOk` are no longer exported from `codex-core`:
other crates must use `CodexConversation` instead (which is created via
`ConversationManager`).
- `core/src/codex_wrapper.rs` has been deleted in favor of
`ConversationManager`.
- `ConversationManager::new_conversation()` returns `NewConversation`,
which is in line with the `new_conversation` tool we want to add to the
MCP server. Note `NewConversation` includes `SessionConfiguredEvent`, so
we eliminate checks in cases like `codex-rs/core/tests/client.rs` to
verify `SessionConfiguredEvent` is the first event because that is now
internal to `ConversationManager`.
- Quite a bit of code was deleted from
`codex-rs/mcp-server/src/message_processor.rs` since it no longer has to
manage multiple conversations itself: it goes through
`ConversationManager` instead.
- `core/tests/live_agent.rs` has been deleted because I had to update a
bunch of tests and all the tests in here were ignored, and I don't think
anyone ever ran them, so this was just technical debt, at this point.
- Removed `notify_on_sigint()` from `util.rs` (and in a follow-up, I
hope to refactor the blandly-named `util.rs` into more descriptive
files).
- In general, I started replacing local variables named `codex` as
`conversation`, where appropriate, though admittedly I didn't do it
through all the integration tests because that would have added a lot of
noise to this PR.
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/openai/codex/pull/2240).
* #2264
* #2263
* __->__ #2240
2025-08-13 13:38:18 -07:00
|
|
|
|
|
|
|
|
|
|
#[error("session configured event was not the first event in the stream")]
|
|
|
|
|
|
SessionConfiguredNotFirstEvent,
|
|
|
|
|
|
|
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
|
|
|
|
/// Returned by run_command_stream when the spawned child process timed out (10s).
|
|
|
|
|
|
#[error("timeout waiting for child process to exit")]
|
|
|
|
|
|
Timeout,
|
|
|
|
|
|
|
|
|
|
|
|
/// Returned by run_command_stream when the child could not be spawned (its stdout/stderr pipes
|
|
|
|
|
|
/// could not be captured). Analogous to the previous `CodexError::Spawn` variant.
|
|
|
|
|
|
#[error("spawn failed: child stdout/stderr not captured")]
|
|
|
|
|
|
Spawn,
|
|
|
|
|
|
|
|
|
|
|
|
/// Returned by run_command_stream when the user pressed Ctrl‑C (SIGINT). Session uses this to
|
|
|
|
|
|
/// surface a polite FunctionCallOutput back to the model instead of crashing the CLI.
|
2025-05-08 21:46:06 -07:00
|
|
|
|
#[error("interrupted (Ctrl-C)")]
|
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
|
|
|
|
Interrupted,
|
|
|
|
|
|
|
|
|
|
|
|
/// Unexpected HTTP status code.
|
2025-10-01 15:36:04 -07:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
UnexpectedStatus(UnexpectedResponseError),
|
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
|
|
|
|
|
2025-08-07 18:24:34 -07:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
UsageLimitReached(UsageLimitReachedError),
|
2025-08-07 09:46:13 -07:00
|
|
|
|
|
2025-10-16 14:51:42 -07:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
ResponseStreamFailed(ResponseStreamFailed),
|
|
|
|
|
|
|
|
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
ConnectionFailed(ConnectionFailedError),
|
|
|
|
|
|
|
2025-08-07 18:24:34 -07:00
|
|
|
|
#[error(
|
|
|
|
|
|
"To use Codex with your ChatGPT plan, upgrade to Plus: https://openai.com/chatgpt/pricing."
|
|
|
|
|
|
)]
|
2025-08-07 09:46:13 -07:00
|
|
|
|
UsageNotIncluded,
|
|
|
|
|
|
|
2025-08-07 19:01:53 -07:00
|
|
|
|
#[error("We're currently experiencing high demand, which may cause temporary errors.")]
|
2025-08-07 10:46:43 -07:00
|
|
|
|
InternalServerError,
|
|
|
|
|
|
|
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
|
|
|
|
/// Retry limit exceeded.
|
2025-10-01 15:36:04 -07:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
RetryLimit(RetryLimitReachedError),
|
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
|
|
|
|
|
|
|
|
|
|
/// Agent loop died unexpectedly
|
|
|
|
|
|
#[error("internal error; agent loop died unexpectedly")]
|
|
|
|
|
|
InternalAgentDied,
|
|
|
|
|
|
|
|
|
|
|
|
/// Sandbox error
|
|
|
|
|
|
#[error("sandbox error: {0}")]
|
|
|
|
|
|
Sandbox(#[from] SandboxErr),
|
|
|
|
|
|
|
fix: overhaul how we spawn commands under seccomp/landlock on Linux (#1086)
Historically, we spawned the Seatbelt and Landlock sandboxes in
substantially different ways:
For **Seatbelt**, we would run `/usr/bin/sandbox-exec` with our policy
specified as an arg followed by the original command:
https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/core/src/exec.rs#L147-L219
For **Landlock/Seccomp**, we would do
`tokio::runtime::Builder::new_current_thread()`, _invoke
Landlock/Seccomp APIs to modify the permissions of that new thread_, and
then spawn the command:
https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/core/src/exec_linux.rs#L28-L49
While it is neat that Landlock/Seccomp supports applying a policy to
only one thread without having to apply it to the entire process, it
requires us to maintain two different codepaths and is a bit harder to
reason about. The tipping point was
https://github.com/openai/codex/pull/1061, in which we had to start
building up the `env` in an unexpected way for the existing
Landlock/Seccomp approach to continue to work.
This PR overhauls things so that we do similar things for Mac and Linux.
It turned out that we were already building our own "helper binary"
comparable to Mac's `sandbox-exec` as part of the `cli` crate:
https://github.com/openai/codex/blob/d1de7bb383552e8fadd94be79d65d188e00fd562/codex-rs/cli/Cargo.toml#L10-L12
We originally created this to build a small binary to include with the
Node.js version of the Codex CLI to provide support for Linux
sandboxing.
Though the sticky bit is that, at this point, we still want to deploy
the Rust version of Codex as a single, standalone binary rather than a
CLI and a supporting sandboxing binary. To satisfy this goal, we use
"the arg0 trick," in which we:
* use `std::env::current_exe()` to get the path to the CLI that is
currently running
* use the CLI as the `program` for the `Command`
* set `"codex-linux-sandbox"` as arg0 for the `Command`
A CLI that supports sandboxing should check arg0 at the start of the
program. If it is `"codex-linux-sandbox"`, it must invoke
`codex_linux_sandbox::run_main()`, which runs the CLI as if it were
`codex-linux-sandbox`. When acting as `codex-linux-sandbox`, we make the
appropriate Landlock/Seccomp API calls and then use `execvp(3)` to spawn
the original command, so do _replace_ the process rather than spawn a
subprocess. Incidentally, we do this before starting the Tokio runtime,
so the process should only have one thread when `execvp(3)` is called.
Because the `core` crate that needs to spawn the Linux sandboxing is not
a CLI in its own right, this means that every CLI that includes `core`
and relies on this behavior has to (1) implement it and (2) provide the
path to the sandboxing executable. While the path is almost always
`std::env::current_exe()`, we needed to make this configurable for
integration tests, so `Config` now has a `codex_linux_sandbox_exe:
Option<PathBuf>` property to facilitate threading this through,
introduced in https://github.com/openai/codex/pull/1089.
This common pattern is now captured in
`codex_linux_sandbox::run_with_sandbox()` and all of the `main.rs`
functions that should use it have been updated as part of this PR.
The `codex-linux-sandbox` crate added to the Cargo workspace as part of
this PR now has the bulk of the Landlock/Seccomp logic, which makes
`core` a bit simpler. Indeed, `core/src/exec_linux.rs` and
`core/src/landlock.rs` were removed/ported as part of this PR. I also
moved the unit tests for this code into an integration test,
`linux-sandbox/tests/landlock.rs`, in which I use
`env!("CARGO_BIN_EXE_codex-linux-sandbox")` as the value for
`codex_linux_sandbox_exe` since `std::env::current_exe()` is not
appropriate in that case.
2025-05-23 11:37:07 -07:00
|
|
|
|
#[error("codex-linux-sandbox was required but not provided")]
|
|
|
|
|
|
LandlockSandboxExecutableNotProvided,
|
|
|
|
|
|
|
2025-09-23 13:59:16 -07:00
|
|
|
|
#[error("unsupported operation: {0}")]
|
|
|
|
|
|
UnsupportedOperation(String),
|
|
|
|
|
|
|
chore: refactor tool handling (#4510)
# Tool System Refactor
- Centralizes tool definitions and execution in `core/src/tools/*`:
specs (`spec.rs`), handlers (`handlers/*`), router (`router.rs`),
registry/dispatch (`registry.rs`), and shared context (`context.rs`).
One registry now builds the model-visible tool list and binds handlers.
- Router converts model responses to tool calls; Registry dispatches
with consistent telemetry via `codex-rs/otel` and unified error
handling. Function, Local Shell, MCP, and experimental `unified_exec`
all flow through this path; legacy shell aliases still work.
- Rationale: reduce per‑tool boilerplate, keep spec/handler in sync, and
make adding tools predictable and testable.
Example: `read_file`
- Spec: `core/src/tools/spec.rs` (see `create_read_file_tool`,
registered by `build_specs`).
- Handler: `core/src/tools/handlers/read_file.rs` (absolute `file_path`,
1‑indexed `offset`, `limit`, `L#: ` prefixes, safe truncation).
- E2E test: `core/tests/suite/read_file.rs` validates the tool returns
the requested lines.
## Next steps:
- Decompose `handle_container_exec_with_params`
- Add parallel tool calls
2025-10-03 13:21:06 +01:00
|
|
|
|
#[error("Fatal error: {0}")]
|
|
|
|
|
|
Fatal(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
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
// Automatic conversions for common external error types
|
|
|
|
|
|
// -----------------------------------------------------------------
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
Io(#[from] io::Error),
|
|
|
|
|
|
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
Json(#[from] serde_json::Error),
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
LandlockRuleset(#[from] landlock::RulesetError),
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(target_os = "linux")]
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
LandlockPathFd(#[from] landlock::PathFdError),
|
|
|
|
|
|
|
|
|
|
|
|
#[error(transparent)]
|
|
|
|
|
|
TokioJoin(#[from] JoinError),
|
|
|
|
|
|
|
2025-05-08 21:46:06 -07:00
|
|
|
|
#[error("{0}")]
|
|
|
|
|
|
EnvVar(EnvVarError),
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 11:52:57 -07:00
|
|
|
|
impl From<CancelErr> for CodexErr {
|
|
|
|
|
|
fn from(_: CancelErr) -> Self {
|
|
|
|
|
|
CodexErr::TurnAborted
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-16 14:51:42 -07:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub struct ConnectionFailedError {
|
|
|
|
|
|
pub source: reqwest::Error,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for ConnectionFailedError {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
write!(f, "Connection failed: {}", self.source)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub struct ResponseStreamFailed {
|
|
|
|
|
|
pub source: reqwest::Error,
|
|
|
|
|
|
pub request_id: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for ResponseStreamFailed {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
f,
|
|
|
|
|
|
"Error while reading the server response: {}{}",
|
|
|
|
|
|
self.source,
|
|
|
|
|
|
self.request_id
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|id| format!(", request id: {id}"))
|
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-01 15:36:04 -07:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub struct UnexpectedResponseError {
|
|
|
|
|
|
pub status: StatusCode,
|
|
|
|
|
|
pub body: String,
|
|
|
|
|
|
pub request_id: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for UnexpectedResponseError {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
f,
|
|
|
|
|
|
"unexpected status {}: {}{}",
|
|
|
|
|
|
self.status,
|
|
|
|
|
|
self.body,
|
|
|
|
|
|
self.request_id
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|id| format!(", request id: {id}"))
|
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::error::Error for UnexpectedResponseError {}
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub struct RetryLimitReachedError {
|
|
|
|
|
|
pub status: StatusCode,
|
|
|
|
|
|
pub request_id: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for RetryLimitReachedError {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
write!(
|
|
|
|
|
|
f,
|
|
|
|
|
|
"exceeded retry limit, last status: {}{}",
|
|
|
|
|
|
self.status,
|
|
|
|
|
|
self.request_id
|
|
|
|
|
|
.as_ref()
|
|
|
|
|
|
.map(|id| format!(", request id: {id}"))
|
|
|
|
|
|
.unwrap_or_default()
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 18:24:34 -07:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub struct UsageLimitReachedError {
|
2025-09-11 18:01:25 -04:00
|
|
|
|
pub(crate) plan_type: Option<PlanType>,
|
2025-10-17 17:39:37 -07:00
|
|
|
|
pub(crate) resets_at: Option<DateTime<Utc>>,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
pub(crate) rate_limits: Option<RateLimitSnapshot>,
|
2025-08-07 18:24:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for UsageLimitReachedError {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
2025-09-11 18:01:25 -04:00
|
|
|
|
let message = match self.plan_type.as_ref() {
|
|
|
|
|
|
Some(PlanType::Known(KnownPlan::Plus)) => format!(
|
|
|
|
|
|
"You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing){}",
|
2025-10-17 17:39:37 -07:00
|
|
|
|
retry_suffix_after_or(self.resets_at.as_ref())
|
2025-09-11 18:01:25 -04:00
|
|
|
|
),
|
|
|
|
|
|
Some(PlanType::Known(KnownPlan::Team)) | Some(PlanType::Known(KnownPlan::Business)) => {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"You've hit your usage limit. To get more access now, send a request to your admin{}",
|
2025-10-17 17:39:37 -07:00
|
|
|
|
retry_suffix_after_or(self.resets_at.as_ref())
|
2025-09-11 18:01:25 -04:00
|
|
|
|
)
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
2025-09-11 18:01:25 -04:00
|
|
|
|
Some(PlanType::Known(KnownPlan::Free)) => {
|
2025-09-25 11:10:40 -07:00
|
|
|
|
"You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)."
|
2025-09-11 18:01:25 -04:00
|
|
|
|
.to_string()
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
2025-09-11 18:01:25 -04:00
|
|
|
|
Some(PlanType::Known(KnownPlan::Pro))
|
|
|
|
|
|
| Some(PlanType::Known(KnownPlan::Enterprise))
|
|
|
|
|
|
| Some(PlanType::Known(KnownPlan::Edu)) => format!(
|
|
|
|
|
|
"You've hit your usage limit.{}",
|
2025-10-17 17:39:37 -07:00
|
|
|
|
retry_suffix(self.resets_at.as_ref())
|
2025-09-11 18:01:25 -04:00
|
|
|
|
),
|
|
|
|
|
|
Some(PlanType::Unknown(_)) | None => format!(
|
|
|
|
|
|
"You've hit your usage limit.{}",
|
2025-10-17 17:39:37 -07:00
|
|
|
|
retry_suffix(self.resets_at.as_ref())
|
2025-09-11 18:01:25 -04:00
|
|
|
|
),
|
|
|
|
|
|
};
|
2025-08-25 21:42:10 -07:00
|
|
|
|
|
2025-09-11 18:01:25 -04:00
|
|
|
|
write!(f, "{message}")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:39:37 -07:00
|
|
|
|
fn retry_suffix(resets_at: Option<&DateTime<Utc>>) -> String {
|
|
|
|
|
|
if let Some(secs) = remaining_seconds(resets_at) {
|
2025-09-11 18:01:25 -04:00
|
|
|
|
let reset_duration = format_reset_duration(secs);
|
|
|
|
|
|
format!(" Try again in {reset_duration}.")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
" Try again later.".to_string()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:39:37 -07:00
|
|
|
|
fn retry_suffix_after_or(resets_at: Option<&DateTime<Utc>>) -> String {
|
|
|
|
|
|
if let Some(secs) = remaining_seconds(resets_at) {
|
2025-09-11 18:01:25 -04:00
|
|
|
|
let reset_duration = format_reset_duration(secs);
|
|
|
|
|
|
format!(" or try again in {reset_duration}.")
|
|
|
|
|
|
} else {
|
|
|
|
|
|
" or try again later.".to_string()
|
2025-08-07 18:24:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:39:37 -07:00
|
|
|
|
fn remaining_seconds(resets_at: Option<&DateTime<Utc>>) -> Option<u64> {
|
|
|
|
|
|
let resets_at = resets_at.cloned()?;
|
|
|
|
|
|
let now = now_for_retry();
|
|
|
|
|
|
let secs = resets_at.signed_duration_since(now).num_seconds();
|
|
|
|
|
|
Some(if secs <= 0 { 0 } else { secs as u64 })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
thread_local! {
|
|
|
|
|
|
static NOW_OVERRIDE: std::cell::RefCell<Option<DateTime<Utc>>> =
|
|
|
|
|
|
const { std::cell::RefCell::new(None) };
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
fn now_for_retry() -> DateTime<Utc> {
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
{
|
|
|
|
|
|
if let Some(now) = NOW_OVERRIDE.with(|cell| *cell.borrow()) {
|
|
|
|
|
|
return now;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
Utc::now()
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-25 21:42:10 -07:00
|
|
|
|
fn format_reset_duration(total_secs: u64) -> String {
|
|
|
|
|
|
let days = total_secs / 86_400;
|
|
|
|
|
|
let hours = (total_secs % 86_400) / 3_600;
|
|
|
|
|
|
let minutes = (total_secs % 3_600) / 60;
|
|
|
|
|
|
|
|
|
|
|
|
let mut parts: Vec<String> = Vec::new();
|
|
|
|
|
|
if days > 0 {
|
|
|
|
|
|
let unit = if days == 1 { "day" } else { "days" };
|
2025-08-28 11:25:23 -07:00
|
|
|
|
parts.push(format!("{days} {unit}"));
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
if hours > 0 {
|
|
|
|
|
|
let unit = if hours == 1 { "hour" } else { "hours" };
|
2025-08-28 11:25:23 -07:00
|
|
|
|
parts.push(format!("{hours} {unit}"));
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
if minutes > 0 {
|
|
|
|
|
|
let unit = if minutes == 1 { "minute" } else { "minutes" };
|
2025-08-28 11:25:23 -07:00
|
|
|
|
parts.push(format!("{minutes} {unit}"));
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if parts.is_empty() {
|
|
|
|
|
|
return "less than a minute".to_string();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
match parts.len() {
|
|
|
|
|
|
1 => parts[0].clone(),
|
|
|
|
|
|
2 => format!("{} {}", parts[0], parts[1]),
|
|
|
|
|
|
_ => format!("{} {} {}", parts[0], parts[1], parts[2]),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-08 21:46:06 -07:00
|
|
|
|
#[derive(Debug)]
|
|
|
|
|
|
pub struct EnvVarError {
|
|
|
|
|
|
/// Name of the environment variable that is missing.
|
|
|
|
|
|
pub var: String,
|
|
|
|
|
|
|
|
|
|
|
|
/// Optional instructions to help the user get a valid value for the
|
|
|
|
|
|
/// variable and set it.
|
|
|
|
|
|
pub instructions: Option<String>,
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl std::fmt::Display for EnvVarError {
|
|
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
|
|
write!(f, "Missing environment variable: `{}`.", self.var)?;
|
|
|
|
|
|
if let Some(instructions) = &self.instructions {
|
|
|
|
|
|
write!(f, " {instructions}")?;
|
|
|
|
|
|
}
|
|
|
|
|
|
Ok(())
|
|
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
impl CodexErr {
|
|
|
|
|
|
/// Minimal shim so that existing `e.downcast_ref::<CodexErr>()` checks continue to compile
|
|
|
|
|
|
/// after replacing `anyhow::Error` in the return signature. This mirrors the behavior of
|
|
|
|
|
|
/// `anyhow::Error::downcast_ref` but works directly on our concrete enum.
|
|
|
|
|
|
pub fn downcast_ref<T: std::any::Any>(&self) -> Option<&T> {
|
|
|
|
|
|
(self as &dyn std::any::Any).downcast_ref::<T>()
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-06 23:25:56 -07:00
|
|
|
|
|
|
|
|
|
|
pub fn get_error_message_ui(e: &CodexErr) -> String {
|
2025-10-09 17:01:01 +01:00
|
|
|
|
let message = match e {
|
|
|
|
|
|
CodexErr::Sandbox(SandboxErr::Denied { output }) => {
|
|
|
|
|
|
let aggregated = output.aggregated_output.text.trim();
|
|
|
|
|
|
if !aggregated.is_empty() {
|
|
|
|
|
|
output.aggregated_output.text.clone()
|
|
|
|
|
|
} else {
|
|
|
|
|
|
let stderr = output.stderr.text.trim();
|
|
|
|
|
|
let stdout = output.stdout.text.trim();
|
|
|
|
|
|
match (stderr.is_empty(), stdout.is_empty()) {
|
|
|
|
|
|
(false, false) => format!("{stderr}\n{stdout}"),
|
|
|
|
|
|
(false, true) => output.stderr.text.clone(),
|
|
|
|
|
|
(true, false) => output.stdout.text.clone(),
|
|
|
|
|
|
(true, true) => format!(
|
|
|
|
|
|
"command failed inside sandbox with exit code {}",
|
|
|
|
|
|
output.exit_code
|
|
|
|
|
|
),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-08-25 17:52:23 -07:00
|
|
|
|
// Timeouts are not sandbox errors from a UX perspective; present them plainly
|
2025-10-09 17:01:01 +01:00
|
|
|
|
CodexErr::Sandbox(SandboxErr::Timeout { output }) => {
|
|
|
|
|
|
format!(
|
|
|
|
|
|
"error: command timed out after {} ms",
|
|
|
|
|
|
output.duration.as_millis()
|
|
|
|
|
|
)
|
|
|
|
|
|
}
|
2025-08-06 23:25:56 -07:00
|
|
|
|
_ => e.to_string(),
|
2025-10-09 17:01:01 +01:00
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
truncate_middle(&message, ERROR_MESSAGE_UI_MAX_BYTES).0
|
2025-08-06 23:25:56 -07:00
|
|
|
|
}
|
2025-08-07 18:24:34 -07:00
|
|
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
|
mod tests {
|
|
|
|
|
|
use super::*;
|
2025-10-09 17:01:01 +01:00
|
|
|
|
use crate::exec::StreamOutput;
|
2025-10-17 17:39:37 -07:00
|
|
|
|
use chrono::DateTime;
|
|
|
|
|
|
use chrono::Duration as ChronoDuration;
|
|
|
|
|
|
use chrono::TimeZone;
|
|
|
|
|
|
use chrono::Utc;
|
2025-09-24 08:31:08 -07:00
|
|
|
|
use codex_protocol::protocol::RateLimitWindow;
|
2025-10-09 17:01:01 +01:00
|
|
|
|
use pretty_assertions::assert_eq;
|
2025-08-07 18:24:34 -07:00
|
|
|
|
|
2025-09-23 15:56:34 -07:00
|
|
|
|
fn rate_limit_snapshot() -> RateLimitSnapshot {
|
|
|
|
|
|
RateLimitSnapshot {
|
2025-09-24 08:31:08 -07:00
|
|
|
|
primary: Some(RateLimitWindow {
|
|
|
|
|
|
used_percent: 50.0,
|
|
|
|
|
|
window_minutes: Some(60),
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: Some("2024-01-01T01:00:00Z".to_string()),
|
2025-09-24 08:31:08 -07:00
|
|
|
|
}),
|
|
|
|
|
|
secondary: Some(RateLimitWindow {
|
|
|
|
|
|
used_percent: 30.0,
|
|
|
|
|
|
window_minutes: Some(120),
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: Some("2024-01-01T02:00:00Z".to_string()),
|
2025-09-24 08:31:08 -07:00
|
|
|
|
}),
|
2025-09-23 15:56:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-17 17:39:37 -07:00
|
|
|
|
fn with_now_override<T>(now: DateTime<Utc>, f: impl FnOnce() -> T) -> T {
|
|
|
|
|
|
NOW_OVERRIDE.with(|cell| {
|
|
|
|
|
|
*cell.borrow_mut() = Some(now);
|
|
|
|
|
|
let result = f();
|
|
|
|
|
|
*cell.borrow_mut() = None;
|
|
|
|
|
|
result
|
|
|
|
|
|
})
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 18:24:34 -07:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_error_formats_plus_plan() {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
2025-09-11 18:01:25 -04:00
|
|
|
|
plan_type: Some(PlanType::Known(KnownPlan::Plus)),
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: None,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
2025-08-07 18:24:34 -07:00
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
2025-08-25 21:42:10 -07:00
|
|
|
|
"You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing) or try again later."
|
2025-08-07 18:24:34 -07:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-09 17:01:01 +01:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn sandbox_denied_uses_aggregated_output_when_stderr_empty() {
|
|
|
|
|
|
let output = ExecToolCallOutput {
|
|
|
|
|
|
exit_code: 77,
|
|
|
|
|
|
stdout: StreamOutput::new(String::new()),
|
|
|
|
|
|
stderr: StreamOutput::new(String::new()),
|
|
|
|
|
|
aggregated_output: StreamOutput::new("aggregate detail".to_string()),
|
|
|
|
|
|
duration: Duration::from_millis(10),
|
|
|
|
|
|
timed_out: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
|
|
|
|
|
output: Box::new(output),
|
|
|
|
|
|
});
|
|
|
|
|
|
assert_eq!(get_error_message_ui(&err), "aggregate detail");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn sandbox_denied_reports_both_streams_when_available() {
|
|
|
|
|
|
let output = ExecToolCallOutput {
|
|
|
|
|
|
exit_code: 9,
|
|
|
|
|
|
stdout: StreamOutput::new("stdout detail".to_string()),
|
|
|
|
|
|
stderr: StreamOutput::new("stderr detail".to_string()),
|
|
|
|
|
|
aggregated_output: StreamOutput::new(String::new()),
|
|
|
|
|
|
duration: Duration::from_millis(10),
|
|
|
|
|
|
timed_out: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
|
|
|
|
|
output: Box::new(output),
|
|
|
|
|
|
});
|
|
|
|
|
|
assert_eq!(get_error_message_ui(&err), "stderr detail\nstdout detail");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn sandbox_denied_reports_stdout_when_no_stderr() {
|
|
|
|
|
|
let output = ExecToolCallOutput {
|
|
|
|
|
|
exit_code: 11,
|
|
|
|
|
|
stdout: StreamOutput::new("stdout only".to_string()),
|
|
|
|
|
|
stderr: StreamOutput::new(String::new()),
|
|
|
|
|
|
aggregated_output: StreamOutput::new(String::new()),
|
|
|
|
|
|
duration: Duration::from_millis(8),
|
|
|
|
|
|
timed_out: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
|
|
|
|
|
output: Box::new(output),
|
|
|
|
|
|
});
|
|
|
|
|
|
assert_eq!(get_error_message_ui(&err), "stdout only");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn sandbox_denied_reports_exit_code_when_no_output_available() {
|
|
|
|
|
|
let output = ExecToolCallOutput {
|
|
|
|
|
|
exit_code: 13,
|
|
|
|
|
|
stdout: StreamOutput::new(String::new()),
|
|
|
|
|
|
stderr: StreamOutput::new(String::new()),
|
|
|
|
|
|
aggregated_output: StreamOutput::new(String::new()),
|
|
|
|
|
|
duration: Duration::from_millis(5),
|
|
|
|
|
|
timed_out: false,
|
|
|
|
|
|
};
|
|
|
|
|
|
let err = CodexErr::Sandbox(SandboxErr::Denied {
|
|
|
|
|
|
output: Box::new(output),
|
|
|
|
|
|
});
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
get_error_message_ui(&err),
|
|
|
|
|
|
"command failed inside sandbox with exit code 13"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 18:01:25 -04:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_error_formats_free_plan() {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: Some(PlanType::Known(KnownPlan::Free)),
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: None,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
2025-09-11 18:01:25 -04:00
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
2025-09-25 11:10:40 -07:00
|
|
|
|
"You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)."
|
2025-09-11 18:01:25 -04:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 18:24:34 -07:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_error_formats_default_when_none() {
|
2025-08-25 21:42:10 -07:00
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: None,
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: None,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
2025-08-25 21:42:10 -07:00
|
|
|
|
};
|
2025-08-07 18:24:34 -07:00
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
2025-08-25 21:42:10 -07:00
|
|
|
|
"You've hit your usage limit. Try again later."
|
2025-08-07 18:24:34 -07:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-09-11 18:01:25 -04:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_error_formats_team_plan() {
|
2025-10-17 17:39:37 -07:00
|
|
|
|
let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
|
|
|
|
|
|
let resets_at = base + ChronoDuration::hours(1);
|
|
|
|
|
|
with_now_override(base, move || {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: Some(PlanType::Known(KnownPlan::Team)),
|
|
|
|
|
|
resets_at: Some(resets_at),
|
|
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. To get more access now, send a request to your admin or try again in 1 hour."
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-09-11 18:01:25 -04:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_error_formats_business_plan_without_reset() {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: Some(PlanType::Known(KnownPlan::Business)),
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: None,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
2025-09-11 18:01:25 -04:00
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. To get more access now, send a request to your admin or try again later."
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-08-07 18:24:34 -07:00
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_error_formats_default_for_other_plans() {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
2025-09-11 18:01:25 -04:00
|
|
|
|
plan_type: Some(PlanType::Known(KnownPlan::Pro)),
|
2025-10-17 17:39:37 -07:00
|
|
|
|
resets_at: None,
|
2025-09-23 15:56:34 -07:00
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
2025-08-25 21:42:10 -07:00
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. Try again later."
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_includes_minutes_when_available() {
|
2025-10-17 17:39:37 -07:00
|
|
|
|
let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
|
|
|
|
|
|
let resets_at = base + ChronoDuration::minutes(5);
|
|
|
|
|
|
with_now_override(base, move || {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: None,
|
|
|
|
|
|
resets_at: Some(resets_at),
|
|
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. Try again in 5 minutes."
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_includes_hours_and_minutes() {
|
2025-10-17 17:39:37 -07:00
|
|
|
|
let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
|
|
|
|
|
|
let resets_at = base + ChronoDuration::hours(3) + ChronoDuration::minutes(32);
|
|
|
|
|
|
with_now_override(base, move || {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: Some(PlanType::Known(KnownPlan::Plus)),
|
|
|
|
|
|
resets_at: Some(resets_at),
|
|
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing) or try again in 3 hours 32 minutes."
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_includes_days_hours_minutes() {
|
2025-10-17 17:39:37 -07:00
|
|
|
|
let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
|
|
|
|
|
|
let resets_at =
|
|
|
|
|
|
base + ChronoDuration::days(2) + ChronoDuration::hours(3) + ChronoDuration::minutes(5);
|
|
|
|
|
|
with_now_override(base, move || {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: None,
|
|
|
|
|
|
resets_at: Some(resets_at),
|
|
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. Try again in 2 days 3 hours 5 minutes."
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-08-25 21:42:10 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
|
fn usage_limit_reached_less_than_minute() {
|
2025-10-17 17:39:37 -07:00
|
|
|
|
let base = Utc.with_ymd_and_hms(2024, 1, 1, 0, 0, 0).unwrap();
|
|
|
|
|
|
let resets_at = base + ChronoDuration::seconds(30);
|
|
|
|
|
|
with_now_override(base, move || {
|
|
|
|
|
|
let err = UsageLimitReachedError {
|
|
|
|
|
|
plan_type: None,
|
|
|
|
|
|
resets_at: Some(resets_at),
|
|
|
|
|
|
rate_limits: Some(rate_limit_snapshot()),
|
|
|
|
|
|
};
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
|
err.to_string(),
|
|
|
|
|
|
"You've hit your usage limit. Try again in less than a minute."
|
|
|
|
|
|
);
|
|
|
|
|
|
});
|
2025-08-07 18:24:34 -07:00
|
|
|
|
}
|
|
|
|
|
|
}
|