2025-07-31 00:43:21 -07:00
use std ::collections ::HashMap ;
2025-08-25 14:38:38 -07:00
use std ::collections ::VecDeque ;
2025-05-04 11:12:40 -07:00
use std ::path ::PathBuf ;
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
use std ::sync ::Arc ;
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-04-27 21:47:50 -07:00
use codex_core ::config ::Config ;
2025-09-15 10:22:02 -07:00
use codex_core ::config_types ::Notifications ;
2025-09-21 20:18:35 -07:00
use codex_core ::git_info ::current_branch_name ;
use codex_core ::git_info ::local_git_branches ;
2025-07-16 15:11:18 -07:00
use codex_core ::protocol ::AgentMessageDeltaEvent ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::AgentMessageEvent ;
2025-07-16 15:11:18 -07:00
use codex_core ::protocol ::AgentReasoningDeltaEvent ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::AgentReasoningEvent ;
2025-08-05 01:56:13 -07:00
use codex_core ::protocol ::AgentReasoningRawContentDeltaEvent ;
use codex_core ::protocol ::AgentReasoningRawContentEvent ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::ApplyPatchApprovalRequestEvent ;
2025-08-05 22:44:27 -07:00
use codex_core ::protocol ::BackgroundEventEvent ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::ErrorEvent ;
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 ::Event ;
use codex_core ::protocol ::EventMsg ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::ExecApprovalRequestEvent ;
use codex_core ::protocol ::ExecCommandBeginEvent ;
use codex_core ::protocol ::ExecCommandEndEvent ;
2025-09-18 14:14:16 -07:00
use codex_core ::protocol ::ExitedReviewModeEvent ;
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 ::InputItem ;
2025-09-03 23:20:40 -07:00
use codex_core ::protocol ::InputMessageKind ;
2025-08-28 19:16:39 -07:00
use codex_core ::protocol ::ListCustomPromptsResponseEvent ;
2025-08-19 09:00:31 -07:00
use codex_core ::protocol ::McpListToolsResponseEvent ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::McpToolCallBeginEvent ;
use codex_core ::protocol ::McpToolCallEndEvent ;
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 ::Op ;
2025-05-13 20:44:42 -07:00
use codex_core ::protocol ::PatchApplyBeginEvent ;
2025-09-23 15:56:34 -07:00
use codex_core ::protocol ::RateLimitSnapshot ;
2025-09-18 14:14:16 -07:00
use codex_core ::protocol ::ReviewRequest ;
2025-08-21 01:15:24 -07:00
use codex_core ::protocol ::StreamErrorEvent ;
2025-05-19 16:08:18 -07:00
use codex_core ::protocol ::TaskCompleteEvent ;
feat: show number of tokens remaining in UI (#1388)
When using the OpenAI Responses API, we now record the `usage` field for
a `"response.completed"` event, which includes metrics about the number
of tokens consumed. We also introduce `openai_model_info.rs`, which
includes current data about the most common OpenAI models available via
the API (specifically `context_window` and `max_output_tokens`). If
Codex does not recognize the model, you can set `model_context_window`
and `model_max_output_tokens` explicitly in `config.toml`.
When then introduce a new event type to `protocol.rs`, `TokenCount`,
which includes the `TokenUsage` for the most recent turn.
Finally, we update the TUI to record the running sum of tokens used so
the percentage of available context window remaining can be reported via
the placeholder text for the composer:

We could certainly get much fancier with this (such as reporting the
estimated cost of the conversation), but for now, we are just trying to
achieve feature parity with the TypeScript CLI.
Though arguably this improves upon the TypeScript CLI, as the TypeScript
CLI uses heuristics to estimate the number of tokens used rather than
using the `usage` information directly:
https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
Fixes https://github.com/openai/codex/issues/1242
2025-06-25 23:31:11 -07:00
use codex_core ::protocol ::TokenUsage ;
2025-09-06 08:19:23 -07:00
use codex_core ::protocol ::TokenUsageInfo ;
2025-08-22 20:34:43 -07:00
use codex_core ::protocol ::TurnAbortReason ;
2025-08-05 22:44:27 -07:00
use codex_core ::protocol ::TurnDiffEvent ;
2025-09-03 23:20:40 -07:00
use codex_core ::protocol ::UserMessageEvent ;
2025-10-02 11:36:03 -07:00
use codex_core ::protocol ::ViewImageToolCallEvent ;
2025-08-23 22:58:56 -07:00
use codex_core ::protocol ::WebSearchBeginEvent ;
2025-08-28 19:24:38 -07:00
use codex_core ::protocol ::WebSearchEndEvent ;
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-08-15 12:44:40 -07:00
use codex_protocol ::parse_command ::ParsedCommand ;
2025-08-25 14:38:38 -07:00
use crossterm ::event ::KeyCode ;
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 crossterm ::event ::KeyEvent ;
2025-07-31 17:10:52 -07:00
use crossterm ::event ::KeyEventKind ;
2025-08-25 14:38:38 -07:00
use crossterm ::event ::KeyModifiers ;
2025-08-15 22:37:10 -04:00
use rand ::Rng ;
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 ratatui ::buffer ::Buffer ;
2025-08-06 12:03:45 -07:00
use ratatui ::layout ::Constraint ;
use ratatui ::layout ::Layout ;
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 ratatui ::layout ::Rect ;
use ratatui ::widgets ::Widget ;
use ratatui ::widgets ::WidgetRef ;
use tokio ::sync ::mpsc ::UnboundedSender ;
2025-08-12 17:37:28 -07:00
use tracing ::debug ;
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 crate ::app_event ::AppEvent ;
2025-05-15 14:50:30 -07:00
use crate ::app_event_sender ::AppEventSender ;
2025-09-26 16:35:56 -07:00
use crate ::bottom_pane ::ApprovalRequest ;
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 crate ::bottom_pane ::BottomPane ;
use crate ::bottom_pane ::BottomPaneParams ;
2025-07-28 12:00:06 -07:00
use crate ::bottom_pane ::CancellationEvent ;
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 crate ::bottom_pane ::InputResult ;
2025-08-19 10:55:07 -07:00
use crate ::bottom_pane ::SelectionAction ;
use crate ::bottom_pane ::SelectionItem ;
2025-09-21 20:18:35 -07:00
use crate ::bottom_pane ::SelectionViewParams ;
use crate ::bottom_pane ::custom_prompt_view ::CustomPromptView ;
2025-10-02 11:34:47 -07:00
use crate ::bottom_pane ::popup_consts ::standard_popup_hint_line ;
2025-09-05 07:02:11 -07:00
use crate ::clipboard_paste ::paste_image_to_temp_png ;
2025-09-15 10:22:02 -07:00
use crate ::diff_render ::display_path_for ;
2025-09-26 07:13:44 -07:00
use crate ::exec_cell ::CommandOutput ;
use crate ::exec_cell ::ExecCell ;
use crate ::exec_cell ::new_active_exec_command ;
2025-08-21 10:36:58 -07:00
use crate ::get_git_diff ::get_git_diff ;
2025-08-14 14:10:05 -04:00
use crate ::history_cell ;
2025-09-18 14:14:16 -07:00
use crate ::history_cell ::AgentMessageCell ;
2025-07-30 10:05:40 -07:00
use crate ::history_cell ::HistoryCell ;
2025-09-24 13:36:01 -07:00
use crate ::history_cell ::McpToolCallCell ;
2025-09-18 14:14:16 -07:00
use crate ::markdown ::append_markdown ;
2025-08-21 10:36:58 -07:00
use crate ::slash_command ::SlashCommand ;
2025-09-26 16:35:56 -07:00
use crate ::status ::RateLimitSnapshotDisplay ;
2025-09-15 10:22:02 -07:00
use crate ::text_formatting ::truncate_text ;
2025-08-20 13:47:24 -07:00
use crate ::tui ::FrameRequester ;
2025-08-12 17:37:28 -07:00
mod interrupts ;
use self ::interrupts ::InterruptManager ;
mod agent ;
use self ::agent ::spawn_agent ;
2025-08-23 23:23:15 -07:00
use self ::agent ::spawn_agent_from_existing ;
2025-09-14 20:53:50 -04:00
mod session_header ;
use self ::session_header ::SessionHeader ;
2025-08-12 17:37:28 -07:00
use crate ::streaming ::controller ::StreamController ;
2025-09-23 16:59:52 +01:00
use std ::path ::Path ;
2025-09-24 08:31:08 -07:00
use chrono ::Local ;
2025-08-19 22:34:37 -07:00
use codex_common ::approval_presets ::ApprovalPreset ;
use codex_common ::approval_presets ::builtin_approval_presets ;
2025-08-19 10:55:07 -07:00
use codex_common ::model_presets ::ModelPreset ;
use codex_common ::model_presets ::builtin_model_presets ;
2025-09-14 21:32:18 -04:00
use codex_core ::AuthManager ;
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
use codex_core ::ConversationManager ;
2025-08-19 22:34:37 -07:00
use codex_core ::protocol ::AskForApproval ;
use codex_core ::protocol ::SandboxPolicy ;
2025-08-19 10:55:07 -07:00
use codex_core ::protocol_config_types ::ReasoningEffort as ReasoningEffortConfig ;
2025-06-28 15:04:23 -07:00
use codex_file_search ::FileMatch ;
2025-09-23 16:59:52 +01:00
use codex_git_tooling ::CreateGhostCommitOptions ;
use codex_git_tooling ::GhostCommit ;
use codex_git_tooling ::GitToolingError ;
use codex_git_tooling ::create_ghost_commit ;
use codex_git_tooling ::restore_ghost_commit ;
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
use codex_protocol ::plan_tool ::UpdatePlanArgs ;
2025-10-02 12:38:24 -07:00
use strum ::IntoEnumIterator ;
2025-09-23 16:59:52 +01:00
const MAX_TRACKED_GHOST_COMMITS : usize = 20 ;
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-12 17:37:28 -07:00
// Track information about an in-flight exec command.
2025-07-31 00:43:21 -07:00
struct RunningCommand {
command : Vec < String > ,
2025-08-11 11:26:15 -07:00
parsed_cmd : Vec < ParsedCommand > ,
2025-07-31 00:43:21 -07:00
}
2025-09-23 09:15:16 -07:00
const RATE_LIMIT_WARNING_THRESHOLDS : [ f64 ; 3 ] = [ 75.0 , 90.0 , 95.0 ] ;
2025-09-21 10:20:49 -07:00
#[ derive(Default) ]
struct RateLimitWarningState {
2025-09-22 14:06:20 -07:00
secondary_index : usize ,
primary_index : usize ,
2025-09-21 10:20:49 -07:00
}
impl RateLimitWarningState {
2025-09-22 14:06:20 -07:00
fn take_warnings (
& mut self ,
2025-09-24 08:31:08 -07:00
secondary_used_percent : Option < f64 > ,
2025-09-25 11:10:40 -07:00
secondary_window_minutes : Option < u64 > ,
2025-09-24 08:31:08 -07:00
primary_used_percent : Option < f64 > ,
2025-09-25 11:10:40 -07:00
primary_window_minutes : Option < u64 > ,
2025-09-22 14:06:20 -07:00
) -> Vec < String > {
2025-09-24 08:31:08 -07:00
let reached_secondary_cap =
matches! ( secondary_used_percent , Some ( percent ) if percent = = 100.0 ) ;
let reached_primary_cap = matches! ( primary_used_percent , Some ( percent ) if percent = = 100.0 ) ;
if reached_secondary_cap | | reached_primary_cap {
2025-09-23 15:56:34 -07:00
return Vec ::new ( ) ;
}
2025-09-21 10:20:49 -07:00
let mut warnings = Vec ::new ( ) ;
2025-09-24 08:31:08 -07:00
if let Some ( secondary_used_percent ) = secondary_used_percent {
let mut highest_secondary : Option < f64 > = None ;
while self . secondary_index < RATE_LIMIT_WARNING_THRESHOLDS . len ( )
& & secondary_used_percent > = RATE_LIMIT_WARNING_THRESHOLDS [ self . secondary_index ]
{
highest_secondary = Some ( RATE_LIMIT_WARNING_THRESHOLDS [ self . secondary_index ] ) ;
self . secondary_index + = 1 ;
}
if let Some ( threshold ) = highest_secondary {
2025-09-25 11:10:40 -07:00
let limit_label = secondary_window_minutes
. map ( get_limits_duration )
. unwrap_or_else ( | | " weekly " . to_string ( ) ) ;
2025-09-24 08:31:08 -07:00
warnings . push ( format! (
2025-09-25 11:10:40 -07:00
" Heads up, you've used over {threshold:.0}% of your {limit_label} limit. Run /status for a breakdown. "
2025-09-24 08:31:08 -07:00
) ) ;
}
2025-09-21 10:20:49 -07:00
}
2025-09-24 08:31:08 -07:00
if let Some ( primary_used_percent ) = primary_used_percent {
let mut highest_primary : Option < f64 > = None ;
while self . primary_index < RATE_LIMIT_WARNING_THRESHOLDS . len ( )
& & primary_used_percent > = RATE_LIMIT_WARNING_THRESHOLDS [ self . primary_index ]
{
highest_primary = Some ( RATE_LIMIT_WARNING_THRESHOLDS [ self . primary_index ] ) ;
self . primary_index + = 1 ;
}
if let Some ( threshold ) = highest_primary {
2025-09-25 11:10:40 -07:00
let limit_label = primary_window_minutes
. map ( get_limits_duration )
. unwrap_or_else ( | | " 5h " . to_string ( ) ) ;
2025-09-24 08:31:08 -07:00
warnings . push ( format! (
2025-09-25 11:10:40 -07:00
" Heads up, you've used over {threshold:.0}% of your {limit_label} limit. Run /status for a breakdown. "
2025-09-24 08:31:08 -07:00
) ) ;
}
2025-09-21 10:20:49 -07:00
}
warnings
}
}
2025-09-25 15:12:25 -07:00
pub ( crate ) fn get_limits_duration ( windows_minutes : u64 ) -> String {
2025-09-25 11:10:40 -07:00
const MINUTES_PER_HOUR : u64 = 60 ;
const MINUTES_PER_DAY : u64 = 24 * MINUTES_PER_HOUR ;
const MINUTES_PER_WEEK : u64 = 7 * MINUTES_PER_DAY ;
const MINUTES_PER_MONTH : u64 = 30 * MINUTES_PER_DAY ;
const ROUNDING_BIAS_MINUTES : u64 = 3 ;
if windows_minutes < = MINUTES_PER_DAY . saturating_add ( ROUNDING_BIAS_MINUTES ) {
let adjusted = windows_minutes . saturating_add ( ROUNDING_BIAS_MINUTES ) ;
let hours = std ::cmp ::max ( 1 , adjusted / MINUTES_PER_HOUR ) ;
format! ( " {hours} h " )
} else if windows_minutes < = MINUTES_PER_WEEK . saturating_add ( ROUNDING_BIAS_MINUTES ) {
" weekly " . to_string ( )
} else if windows_minutes < = MINUTES_PER_MONTH . saturating_add ( ROUNDING_BIAS_MINUTES ) {
" monthly " . to_string ( )
} else {
" annual " . to_string ( )
}
}
2025-09-03 23:20:40 -07:00
/// Common initialization parameters shared by all `ChatWidget` constructors.
pub ( crate ) struct ChatWidgetInit {
pub ( crate ) config : Config ,
pub ( crate ) frame_requester : FrameRequester ,
pub ( crate ) app_event_tx : AppEventSender ,
pub ( crate ) initial_prompt : Option < String > ,
pub ( crate ) initial_images : Vec < PathBuf > ,
pub ( crate ) enhanced_keys_supported : bool ,
2025-09-14 21:32:18 -04:00
pub ( crate ) auth_manager : Arc < AuthManager > ,
2025-09-03 23:20:40 -07:00
}
2025-08-20 13:47:24 -07:00
pub ( crate ) struct ChatWidget {
2025-05-15 14:50:30 -07:00
app_event_tx : AppEventSender ,
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_op_tx : UnboundedSender < Op > ,
2025-08-20 13:47:24 -07:00
bottom_pane : BottomPane ,
2025-09-24 13:36:01 -07:00
active_cell : Option < Box < dyn HistoryCell > > ,
2025-04-27 21:47:50 -07:00
config : Config ,
2025-09-14 21:32:18 -04:00
auth_manager : Arc < AuthManager > ,
2025-09-14 20:53:50 -04:00
session_header : SessionHeader ,
2025-05-17 09:00:23 -07:00
initial_user_message : Option < UserMessage > ,
2025-09-06 08:19:23 -07:00
token_info : Option < TokenUsageInfo > ,
2025-09-24 08:31:08 -07:00
rate_limit_snapshot : Option < RateLimitSnapshotDisplay > ,
2025-09-21 10:20:49 -07:00
rate_limit_warnings : RateLimitWarningState ,
2025-08-12 17:37:28 -07:00
// Stream lifecycle controller
2025-09-22 11:14:04 -07:00
stream_controller : Option < StreamController > ,
2025-07-31 00:43:21 -07:00
running_commands : HashMap < String , RunningCommand > ,
2025-08-12 17:37:28 -07:00
task_complete_pending : bool ,
// Queue of interruptive UI events deferred during an active write cycle
interrupts : InterruptManager ,
2025-08-20 16:58:56 -07:00
// Accumulates the current reasoning block text to extract a header
reasoning_buffer : String ,
2025-08-20 17:09:46 -07:00
// Accumulates full reasoning content for transcript-only recording
full_reasoning_buffer : String ,
2025-10-10 10:07:14 +01:00
// Current status header shown in the status indicator.
current_status_header : String ,
// Previous status header to restore after a transient stream retry.
retry_status_header : Option < String > ,
2025-09-07 20:22:25 -07:00
conversation_id : Option < ConversationId > ,
2025-08-20 13:47:24 -07:00
frame_requester : FrameRequester ,
2025-08-23 23:23:15 -07:00
// Whether to include the initial welcome banner on session configured
show_welcome_banner : bool ,
2025-09-03 23:20:40 -07:00
// When resuming an existing session (selected via resume picker), avoid an
// immediate redraw on SessionConfigured to prevent a gratuitous UI flicker.
suppress_session_configured_redraw : bool ,
2025-08-25 14:38:38 -07:00
// User messages queued while a turn is in progress
queued_user_messages : VecDeque < UserMessage > ,
2025-09-15 10:22:02 -07:00
// Pending notification to show when unfocused on next Draw
pending_notification : Option < Notification > ,
2025-09-18 14:14:16 -07:00
// Simple review mode flag; used to adjust layout and banners.
is_review_mode : bool ,
2025-09-23 16:59:52 +01:00
// List of ghost commits corresponding to each turn.
ghost_snapshots : Vec < GhostCommit > ,
ghost_snapshots_disabled : bool ,
2025-09-26 22:49:59 -07:00
// Whether to add a final message separator after the last message
needs_final_message_separator : bool ,
2025-09-30 16:13:55 -07:00
last_rendered_width : std ::cell ::Cell < Option < usize > > ,
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-05-17 09:00:23 -07:00
struct UserMessage {
text : String ,
image_paths : Vec < PathBuf > ,
}
impl From < String > for UserMessage {
fn from ( text : String ) -> Self {
Self {
text ,
image_paths : Vec ::new ( ) ,
}
}
}
fn create_initial_user_message ( text : String , image_paths : Vec < PathBuf > ) -> Option < UserMessage > {
if text . is_empty ( ) & & image_paths . is_empty ( ) {
None
} else {
Some ( UserMessage { text , image_paths } )
}
}
2025-08-20 13:47:24 -07:00
impl ChatWidget {
2025-10-02 12:38:24 -07:00
fn model_description_for ( slug : & str ) -> Option < & 'static str > {
if slug . starts_with ( " gpt-5-codex " ) {
Some ( " Optimized for coding tasks with many tools. " )
} else if slug . starts_with ( " gpt-5 " ) {
Some ( " Broad world knowledge with strong general reasoning. " )
} else {
None
}
}
2025-08-13 11:10:48 -07:00
fn flush_answer_stream_with_separator ( & mut self ) {
2025-09-24 11:51:48 -07:00
if let Some ( mut controller ) = self . stream_controller . take ( )
& & let Some ( cell ) = controller . finalize ( )
{
self . add_boxed_history ( cell ) ;
2025-09-22 11:14:04 -07:00
}
2025-08-13 11:10:48 -07:00
}
2025-09-24 11:51:48 -07:00
2025-10-10 10:07:14 +01:00
fn set_status_header ( & mut self , header : String ) {
if self . current_status_header = = header {
return ;
}
self . current_status_header = header . clone ( ) ;
self . bottom_pane . update_status_header ( header ) ;
}
2025-08-12 17:37:28 -07:00
// --- Small event handlers ---
fn on_session_configured ( & mut self , event : codex_core ::protocol ::SessionConfiguredEvent ) {
self . bottom_pane
. set_history_metadata ( event . history_log_id , event . history_entry_count ) ;
2025-09-07 20:22:25 -07:00
self . conversation_id = Some ( event . session_id ) ;
2025-09-03 23:20:40 -07:00
let initial_messages = event . initial_messages . clone ( ) ;
2025-09-14 20:53:50 -04:00
let model_for_header = event . model . clone ( ) ;
self . session_header . set_model ( & model_for_header ) ;
2025-08-23 23:23:15 -07:00
self . add_to_history ( history_cell ::new_session_info (
& self . config ,
event ,
self . show_welcome_banner ,
) ) ;
2025-09-14 23:31:08 -04:00
if let Some ( messages ) = initial_messages {
self . replay_initial_messages ( messages ) ;
}
2025-08-28 19:16:39 -07:00
// Ask codex-core to enumerate custom prompts for this session.
self . submit_op ( Op ::ListCustomPrompts ) ;
2025-08-12 17:37:28 -07:00
if let Some ( user_message ) = self . initial_user_message . take ( ) {
self . submit_user_message ( user_message ) ;
}
2025-09-03 23:20:40 -07:00
if ! self . suppress_session_configured_redraw {
self . request_redraw ( ) ;
}
2025-08-12 17:37:28 -07:00
}
fn on_agent_message ( & mut self , message : String ) {
2025-09-22 11:14:04 -07:00
// If we have a stream_controller, then the final agent message is redundant and will be a
// duplicate of what has already been streamed.
if self . stream_controller . is_none ( ) {
self . handle_streaming_delta ( message ) ;
}
self . flush_answer_stream_with_separator ( ) ;
self . handle_stream_finished ( ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
fn on_agent_message_delta ( & mut self , delta : String ) {
2025-08-20 16:58:56 -07:00
self . handle_streaming_delta ( delta ) ;
2025-08-12 17:37:28 -07:00
}
fn on_agent_reasoning_delta ( & mut self , delta : String ) {
2025-08-20 16:58:56 -07:00
// For reasoning deltas, do not stream to history. Accumulate the
// current reasoning block and extract the first bold element
// (between **/**) as the chunk header. Show this header as status.
self . reasoning_buffer . push_str ( & delta ) ;
if let Some ( header ) = extract_first_bold ( & self . reasoning_buffer ) {
// Update the shimmer header to the extracted reasoning chunk header.
2025-10-10 10:07:14 +01:00
self . set_status_header ( header ) ;
2025-08-20 16:58:56 -07:00
} else {
// Fallback while we don't yet have a bold header: leave existing header as-is.
}
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
2025-08-20 16:58:56 -07:00
fn on_agent_reasoning_final ( & mut self ) {
2025-08-20 17:09:46 -07:00
// At the end of a reasoning block, record transcript-only content.
self . full_reasoning_buffer . push_str ( & self . reasoning_buffer ) ;
if ! self . full_reasoning_buffer . is_empty ( ) {
2025-09-16 16:42:43 -07:00
let cell = history_cell ::new_reasoning_summary_block (
2025-08-20 17:09:46 -07:00
self . full_reasoning_buffer . clone ( ) ,
& self . config ,
2025-09-16 16:42:43 -07:00
) ;
self . add_boxed_history ( cell ) ;
2025-08-20 17:09:46 -07:00
}
2025-08-20 16:58:56 -07:00
self . reasoning_buffer . clear ( ) ;
2025-08-20 17:09:46 -07:00
self . full_reasoning_buffer . clear ( ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
fn on_reasoning_section_break ( & mut self ) {
2025-08-20 17:09:46 -07:00
// Start a new reasoning block for header extraction and accumulate transcript.
self . full_reasoning_buffer . push_str ( & self . reasoning_buffer ) ;
self . full_reasoning_buffer . push_str ( " \n \n " ) ;
2025-08-20 16:58:56 -07:00
self . reasoning_buffer . clear ( ) ;
2025-08-12 17:37:28 -07:00
}
// Raw reasoning uses the same flow as summarized reasoning
fn on_task_started ( & mut self ) {
self . bottom_pane . clear_ctrl_c_quit_hint ( ) ;
self . bottom_pane . set_task_running ( true ) ;
2025-10-10 10:07:14 +01:00
self . retry_status_header = None ;
self . set_status_header ( String ::from ( " Working " ) ) ;
2025-08-20 17:09:46 -07:00
self . full_reasoning_buffer . clear ( ) ;
2025-08-20 16:58:56 -07:00
self . reasoning_buffer . clear ( ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
2025-09-17 11:23:46 -07:00
fn on_task_complete ( & mut self , last_agent_message : Option < String > ) {
2025-09-24 11:51:48 -07:00
// If a stream is currently active, finalize it.
self . flush_answer_stream_with_separator ( ) ;
2025-08-12 17:37:28 -07:00
// Mark task stopped and request redraw now that all content is in history.
self . bottom_pane . set_task_running ( false ) ;
2025-08-14 20:01:19 -04:00
self . running_commands . clear ( ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
// If there is a queued user message, send exactly one now to begin the next turn.
self . maybe_send_next_queued_input ( ) ;
2025-09-15 10:22:02 -07:00
// Emit a notification when the turn completes (suppressed if focused).
2025-09-17 11:23:46 -07:00
self . notify ( Notification ::AgentTurnComplete {
response : last_agent_message . unwrap_or_default ( ) ,
} ) ;
2025-08-12 17:37:28 -07:00
}
2025-09-06 08:19:23 -07:00
pub ( crate ) fn set_token_info ( & mut self , info : Option < TokenUsageInfo > ) {
2025-10-01 11:03:05 -07:00
if let Some ( info ) = info {
let context_window = info
. model_context_window
. or ( self . config . model_context_window ) ;
let percent = context_window . map ( | window | {
info . last_token_usage
. percent_of_context_window_remaining ( window )
} ) ;
self . bottom_pane . set_context_window_percent ( percent ) ;
self . token_info = Some ( info ) ;
2025-09-23 15:56:34 -07:00
}
2025-08-12 17:37:28 -07:00
}
2025-09-21 10:20:49 -07:00
2025-09-23 15:56:34 -07:00
fn on_rate_limit_snapshot ( & mut self , snapshot : Option < RateLimitSnapshot > ) {
2025-09-21 10:20:49 -07:00
if let Some ( snapshot ) = snapshot {
2025-09-22 14:06:20 -07:00
let warnings = self . rate_limit_warnings . take_warnings (
2025-09-24 08:31:08 -07:00
snapshot
. secondary
. as_ref ( )
. map ( | window | window . used_percent ) ,
2025-09-25 11:10:40 -07:00
snapshot
. secondary
. as_ref ( )
. and_then ( | window | window . window_minutes ) ,
2025-09-24 08:31:08 -07:00
snapshot . primary . as_ref ( ) . map ( | window | window . used_percent ) ,
2025-09-25 11:10:40 -07:00
snapshot
. primary
. as_ref ( )
. and_then ( | window | window . window_minutes ) ,
2025-09-22 14:06:20 -07:00
) ;
2025-09-24 08:31:08 -07:00
2025-09-26 16:35:56 -07:00
let display = crate ::status ::rate_limit_snapshot_display ( & snapshot , Local ::now ( ) ) ;
2025-09-24 08:31:08 -07:00
self . rate_limit_snapshot = Some ( display ) ;
2025-09-21 10:20:49 -07:00
if ! warnings . is_empty ( ) {
for warning in warnings {
self . add_to_history ( history_cell ::new_warning_event ( warning ) ) ;
}
self . request_redraw ( ) ;
}
2025-09-24 08:31:08 -07:00
} else {
self . rate_limit_snapshot = None ;
2025-09-21 10:20:49 -07:00
}
}
2025-09-18 14:14:16 -07:00
/// Finalize any active exec as failed and stop/clear running UI state.
fn finalize_turn ( & mut self ) {
2025-08-26 16:26:50 -07:00
// Ensure any spinner is replaced by a red ✗ and flushed into history.
2025-09-24 13:36:01 -07:00
self . finalize_active_cell_as_failed ( ) ;
2025-08-26 16:26:50 -07:00
// Reset running state and clear streaming buffers.
2025-08-12 17:37:28 -07:00
self . bottom_pane . set_task_running ( false ) ;
2025-08-14 20:01:19 -04:00
self . running_commands . clear ( ) ;
2025-09-22 11:14:04 -07:00
self . stream_controller = None ;
2025-08-26 16:26:50 -07:00
}
fn on_error ( & mut self , message : String ) {
2025-09-18 14:14:16 -07:00
self . finalize_turn ( ) ;
self . add_to_history ( history_cell ::new_error_event ( message ) ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
// After an error ends the turn, try sending the next queued input.
self . maybe_send_next_queued_input ( ) ;
2025-08-12 17:37:28 -07:00
}
2025-08-26 16:26:50 -07:00
/// Handle a turn aborted due to user interrupt (Esc).
/// When there are queued user messages, restore them into the composer
/// separated by newlines rather than auto‑ submitting the next one.
2025-09-18 14:14:16 -07:00
fn on_interrupted_turn ( & mut self , reason : TurnAbortReason ) {
2025-08-26 16:26:50 -07:00
// Finalize, log a gentle prompt, and clear running state.
2025-09-18 14:14:16 -07:00
self . finalize_turn ( ) ;
if reason ! = TurnAbortReason ::ReviewEnded {
self . add_to_history ( history_cell ::new_error_event (
" Conversation interrupted - tell the model what to do differently " . to_owned ( ) ,
) ) ;
}
2025-08-26 16:26:50 -07:00
// If any messages were queued during the task, restore them into the composer.
if ! self . queued_user_messages . is_empty ( ) {
2025-09-25 10:07:27 -07:00
let queued_text = self
2025-08-26 16:26:50 -07:00
. queued_user_messages
. iter ( )
. map ( | m | m . text . clone ( ) )
. collect ::< Vec < _ > > ( )
. join ( " \n " ) ;
2025-09-25 10:07:27 -07:00
let existing_text = self . bottom_pane . composer_text ( ) ;
let combined = if existing_text . is_empty ( ) {
queued_text
} else if queued_text . is_empty ( ) {
existing_text
} else {
format! ( " {queued_text} \n {existing_text} " )
} ;
2025-08-26 16:26:50 -07:00
self . bottom_pane . set_composer_text ( combined ) ;
// Clear the queue and update the status indicator list.
self . queued_user_messages . clear ( ) ;
self . refresh_queued_user_messages ( ) ;
}
self . request_redraw ( ) ;
}
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
fn on_plan_update ( & mut self , update : UpdatePlanArgs ) {
2025-08-20 17:09:46 -07:00
self . add_to_history ( history_cell ::new_plan_update ( update ) ) ;
2025-08-12 17:37:28 -07:00
}
fn on_exec_approval_request ( & mut self , id : String , ev : ExecApprovalRequestEvent ) {
let id2 = id . clone ( ) ;
let ev2 = ev . clone ( ) ;
self . defer_or_handle (
| q | q . push_exec_approval ( id , ev ) ,
| s | s . handle_exec_approval_now ( id2 , ev2 ) ,
) ;
}
fn on_apply_patch_approval_request ( & mut self , id : String , ev : ApplyPatchApprovalRequestEvent ) {
let id2 = id . clone ( ) ;
let ev2 = ev . clone ( ) ;
self . defer_or_handle (
| q | q . push_apply_patch_approval ( id , ev ) ,
| s | s . handle_apply_patch_approval_now ( id2 , ev2 ) ,
) ;
}
fn on_exec_command_begin ( & mut self , ev : ExecCommandBeginEvent ) {
2025-08-13 11:10:48 -07:00
self . flush_answer_stream_with_separator ( ) ;
2025-08-12 17:37:28 -07:00
let ev2 = ev . clone ( ) ;
self . defer_or_handle ( | q | q . push_exec_begin ( ev ) , | s | s . handle_exec_begin_now ( ev2 ) ) ;
}
fn on_exec_command_output_delta (
& mut self ,
_ev : codex_core ::protocol ::ExecCommandOutputDeltaEvent ,
) {
// TODO: Handle streaming exec output if/when implemented
}
fn on_patch_apply_begin ( & mut self , event : PatchApplyBeginEvent ) {
2025-08-20 17:09:46 -07:00
self . add_to_history ( history_cell ::new_patch_event (
2025-08-12 17:37:28 -07:00
event . changes ,
2025-09-02 10:29:58 -07:00
& self . config . cwd ,
2025-08-12 17:37:28 -07:00
) ) ;
}
2025-10-02 11:36:03 -07:00
fn on_view_image_tool_call ( & mut self , event : ViewImageToolCallEvent ) {
self . flush_answer_stream_with_separator ( ) ;
self . add_to_history ( history_cell ::new_view_image_tool_call (
event . path ,
& self . config . cwd ,
) ) ;
self . request_redraw ( ) ;
}
2025-08-12 17:37:28 -07:00
fn on_patch_apply_end ( & mut self , event : codex_core ::protocol ::PatchApplyEndEvent ) {
let ev2 = event . clone ( ) ;
self . defer_or_handle (
| q | q . push_patch_end ( event ) ,
| s | s . handle_patch_apply_end_now ( ev2 ) ,
) ;
}
fn on_exec_command_end ( & mut self , ev : ExecCommandEndEvent ) {
let ev2 = ev . clone ( ) ;
self . defer_or_handle ( | q | q . push_exec_end ( ev ) , | s | s . handle_exec_end_now ( ev2 ) ) ;
}
fn on_mcp_tool_call_begin ( & mut self , ev : McpToolCallBeginEvent ) {
let ev2 = ev . clone ( ) ;
self . defer_or_handle ( | q | q . push_mcp_begin ( ev ) , | s | s . handle_mcp_begin_now ( ev2 ) ) ;
}
fn on_mcp_tool_call_end ( & mut self , ev : McpToolCallEndEvent ) {
let ev2 = ev . clone ( ) ;
self . defer_or_handle ( | q | q . push_mcp_end ( ev ) , | s | s . handle_mcp_end_now ( ev2 ) ) ;
}
2025-08-28 19:24:38 -07:00
fn on_web_search_begin ( & mut self , _ev : WebSearchBeginEvent ) {
2025-08-23 22:58:56 -07:00
self . flush_answer_stream_with_separator ( ) ;
2025-08-28 19:24:38 -07:00
}
fn on_web_search_end ( & mut self , ev : WebSearchEndEvent ) {
self . flush_answer_stream_with_separator ( ) ;
self . add_to_history ( history_cell ::new_web_search_call ( format! (
" Searched: {} " ,
ev . query
) ) ) ;
2025-08-23 22:58:56 -07:00
}
2025-08-12 17:37:28 -07:00
fn on_get_history_entry_response (
& mut self ,
event : codex_core ::protocol ::GetHistoryEntryResponseEvent ,
) {
let codex_core ::protocol ::GetHistoryEntryResponseEvent {
offset ,
log_id ,
entry ,
} = event ;
self . bottom_pane
. on_history_entry_response ( log_id , offset , entry . map ( | e | e . text ) ) ;
}
fn on_shutdown_complete ( & mut self ) {
self . app_event_tx . send ( AppEvent ::ExitRequest ) ;
}
fn on_turn_diff ( & mut self , unified_diff : String ) {
debug! ( " TurnDiffEvent: {unified_diff} " ) ;
}
fn on_background_event ( & mut self , message : String ) {
debug! ( " BackgroundEvent: {message} " ) ;
}
2025-08-21 01:15:24 -07:00
fn on_stream_error ( & mut self , message : String ) {
2025-10-10 10:07:14 +01:00
if self . retry_status_header . is_none ( ) {
self . retry_status_header = Some ( self . current_status_header . clone ( ) ) ;
}
self . set_status_header ( message ) ;
2025-08-21 01:15:24 -07:00
}
2025-09-24 11:51:48 -07:00
2025-08-12 17:37:28 -07:00
/// Periodic tick to commit at most one queued line to history with a small delay,
/// animating the output.
pub ( crate ) fn on_commit_tick ( & mut self ) {
2025-09-22 11:14:04 -07:00
if let Some ( controller ) = self . stream_controller . as_mut ( ) {
2025-09-24 11:51:48 -07:00
let ( cell , is_idle ) = controller . on_commit_tick ( ) ;
if let Some ( cell ) = cell {
2025-10-02 15:41:25 -07:00
self . bottom_pane . hide_status_indicator ( ) ;
2025-09-24 11:51:48 -07:00
self . add_boxed_history ( cell ) ;
}
if is_idle {
self . app_event_tx . send ( AppEvent ::StopCommitAnimation ) ;
2025-09-22 11:14:04 -07:00
}
}
2025-08-12 17:37:28 -07:00
}
fn flush_interrupt_queue ( & mut self ) {
let mut mgr = std ::mem ::take ( & mut self . interrupts ) ;
mgr . flush_all ( self ) ;
self . interrupts = mgr ;
}
#[ inline ]
fn defer_or_handle (
& mut self ,
push : impl FnOnce ( & mut InterruptManager ) ,
handle : impl FnOnce ( & mut Self ) ,
) {
// Preserve deterministic FIFO across queued interrupts: once anything
// is queued due to an active write cycle, continue queueing until the
// queue is flushed to avoid reordering (e.g., ExecEnd before ExecBegin).
2025-09-22 11:14:04 -07:00
if self . stream_controller . is_some ( ) | | ! self . interrupts . is_empty ( ) {
2025-08-12 17:37:28 -07:00
push ( & mut self . interrupts ) ;
} else {
handle ( self ) ;
}
}
2025-09-22 11:14:04 -07:00
fn handle_stream_finished ( & mut self ) {
if self . task_complete_pending {
2025-10-02 15:41:25 -07:00
self . bottom_pane . hide_status_indicator ( ) ;
2025-09-22 11:14:04 -07:00
self . task_complete_pending = false ;
2025-08-12 17:37:28 -07:00
}
2025-09-22 11:14:04 -07:00
// A completed stream indicates non-exec content was just inserted.
self . flush_interrupt_queue ( ) ;
2025-08-12 17:37:28 -07:00
}
#[ inline ]
2025-08-20 16:58:56 -07:00
fn handle_streaming_delta ( & mut self , delta : String ) {
2025-09-02 10:29:58 -07:00
// Before streaming agent content, flush any active exec cell group.
2025-09-24 13:36:01 -07:00
self . flush_active_cell ( ) ;
2025-09-22 11:14:04 -07:00
if self . stream_controller . is_none ( ) {
2025-09-26 22:49:59 -07:00
if self . needs_final_message_separator {
let elapsed_seconds = self
. bottom_pane
. status_widget ( )
. map ( super ::status_indicator_widget ::StatusIndicatorWidget ::elapsed_seconds ) ;
self . add_to_history ( history_cell ::FinalMessageSeparator ::new ( elapsed_seconds ) ) ;
self . needs_final_message_separator = false ;
}
2025-09-30 16:13:55 -07:00
self . stream_controller = Some ( StreamController ::new (
self . config . clone ( ) ,
self . last_rendered_width . get ( ) . map ( | w | w . saturating_sub ( 2 ) ) ,
) ) ;
2025-09-22 11:14:04 -07:00
}
2025-09-24 11:51:48 -07:00
if let Some ( controller ) = self . stream_controller . as_mut ( )
& & controller . push ( & delta )
{
self . app_event_tx . send ( AppEvent ::StartCommitAnimation ) ;
2025-09-22 11:14:04 -07:00
}
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
pub ( crate ) fn handle_exec_end_now ( & mut self , ev : ExecCommandEndEvent ) {
let running = self . running_commands . remove ( & ev . call_id ) ;
let ( command , parsed ) = match running {
Some ( rc ) = > ( rc . command , rc . parsed_cmd ) ,
None = > ( vec! [ ev . call_id . clone ( ) ] , Vec ::new ( ) ) ,
} ;
2025-08-13 11:10:48 -07:00
2025-09-24 13:36:01 -07:00
let needs_new = self
. active_cell
. as_ref ( )
. map ( | cell | cell . as_any ( ) . downcast_ref ::< ExecCell > ( ) . is_none ( ) )
. unwrap_or ( true ) ;
if needs_new {
self . flush_active_cell ( ) ;
2025-09-26 07:13:44 -07:00
self . active_cell = Some ( Box ::new ( new_active_exec_command (
2025-09-02 10:29:58 -07:00
ev . call_id . clone ( ) ,
command ,
parsed ,
2025-09-24 13:36:01 -07:00
) ) ) ;
2025-09-02 10:29:58 -07:00
}
2025-09-24 13:36:01 -07:00
if let Some ( cell ) = self
. active_cell
. as_mut ( )
. and_then ( | c | c . as_any_mut ( ) . downcast_mut ::< ExecCell > ( ) )
{
2025-09-02 10:29:58 -07:00
cell . complete_call (
& ev . call_id ,
CommandOutput {
exit_code : ev . exit_code ,
stdout : ev . stdout . clone ( ) ,
stderr : ev . stderr . clone ( ) ,
formatted_output : ev . formatted_output . clone ( ) ,
} ,
ev . duration ,
) ;
if cell . should_flush ( ) {
2025-09-24 13:36:01 -07:00
self . flush_active_cell ( ) ;
2025-08-13 11:10:48 -07:00
}
}
2025-08-12 17:37:28 -07:00
}
pub ( crate ) fn handle_patch_apply_end_now (
& mut self ,
event : codex_core ::protocol ::PatchApplyEndEvent ,
) {
2025-09-02 10:29:58 -07:00
// If the patch was successful, just let the "Edited" block stand.
// Otherwise, add a failure block.
if ! event . success {
2025-08-20 17:09:46 -07:00
self . add_to_history ( history_cell ::new_patch_apply_failure ( event . stderr ) ) ;
2025-08-12 17:37:28 -07:00
}
}
pub ( crate ) fn handle_exec_approval_now ( & mut self , id : String , ev : ExecApprovalRequestEvent ) {
2025-08-13 11:10:48 -07:00
self . flush_answer_stream_with_separator ( ) ;
2025-09-22 20:30:16 +01:00
let command = shlex ::try_join ( ev . command . iter ( ) . map ( String ::as_str ) )
2025-09-15 10:22:02 -07:00
. unwrap_or_else ( | _ | ev . command . join ( " " ) ) ;
self . notify ( Notification ::ExecApprovalRequested { command } ) ;
2025-08-12 17:37:28 -07:00
let request = ApprovalRequest ::Exec {
id ,
command : ev . command ,
reason : ev . reason ,
} ;
self . bottom_pane . push_approval_request ( request ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
pub ( crate ) fn handle_apply_patch_approval_now (
& mut self ,
id : String ,
ev : ApplyPatchApprovalRequestEvent ,
) {
2025-08-13 11:10:48 -07:00
self . flush_answer_stream_with_separator ( ) ;
2025-08-12 17:37:28 -07:00
let request = ApprovalRequest ::ApplyPatch {
id ,
reason : ev . reason ,
2025-10-01 14:29:05 -07:00
changes : ev . changes . clone ( ) ,
cwd : self . config . cwd . clone ( ) ,
2025-08-12 17:37:28 -07:00
} ;
self . bottom_pane . push_approval_request ( request ) ;
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-09-15 10:22:02 -07:00
self . notify ( Notification ::EditApprovalRequested {
cwd : self . config . cwd . clone ( ) ,
changes : ev . changes . keys ( ) . cloned ( ) . collect ( ) ,
} ) ;
2025-08-12 17:37:28 -07:00
}
pub ( crate ) fn handle_exec_begin_now ( & mut self , ev : ExecCommandBeginEvent ) {
// Ensure the status indicator is visible while the command runs.
self . running_commands . insert (
ev . call_id . clone ( ) ,
RunningCommand {
command : ev . command . clone ( ) ,
parsed_cmd : ev . parsed_cmd . clone ( ) ,
} ,
) ;
2025-09-24 13:36:01 -07:00
if let Some ( cell ) = self
. active_cell
. as_mut ( )
. and_then ( | c | c . as_any_mut ( ) . downcast_mut ::< ExecCell > ( ) )
& & let Some ( new_exec ) = cell . with_added_call (
2025-09-02 10:29:58 -07:00
ev . call_id . clone ( ) ,
ev . command . clone ( ) ,
ev . parsed_cmd . clone ( ) ,
2025-09-24 13:36:01 -07:00
)
{
* cell = new_exec ;
2025-09-02 10:29:58 -07:00
} else {
2025-09-24 13:36:01 -07:00
self . flush_active_cell ( ) ;
2025-09-26 07:13:44 -07:00
self . active_cell = Some ( Box ::new ( new_active_exec_command (
2025-09-02 10:29:58 -07:00
ev . call_id . clone ( ) ,
ev . command . clone ( ) ,
2025-09-11 11:59:37 -07:00
ev . parsed_cmd ,
2025-09-24 13:36:01 -07:00
) ) ) ;
2025-08-13 11:10:48 -07:00
}
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
pub ( crate ) fn handle_mcp_begin_now ( & mut self , ev : McpToolCallBeginEvent ) {
2025-08-13 11:10:48 -07:00
self . flush_answer_stream_with_separator ( ) ;
2025-09-24 13:36:01 -07:00
self . flush_active_cell ( ) ;
self . active_cell = Some ( Box ::new ( history_cell ::new_active_mcp_tool_call (
ev . call_id ,
ev . invocation ,
) ) ) ;
self . request_redraw ( ) ;
2025-08-12 17:37:28 -07:00
}
pub ( crate ) fn handle_mcp_end_now ( & mut self , ev : McpToolCallEndEvent ) {
2025-08-13 11:10:48 -07:00
self . flush_answer_stream_with_separator ( ) ;
2025-09-24 13:36:01 -07:00
let McpToolCallEndEvent {
call_id ,
invocation ,
duration ,
result ,
} = ev ;
let extra_cell = match self
. active_cell
. as_mut ( )
. and_then ( | cell | cell . as_any_mut ( ) . downcast_mut ::< McpToolCallCell > ( ) )
{
Some ( cell ) if cell . call_id ( ) = = call_id = > cell . complete ( duration , result ) ,
_ = > {
self . flush_active_cell ( ) ;
let mut cell = history_cell ::new_active_mcp_tool_call ( call_id , invocation ) ;
let extra_cell = cell . complete ( duration , result ) ;
self . active_cell = Some ( Box ::new ( cell ) ) ;
extra_cell
}
} ;
self . flush_active_cell ( ) ;
if let Some ( extra ) = extra_cell {
self . add_boxed_history ( extra ) ;
}
2025-08-12 17:37:28 -07:00
}
2025-08-25 20:15:38 -07:00
2025-09-14 20:53:50 -04:00
fn layout_areas ( & self , area : Rect ) -> [ Rect ; 3 ] {
let bottom_min = self . bottom_pane . desired_height ( area . width ) . min ( area . height ) ;
let remaining = area . height . saturating_sub ( bottom_min ) ;
let active_desired = self
2025-09-24 13:36:01 -07:00
. active_cell
2025-09-14 20:53:50 -04:00
. as_ref ( )
. map_or ( 0 , | c | c . desired_height ( area . width ) + 1 ) ;
let active_height = active_desired . min ( remaining ) ;
// Note: no header area; remaining is not used beyond computing active height.
let header_height = 0 u16 ;
2025-08-06 12:03:45 -07:00
Layout ::vertical ( [
2025-09-14 20:53:50 -04:00
Constraint ::Length ( header_height ) ,
Constraint ::Length ( active_height ) ,
Constraint ::Min ( bottom_min ) ,
2025-08-06 12:03:45 -07:00
] )
. areas ( area )
}
2025-08-12 17:37:28 -07:00
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
pub ( crate ) fn new (
2025-09-03 23:20:40 -07:00
common : ChatWidgetInit ,
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
conversation_manager : Arc < ConversationManager > ,
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
) -> Self {
2025-09-03 23:20:40 -07:00
let ChatWidgetInit {
config ,
frame_requester ,
app_event_tx ,
initial_prompt ,
initial_images ,
enhanced_keys_supported ,
2025-09-14 21:32:18 -04:00
auth_manager ,
2025-09-03 23:20:40 -07:00
} = common ;
2025-08-15 22:37:10 -04:00
let mut rng = rand ::rng ( ) ;
let placeholder = EXAMPLE_PROMPTS [ rng . random_range ( 0 .. EXAMPLE_PROMPTS . len ( ) ) ] . to_string ( ) ;
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
let codex_op_tx = spawn_agent ( config . clone ( ) , app_event_tx . clone ( ) , conversation_manager ) ;
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-05-17 09:00:23 -07:00
Self {
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
app_event_tx : app_event_tx . clone ( ) ,
2025-08-20 13:47:24 -07:00
frame_requester : frame_requester . 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
codex_op_tx ,
bottom_pane : BottomPane ::new ( BottomPaneParams {
2025-08-20 13:47:24 -07:00
frame_requester ,
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
app_event_tx ,
has_input_focus : true ,
2025-07-31 17:30:44 -07:00
enhanced_keys_supported ,
2025-08-15 22:37:10 -04:00
placeholder_text : placeholder ,
2025-08-28 12:54:12 -07:00
disable_paste_burst : config . disable_paste_burst ,
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-09-24 13:36:01 -07:00
active_cell : None ,
2025-08-12 17:37:28 -07:00
config : config . clone ( ) ,
2025-09-14 21:32:18 -04:00
auth_manager ,
2025-09-22 11:14:04 -07:00
session_header : SessionHeader ::new ( config . model ) ,
2025-05-17 09:00:23 -07:00
initial_user_message : create_initial_user_message (
initial_prompt . unwrap_or_default ( ) ,
initial_images ,
) ,
2025-09-06 08:19:23 -07:00
token_info : None ,
2025-09-21 10:20:49 -07:00
rate_limit_snapshot : None ,
rate_limit_warnings : RateLimitWarningState ::default ( ) ,
2025-09-22 11:14:04 -07:00
stream_controller : None ,
2025-07-31 00:43:21 -07:00
running_commands : HashMap ::new ( ) ,
2025-08-12 17:37:28 -07:00
task_complete_pending : false ,
interrupts : InterruptManager ::new ( ) ,
2025-08-20 16:58:56 -07:00
reasoning_buffer : String ::new ( ) ,
2025-08-20 17:09:46 -07:00
full_reasoning_buffer : String ::new ( ) ,
2025-10-10 10:07:14 +01:00
current_status_header : String ::from ( " Working " ) ,
retry_status_header : None ,
2025-09-07 20:22:25 -07:00
conversation_id : None ,
2025-08-25 14:38:38 -07:00
queued_user_messages : VecDeque ::new ( ) ,
2025-08-23 23:23:15 -07:00
show_welcome_banner : true ,
2025-09-03 23:20:40 -07:00
suppress_session_configured_redraw : false ,
2025-09-15 10:22:02 -07:00
pending_notification : None ,
2025-09-18 14:14:16 -07:00
is_review_mode : false ,
2025-09-23 16:59:52 +01:00
ghost_snapshots : Vec ::new ( ) ,
ghost_snapshots_disabled : true ,
2025-09-26 22:49:59 -07:00
needs_final_message_separator : false ,
2025-09-30 16:13:55 -07:00
last_rendered_width : std ::cell ::Cell ::new ( None ) ,
2025-08-23 23:23:15 -07:00
}
}
/// Create a ChatWidget attached to an existing conversation (e.g., a fork).
pub ( crate ) fn new_from_existing (
2025-09-03 23:20:40 -07:00
common : ChatWidgetInit ,
2025-08-23 23:23:15 -07:00
conversation : std ::sync ::Arc < codex_core ::CodexConversation > ,
session_configured : codex_core ::protocol ::SessionConfiguredEvent ,
) -> Self {
2025-09-03 23:20:40 -07:00
let ChatWidgetInit {
config ,
frame_requester ,
app_event_tx ,
initial_prompt ,
initial_images ,
enhanced_keys_supported ,
2025-09-14 21:32:18 -04:00
auth_manager ,
2025-09-03 23:20:40 -07:00
} = common ;
2025-08-23 23:23:15 -07:00
let mut rng = rand ::rng ( ) ;
let placeholder = EXAMPLE_PROMPTS [ rng . random_range ( 0 .. EXAMPLE_PROMPTS . len ( ) ) ] . to_string ( ) ;
let codex_op_tx =
spawn_agent_from_existing ( conversation , session_configured , app_event_tx . clone ( ) ) ;
Self {
app_event_tx : app_event_tx . clone ( ) ,
frame_requester : frame_requester . clone ( ) ,
codex_op_tx ,
bottom_pane : BottomPane ::new ( BottomPaneParams {
frame_requester ,
app_event_tx ,
has_input_focus : true ,
enhanced_keys_supported ,
placeholder_text : placeholder ,
2025-08-28 12:54:12 -07:00
disable_paste_burst : config . disable_paste_burst ,
2025-08-23 23:23:15 -07:00
} ) ,
2025-09-24 13:36:01 -07:00
active_cell : None ,
2025-08-23 23:23:15 -07:00
config : config . clone ( ) ,
2025-09-14 21:32:18 -04:00
auth_manager ,
2025-09-22 11:14:04 -07:00
session_header : SessionHeader ::new ( config . model ) ,
2025-09-03 23:20:40 -07:00
initial_user_message : create_initial_user_message (
initial_prompt . unwrap_or_default ( ) ,
initial_images ,
) ,
2025-09-06 08:19:23 -07:00
token_info : None ,
2025-09-21 10:20:49 -07:00
rate_limit_snapshot : None ,
rate_limit_warnings : RateLimitWarningState ::default ( ) ,
2025-09-22 11:14:04 -07:00
stream_controller : None ,
2025-08-23 23:23:15 -07:00
running_commands : HashMap ::new ( ) ,
task_complete_pending : false ,
interrupts : InterruptManager ::new ( ) ,
reasoning_buffer : String ::new ( ) ,
full_reasoning_buffer : String ::new ( ) ,
2025-10-10 10:07:14 +01:00
current_status_header : String ::from ( " Working " ) ,
retry_status_header : None ,
2025-09-07 20:22:25 -07:00
conversation_id : None ,
2025-08-25 14:38:38 -07:00
queued_user_messages : VecDeque ::new ( ) ,
2025-09-14 23:31:08 -04:00
show_welcome_banner : true ,
2025-09-03 23:20:40 -07:00
suppress_session_configured_redraw : true ,
2025-09-15 10:22:02 -07:00
pending_notification : None ,
2025-09-18 14:14:16 -07:00
is_review_mode : false ,
2025-09-23 16:59:52 +01:00
ghost_snapshots : Vec ::new ( ) ,
ghost_snapshots_disabled : true ,
2025-09-26 22:49:59 -07:00
needs_final_message_separator : false ,
2025-09-30 16:13:55 -07:00
last_rendered_width : std ::cell ::Cell ::new ( None ) ,
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-07-31 00:43:21 -07:00
pub fn desired_height ( & self , width : u16 ) -> u16 {
self . bottom_pane . desired_height ( width )
2025-08-06 12:03:45 -07:00
+ self
2025-09-24 13:36:01 -07:00
. active_cell
2025-08-06 12:03:45 -07:00
. as_ref ( )
2025-09-02 10:29:58 -07:00
. map_or ( 0 , | c | c . desired_height ( width ) + 1 )
2025-07-30 17:06:55 -07:00
}
2025-05-15 14:50:30 -07:00
pub ( crate ) fn handle_key_event ( & mut self , key_event : KeyEvent ) {
2025-08-25 20:15:38 -07:00
match key_event {
2025-08-25 14:38:38 -07:00
KeyEvent {
2025-08-25 20:15:38 -07:00
code : KeyCode ::Char ( 'c' ) ,
modifiers : crossterm ::event ::KeyModifiers ::CONTROL ,
2025-08-25 14:38:38 -07:00
kind : KeyEventKind ::Press ,
..
2025-08-25 20:15:38 -07:00
} = > {
self . on_ctrl_c ( ) ;
return ;
2025-08-25 14:38:38 -07:00
}
2025-09-05 07:02:11 -07:00
KeyEvent {
code : KeyCode ::Char ( 'v' ) ,
modifiers : KeyModifiers ::CONTROL ,
kind : KeyEventKind ::Press ,
..
} = > {
if let Ok ( ( path , info ) ) = paste_image_to_temp_png ( ) {
self . attach_image ( path , info . width , info . height , info . encoded_format . label ( ) ) ;
}
return ;
}
2025-08-25 20:15:38 -07:00
other if other . kind = = KeyEventKind ::Press = > {
self . bottom_pane . clear_ctrl_c_quit_hint ( ) ;
2025-08-25 14:38:38 -07:00
}
2025-08-25 20:15:38 -07:00
_ = > { }
2025-08-25 14:38:38 -07:00
}
2025-08-25 20:15:38 -07:00
match key_event {
KeyEvent {
code : KeyCode ::Up ,
modifiers : KeyModifiers ::ALT ,
kind : KeyEventKind ::Press ,
..
} if ! self . queued_user_messages . is_empty ( ) = > {
// Prefer the most recently queued item.
if let Some ( user_message ) = self . queued_user_messages . pop_back ( ) {
self . bottom_pane . set_composer_text ( user_message . text ) ;
2025-08-25 14:38:38 -07:00
self . refresh_queued_user_messages ( ) ;
2025-08-25 20:15:38 -07:00
self . request_redraw ( ) ;
2025-08-25 14:38:38 -07:00
}
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-25 20:15:38 -07:00
_ = > {
match self . bottom_pane . handle_key_event ( key_event ) {
InputResult ::Submitted ( text ) = > {
// If a task is running, queue the user input to be sent after the turn completes.
let user_message = UserMessage {
text ,
image_paths : self . bottom_pane . take_recent_submission_images ( ) ,
} ;
if self . bottom_pane . is_task_running ( ) {
self . queued_user_messages . push_back ( user_message ) ;
self . refresh_queued_user_messages ( ) ;
} else {
self . submit_user_message ( user_message ) ;
}
}
InputResult ::Command ( cmd ) = > {
self . dispatch_command ( cmd ) ;
}
InputResult ::None = > { }
}
2025-08-21 10:36:58 -07:00
}
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-22 18:05:43 +01:00
pub ( crate ) fn attach_image (
& mut self ,
path : PathBuf ,
width : u32 ,
height : u32 ,
format_label : & str ,
) {
tracing ::info! (
" attach_image path={path:?} width={width} height={height} format={format_label} " ,
) ;
self . bottom_pane
2025-09-11 11:59:37 -07:00
. attach_image ( path , width , height , format_label ) ;
2025-08-22 18:05:43 +01:00
self . request_redraw ( ) ;
}
2025-08-21 10:36:58 -07:00
fn dispatch_command ( & mut self , cmd : SlashCommand ) {
2025-08-28 10:15:59 -07:00
if ! cmd . available_during_task ( ) & & self . bottom_pane . is_task_running ( ) {
let message = format! (
2025-09-02 09:46:41 -07:00
" '/{}' is disabled while a task is in progress. " ,
2025-08-28 10:15:59 -07:00
cmd . command ( )
) ;
self . add_to_history ( history_cell ::new_error_event ( message ) ) ;
self . request_redraw ( ) ;
return ;
}
2025-08-21 10:36:58 -07:00
match cmd {
SlashCommand ::New = > {
self . app_event_tx . send ( AppEvent ::NewSession ) ;
}
SlashCommand ::Init = > {
const INIT_PROMPT : & str = include_str! ( " ../prompt_for_init_command.md " ) ;
self . submit_text_message ( INIT_PROMPT . to_string ( ) ) ;
}
SlashCommand ::Compact = > {
self . clear_token_usage ( ) ;
self . app_event_tx . send ( AppEvent ::CodexOp ( Op ::Compact ) ) ;
}
2025-09-18 14:14:16 -07:00
SlashCommand ::Review = > {
2025-09-21 20:18:35 -07:00
self . open_review_popup ( ) ;
2025-09-18 14:14:16 -07:00
}
2025-08-21 10:36:58 -07:00
SlashCommand ::Model = > {
self . open_model_popup ( ) ;
}
SlashCommand ::Approvals = > {
self . open_approvals_popup ( ) ;
}
SlashCommand ::Quit = > {
self . app_event_tx . send ( AppEvent ::ExitRequest ) ;
}
SlashCommand ::Logout = > {
2025-09-02 18:36:19 -07:00
if let Err ( e ) = codex_core ::auth ::logout ( & self . config . codex_home ) {
2025-08-21 10:36:58 -07:00
tracing ::error! ( " failed to logout: {e} " ) ;
}
self . app_event_tx . send ( AppEvent ::ExitRequest ) ;
}
2025-09-23 16:59:52 +01:00
SlashCommand ::Undo = > {
self . undo_last_snapshot ( ) ;
}
2025-08-21 10:36:58 -07:00
SlashCommand ::Diff = > {
self . add_diff_in_progress ( ) ;
let tx = self . app_event_tx . clone ( ) ;
tokio ::spawn ( async move {
let text = match get_git_diff ( ) . await {
Ok ( ( is_git_repo , diff_text ) ) = > {
if is_git_repo {
diff_text
} else {
" `/diff` — _not inside a git repository_ " . to_string ( )
}
}
Err ( e ) = > format! ( " Failed to compute diff: {e} " ) ,
} ;
tx . send ( AppEvent ::DiffResult ( text ) ) ;
} ) ;
}
SlashCommand ::Mention = > {
self . insert_str ( " @ " ) ;
}
SlashCommand ::Status = > {
self . add_status_output ( ) ;
}
SlashCommand ::Mcp = > {
self . add_mcp_output ( ) ;
}
#[ cfg(debug_assertions) ]
SlashCommand ::TestApproval = > {
use codex_core ::protocol ::EventMsg ;
use std ::collections ::HashMap ;
use codex_core ::protocol ::ApplyPatchApprovalRequestEvent ;
use codex_core ::protocol ::FileChange ;
self . app_event_tx . send ( AppEvent ::CodexEvent ( Event {
id : " 1 " . to_string ( ) ,
// msg: EventMsg::ExecApprovalRequest(ExecApprovalRequestEvent {
// call_id: "1".to_string(),
// command: vec!["git".into(), "apply".into()],
// cwd: self.config.cwd.clone(),
// reason: Some("test".to_string()),
// }),
msg : EventMsg ::ApplyPatchApprovalRequest ( ApplyPatchApprovalRequestEvent {
call_id : " 1 " . to_string ( ) ,
changes : HashMap ::from ( [
(
PathBuf ::from ( " /tmp/test.txt " ) ,
FileChange ::Add {
content : " test " . to_string ( ) ,
} ,
) ,
(
PathBuf ::from ( " /tmp/test2.txt " ) ,
FileChange ::Update {
unified_diff : " +test \n -test2 " . to_string ( ) ,
move_path : None ,
} ,
) ,
] ) ,
reason : None ,
grant_root : Some ( PathBuf ::from ( " /tmp " ) ) ,
} ) ,
} ) ) ;
}
}
}
2025-07-12 15:32:00 -07:00
pub ( crate ) fn handle_paste ( & mut self , text : String ) {
2025-07-27 11:04:09 -07:00
self . bottom_pane . handle_paste ( text ) ;
2025-07-12 15:32:00 -07:00
}
2025-08-28 12:54:12 -07:00
// Returns true if caller should skip rendering this frame (a future frame is scheduled).
pub ( crate ) fn handle_paste_burst_tick ( & mut self , frame_requester : FrameRequester ) -> bool {
if self . bottom_pane . flush_paste_burst_if_due ( ) {
// A paste just flushed; request an immediate redraw and skip this frame.
self . request_redraw ( ) ;
true
} else if self . bottom_pane . is_in_paste_burst ( ) {
// While capturing a burst, schedule a follow-up tick and skip this frame
// to avoid redundant renders between ticks.
frame_requester . schedule_frame_in (
crate ::bottom_pane ::ChatComposer ::recommended_paste_flush_delay ( ) ,
) ;
true
} else {
false
}
}
2025-09-24 13:36:01 -07:00
fn flush_active_cell ( & mut self ) {
if let Some ( active ) = self . active_cell . take ( ) {
2025-09-26 22:49:59 -07:00
self . needs_final_message_separator = true ;
2025-09-24 13:36:01 -07:00
self . app_event_tx . send ( AppEvent ::InsertHistoryCell ( active ) ) ;
2025-08-12 17:37:28 -07:00
}
}
2025-08-20 17:09:46 -07:00
fn add_to_history ( & mut self , cell : impl HistoryCell + 'static ) {
2025-09-02 18:47:14 -07:00
self . add_boxed_history ( Box ::new ( cell ) ) ;
}
fn add_boxed_history ( & mut self , cell : Box < dyn HistoryCell > ) {
2025-09-02 10:29:58 -07:00
if ! cell . display_lines ( u16 ::MAX ) . is_empty ( ) {
// Only break exec grouping if the cell renders visible lines.
2025-09-24 13:36:01 -07:00
self . flush_active_cell ( ) ;
2025-09-26 22:49:59 -07:00
self . needs_final_message_separator = true ;
2025-08-22 16:32:31 -07:00
}
2025-08-20 17:09:46 -07:00
self . app_event_tx . send ( AppEvent ::InsertHistoryCell ( cell ) ) ;
2025-07-25 01:56:40 -07:00
}
2025-05-17 09:00:23 -07:00
fn submit_user_message ( & mut self , user_message : UserMessage ) {
let UserMessage { text , image_paths } = user_message ;
2025-09-23 16:59:52 +01:00
if text . is_empty ( ) & & image_paths . is_empty ( ) {
return ;
}
self . capture_ghost_snapshot ( ) ;
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 items : Vec < InputItem > = Vec ::new ( ) ;
if ! text . is_empty ( ) {
items . push ( InputItem ::Text { text : text . clone ( ) } ) ;
}
for path in image_paths {
items . push ( InputItem ::LocalImage { path } ) ;
}
self . codex_op_tx
. send ( Op ::UserInput { items } )
. unwrap_or_else ( | e | {
tracing ::error! ( " failed to send message: {e} " ) ;
} ) ;
feat: record messages from user in ~/.codex/history.jsonl (#939)
This is a large change to support a "history" feature like you would
expect in a shell like Bash.
History events are recorded in `$CODEX_HOME/history.jsonl`. Because it
is a JSONL file, it is straightforward to append new entries (as opposed
to the TypeScript file that uses `$CODEX_HOME/history.json`, so to be
valid JSON, each new entry entails rewriting the entire file). Because
it is possible for there to be multiple instances of Codex CLI writing
to `history.jsonl` at once, we use advisory file locking when working
with `history.jsonl` in `codex-rs/core/src/message_history.rs`.
Because we believe history is a sufficiently useful feature, we enable
it by default. Though to provide some safety, we set the file
permissions of `history.jsonl` to be `o600` so that other users on the
system cannot read the user's history. We do not yet support a default
list of `SENSITIVE_PATTERNS` as the TypeScript CLI does:
https://github.com/openai/codex/blob/3fdf9df1335ac9501e3fb0e61715359145711e8b/codex-cli/src/utils/storage/command-history.ts#L10-L17
We are going to take a more conservative approach to this list in the
Rust CLI. For example, while `/\b[A-Za-z0-9-_]{20,}\b/` might exclude
sensitive information like API tokens, it would also exclude valuable
information such as references to Git commits.
As noted in the updated documentation, users can opt-out of history by
adding the following to `config.toml`:
```toml
[history]
persistence = "none"
```
Because `history.jsonl` could, in theory, be quite large, we take a[n
arguably overly pedantic] approach in reading history entries into
memory. Specifically, we start by telling the client the current number
of entries in the history file (`history_entry_count`) as well as the
inode (`history_log_id`) of `history.jsonl` (see the new fields on
`SessionConfiguredEvent`).
The client is responsible for keeping new entries in memory to create a
"local history," but if the user hits up enough times to go "past" the
end of local history, then the client should use the new
`GetHistoryEntryRequest` in the protocol to fetch older entries.
Specifically, it should pass the `history_log_id` it was given
originally and work backwards from `history_entry_count`. (It should
really fetch history in batches rather than one-at-a-time, but that is
something we can improve upon in subsequent PRs.)
The motivation behind this crazy scheme is that it is designed to defend
against:
* The `history.jsonl` being truncated during the session such that the
index into the history is no longer consistent with what had been read
up to that point. We do not yet have logic to enforce a `max_bytes` for
`history.jsonl`, but once we do, we will aspire to implement it in a way
that should result in a new inode for the file on most systems.
* New items from concurrent Codex CLI sessions amending to the history.
Because, in absence of truncation, `history.jsonl` is an append-only
log, so long as the client reads backwards from `history_entry_count`,
it should always get a consistent view of history. (That said, it will
not be able to read _new_ commands from concurrent sessions, but perhaps
we will introduce a `/` command to reload latest history or something
down the road.)
Admittedly, my testing of this feature thus far has been fairly light. I
expect we will find bugs and introduce enhancements/fixes going forward.
2025-05-15 16:26:23 -07:00
// Persist the text to cross-session message history.
if ! text . is_empty ( ) {
self . codex_op_tx
. send ( Op ::AddToHistory { text : text . clone ( ) } )
. unwrap_or_else ( | e | {
tracing ::error! ( " failed to send AddHistory op: {e} " ) ;
} ) ;
}
2025-08-12 17:37:28 -07:00
// Only show the text portion in conversation history.
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
if ! text . is_empty ( ) {
2025-09-11 11:59:37 -07:00
self . add_to_history ( history_cell ::new_user_prompt ( text ) ) ;
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-09-26 22:49:59 -07:00
self . needs_final_message_separator = false ;
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-09-23 16:59:52 +01:00
fn capture_ghost_snapshot ( & mut self ) {
if self . ghost_snapshots_disabled {
return ;
}
let options = CreateGhostCommitOptions ::new ( & self . config . cwd ) ;
match create_ghost_commit ( & options ) {
Ok ( commit ) = > {
self . ghost_snapshots . push ( commit ) ;
if self . ghost_snapshots . len ( ) > MAX_TRACKED_GHOST_COMMITS {
self . ghost_snapshots . remove ( 0 ) ;
}
}
Err ( err ) = > {
self . ghost_snapshots_disabled = true ;
let ( message , hint ) = match & err {
GitToolingError ::NotAGitRepository { .. } = > (
" Snapshots disabled: current directory is not a Git repository. "
. to_string ( ) ,
None ,
) ,
_ = > (
format! ( " Snapshots disabled after error: {err} " ) ,
Some (
" Restart Codex after resolving the issue to re-enable snapshots. "
. to_string ( ) ,
) ,
) ,
} ;
self . add_info_message ( message , hint ) ;
tracing ::warn! ( " failed to create ghost snapshot: {err} " ) ;
}
}
}
fn undo_last_snapshot ( & mut self ) {
let Some ( commit ) = self . ghost_snapshots . pop ( ) else {
self . add_info_message ( " No snapshot available to undo. " . to_string ( ) , None ) ;
return ;
} ;
if let Err ( err ) = restore_ghost_commit ( & self . config . cwd , & commit ) {
self . add_error_message ( format! ( " Failed to restore snapshot: {err} " ) ) ;
self . ghost_snapshots . push ( commit ) ;
return ;
}
let short_id : String = commit . id ( ) . chars ( ) . take ( 8 ) . collect ( ) ;
self . add_info_message ( format! ( " Restored workspace to snapshot {short_id} " ) , None ) ;
}
2025-09-03 23:20:40 -07:00
/// Replay a subset of initial events into the UI to seed the transcript when
/// resuming an existing session. This approximates the live event flow and
/// is intentionally conservative: only safe-to-replay items are rendered to
/// avoid triggering side effects. Event ids are passed as `None` to
/// distinguish replayed events from live ones.
fn replay_initial_messages ( & mut self , events : Vec < EventMsg > ) {
for msg in events {
if matches! ( msg , EventMsg ::SessionConfigured ( _ ) ) {
continue ;
}
// `id: None` indicates a synthetic/fake id coming from replay.
self . dispatch_event_msg ( None , msg , true ) ;
}
}
2025-05-15 14:50:30 -07:00
pub ( crate ) fn handle_codex_event ( & mut self , event : Event ) {
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 Event { id , msg } = event ;
2025-09-03 23:20:40 -07:00
self . dispatch_event_msg ( Some ( id ) , msg , false ) ;
}
2025-08-11 11:43:58 -07:00
2025-09-03 23:20:40 -07:00
/// Dispatch a protocol `EventMsg` to the appropriate handler.
///
/// `id` is `Some` for live events and `None` for replayed events from
/// `replay_initial_messages()`. Callers should treat `None` as a "fake" id
/// that must not be used to correlate follow-up actions.
fn dispatch_event_msg ( & mut self , id : Option < String > , msg : EventMsg , from_replay : bool ) {
2025-08-11 11:43:58 -07:00
match msg {
EventMsg ::AgentMessageDelta ( _ )
| EventMsg ::AgentReasoningDelta ( _ )
| EventMsg ::ExecCommandOutputDelta ( _ ) = > { }
_ = > {
2025-08-14 13:01:15 -07:00
tracing ::trace! ( " handle_codex_event: {:?} " , msg ) ;
2025-08-11 11:43:58 -07:00
}
}
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
match msg {
2025-08-12 17:37:28 -07:00
EventMsg ::SessionConfigured ( e ) = > self . on_session_configured ( e ) ,
EventMsg ::AgentMessage ( AgentMessageEvent { message } ) = > self . on_agent_message ( message ) ,
2025-07-16 22:26:31 -07:00
EventMsg ::AgentMessageDelta ( AgentMessageDeltaEvent { delta } ) = > {
2025-08-12 17:37:28 -07:00
self . on_agent_message_delta ( delta )
2025-05-10 21:43:27 -07:00
}
2025-08-12 17:37:28 -07:00
EventMsg ::AgentReasoningDelta ( AgentReasoningDeltaEvent { delta } )
| EventMsg ::AgentReasoningRawContentDelta ( AgentReasoningRawContentDeltaEvent {
2025-08-05 01:56:13 -07:00
delta ,
2025-08-12 17:37:28 -07:00
} ) = > self . on_agent_reasoning_delta ( delta ) ,
2025-09-14 17:50:06 -07:00
EventMsg ::AgentReasoning ( AgentReasoningEvent { .. } ) = > self . on_agent_reasoning_final ( ) ,
EventMsg ::AgentReasoningRawContent ( AgentReasoningRawContentEvent { text } ) = > {
self . on_agent_reasoning_delta ( text ) ;
2025-08-20 16:58:56 -07:00
self . on_agent_reasoning_final ( )
2025-08-12 17:37:28 -07:00
}
EventMsg ::AgentReasoningSectionBreak ( _ ) = > self . on_reasoning_section_break ( ) ,
2025-08-27 00:04:21 -07:00
EventMsg ::TaskStarted ( _ ) = > self . on_task_started ( ) ,
2025-09-17 11:23:46 -07:00
EventMsg ::TaskComplete ( TaskCompleteEvent { last_agent_message } ) = > {
self . on_task_complete ( last_agent_message )
}
2025-09-21 10:20:49 -07:00
EventMsg ::TokenCount ( ev ) = > {
self . set_token_info ( ev . info ) ;
self . on_rate_limit_snapshot ( ev . rate_limits ) ;
}
2025-08-12 17:37:28 -07:00
EventMsg ::Error ( ErrorEvent { message } ) = > self . on_error ( message ) ,
2025-08-22 20:34:43 -07:00
EventMsg ::TurnAborted ( ev ) = > match ev . reason {
TurnAbortReason ::Interrupted = > {
2025-09-18 14:14:16 -07:00
self . on_interrupted_turn ( ev . reason ) ;
2025-08-22 20:34:43 -07:00
}
TurnAbortReason ::Replaced = > {
self . on_error ( " Turn aborted: replaced by a new task " . to_owned ( ) )
}
2025-09-18 14:14:16 -07:00
TurnAbortReason ::ReviewEnded = > {
self . on_interrupted_turn ( ev . reason ) ;
}
2025-08-22 20:34:43 -07:00
} ,
2025-08-12 17:37:28 -07:00
EventMsg ::PlanUpdate ( update ) = > self . on_plan_update ( update ) ,
2025-09-03 23:20:40 -07:00
EventMsg ::ExecApprovalRequest ( ev ) = > {
// For replayed events, synthesize an empty id (these should not occur).
2025-09-11 11:59:37 -07:00
self . on_exec_approval_request ( id . unwrap_or_default ( ) , ev )
2025-09-03 23:20:40 -07:00
}
EventMsg ::ApplyPatchApprovalRequest ( ev ) = > {
2025-09-11 11:59:37 -07:00
self . on_apply_patch_approval_request ( id . unwrap_or_default ( ) , ev )
2025-09-03 23:20:40 -07:00
}
2025-08-12 17:37:28 -07:00
EventMsg ::ExecCommandBegin ( ev ) = > self . on_exec_command_begin ( ev ) ,
EventMsg ::ExecCommandOutputDelta ( delta ) = > self . on_exec_command_output_delta ( delta ) ,
EventMsg ::PatchApplyBegin ( ev ) = > self . on_patch_apply_begin ( ev ) ,
EventMsg ::PatchApplyEnd ( ev ) = > self . on_patch_apply_end ( ev ) ,
EventMsg ::ExecCommandEnd ( ev ) = > self . on_exec_command_end ( ev ) ,
2025-10-02 11:36:03 -07:00
EventMsg ::ViewImageToolCall ( ev ) = > self . on_view_image_tool_call ( ev ) ,
2025-08-12 17:37:28 -07:00
EventMsg ::McpToolCallBegin ( ev ) = > self . on_mcp_tool_call_begin ( ev ) ,
EventMsg ::McpToolCallEnd ( ev ) = > self . on_mcp_tool_call_end ( ev ) ,
2025-08-23 22:58:56 -07:00
EventMsg ::WebSearchBegin ( ev ) = > self . on_web_search_begin ( ev ) ,
2025-08-28 19:24:38 -07:00
EventMsg ::WebSearchEnd ( ev ) = > self . on_web_search_end ( ev ) ,
2025-08-12 17:37:28 -07:00
EventMsg ::GetHistoryEntryResponse ( ev ) = > self . on_get_history_entry_response ( ev ) ,
2025-08-19 09:00:31 -07:00
EventMsg ::McpListToolsResponse ( ev ) = > self . on_list_mcp_tools ( ev ) ,
2025-08-28 19:16:39 -07:00
EventMsg ::ListCustomPromptsResponse ( ev ) = > self . on_list_custom_prompts ( ev ) ,
2025-08-12 17:37:28 -07:00
EventMsg ::ShutdownComplete = > self . on_shutdown_complete ( ) ,
EventMsg ::TurnDiff ( TurnDiffEvent { unified_diff } ) = > self . on_turn_diff ( unified_diff ) ,
2025-08-05 22:44:27 -07:00
EventMsg ::BackgroundEvent ( BackgroundEventEvent { message } ) = > {
2025-08-12 17:37:28 -07:00
self . on_background_event ( message )
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-21 01:15:24 -07:00
EventMsg ::StreamError ( StreamErrorEvent { message } ) = > self . on_stream_error ( message ) ,
2025-09-03 23:20:40 -07:00
EventMsg ::UserMessage ( ev ) = > {
if from_replay {
self . on_user_message_event ( ev ) ;
}
}
2025-09-10 17:42:54 -07:00
EventMsg ::ConversationPath ( ev ) = > {
2025-08-23 23:23:15 -07:00
self . app_event_tx
. send ( crate ::app_event ::AppEvent ::ConversationHistory ( ev ) ) ;
}
2025-09-18 14:14:16 -07:00
EventMsg ::EnteredReviewMode ( review_request ) = > {
self . on_entered_review_mode ( review_request )
}
EventMsg ::ExitedReviewMode ( review ) = > self . on_exited_review_mode ( review ) ,
}
}
fn on_entered_review_mode ( & mut self , review : ReviewRequest ) {
// Enter review mode and emit a concise banner
self . is_review_mode = true ;
let banner = format! ( " >> Code review started: {} << " , review . user_facing_hint ) ;
self . add_to_history ( history_cell ::new_review_status_line ( banner ) ) ;
self . request_redraw ( ) ;
}
fn on_exited_review_mode ( & mut self , review : ExitedReviewModeEvent ) {
// Leave review mode; if output is present, flush pending stream + show results.
if let Some ( output ) = review . review_output {
self . flush_answer_stream_with_separator ( ) ;
self . flush_interrupt_queue ( ) ;
2025-09-24 13:36:01 -07:00
self . flush_active_cell ( ) ;
2025-09-18 14:14:16 -07:00
if output . findings . is_empty ( ) {
let explanation = output . overall_explanation . trim ( ) . to_string ( ) ;
if explanation . is_empty ( ) {
tracing ::error! ( " Reviewer failed to output a response. " ) ;
self . add_to_history ( history_cell ::new_error_event (
" Reviewer failed to output a response. " . to_owned ( ) ,
) ) ;
} else {
// Show explanation when there are no structured findings.
let mut rendered : Vec < ratatui ::text ::Line < 'static > > = vec! [ " " . into ( ) ] ;
2025-09-30 16:13:55 -07:00
append_markdown ( & explanation , None , & mut rendered , & self . config ) ;
2025-09-18 14:14:16 -07:00
let body_cell = AgentMessageCell ::new ( rendered , false ) ;
self . app_event_tx
. send ( AppEvent ::InsertHistoryCell ( Box ::new ( body_cell ) ) ) ;
}
} else {
let message_text =
codex_core ::review_format ::format_review_findings_block ( & output . findings , None ) ;
let mut message_lines : Vec < ratatui ::text ::Line < 'static > > = Vec ::new ( ) ;
2025-09-30 16:13:55 -07:00
append_markdown ( & message_text , None , & mut message_lines , & self . config ) ;
2025-09-18 14:14:16 -07:00
let body_cell = AgentMessageCell ::new ( message_lines , true ) ;
self . app_event_tx
. send ( AppEvent ::InsertHistoryCell ( Box ::new ( body_cell ) ) ) ;
}
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-09-18 14:14:16 -07:00
self . is_review_mode = false ;
// Append a finishing banner at the end of this turn.
self . add_to_history ( history_cell ::new_review_status_line (
" << Code review finished >> " . to_string ( ) ,
) ) ;
self . request_redraw ( ) ;
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-09-03 23:20:40 -07:00
fn on_user_message_event ( & mut self , event : UserMessageEvent ) {
match event . kind {
Some ( InputMessageKind ::EnvironmentContext )
| Some ( InputMessageKind ::UserInstructions ) = > {
// Skip XML‑ wrapped context blocks in the transcript.
}
Some ( InputMessageKind ::Plain ) | None = > {
let message = event . message . trim ( ) ;
if ! message . is_empty ( ) {
self . add_to_history ( history_cell ::new_user_prompt ( message . to_string ( ) ) ) ;
}
}
}
}
2025-05-15 14:50:30 -07:00
fn request_redraw ( & mut self ) {
2025-08-20 13:47:24 -07:00
self . frame_requester . schedule_frame ( ) ;
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-09-15 10:22:02 -07:00
fn notify ( & mut self , notification : Notification ) {
if ! notification . allowed_for ( & self . config . tui_notifications ) {
return ;
}
self . pending_notification = Some ( notification ) ;
self . request_redraw ( ) ;
}
pub ( crate ) fn maybe_post_pending_notification ( & mut self , tui : & mut crate ::tui ::Tui ) {
if let Some ( notif ) = self . pending_notification . take ( ) {
tui . notify ( notif . display ( ) ) ;
}
}
2025-09-24 13:36:01 -07:00
/// Mark the active cell as failed (✗) and flush it into history.
fn finalize_active_cell_as_failed ( & mut self ) {
if let Some ( mut cell ) = self . active_cell . take ( ) {
// Insert finalized cell into history and keep grouping consistent.
if let Some ( exec ) = cell . as_any_mut ( ) . downcast_mut ::< ExecCell > ( ) {
exec . mark_failed ( ) ;
} else if let Some ( tool ) = cell . as_any_mut ( ) . downcast_mut ::< McpToolCallCell > ( ) {
tool . mark_failed ( ) ;
}
self . add_boxed_history ( cell ) ;
2025-08-25 20:15:38 -07:00
}
}
2025-08-25 14:38:38 -07:00
// If idle and there are queued inputs, submit exactly one to start the next turn.
fn maybe_send_next_queued_input ( & mut self ) {
if self . bottom_pane . is_task_running ( ) {
return ;
}
if let Some ( user_message ) = self . queued_user_messages . pop_front ( ) {
self . submit_user_message ( user_message ) ;
}
// Update the list to reflect the remaining queued messages (if any).
self . refresh_queued_user_messages ( ) ;
}
/// Rebuild and update the queued user messages from the current queue.
fn refresh_queued_user_messages ( & mut self ) {
let messages : Vec < String > = self
. queued_user_messages
. iter ( )
. map ( | m | m . text . clone ( ) )
. collect ( ) ;
self . bottom_pane . set_queued_user_messages ( messages ) ;
}
2025-08-15 15:32:41 -07:00
pub ( crate ) fn add_diff_in_progress ( & mut self ) {
self . request_redraw ( ) ;
}
2025-08-22 08:24:13 -07:00
pub ( crate ) fn on_diff_complete ( & mut self ) {
2025-08-25 14:38:38 -07:00
self . request_redraw ( ) ;
2025-04-25 12:01:52 -07:00
}
2025-08-05 23:57:52 -07:00
pub ( crate ) fn add_status_output ( & mut self ) {
2025-10-01 11:03:05 -07:00
let default_usage = TokenUsage ::default ( ) ;
let ( total_usage , context_usage ) = if let Some ( ti ) = & self . token_info {
( & ti . total_token_usage , Some ( & ti . last_token_usage ) )
2025-09-06 08:19:23 -07:00
} else {
2025-10-01 11:03:05 -07:00
( & default_usage , Some ( & default_usage ) )
2025-09-06 08:19:23 -07:00
} ;
2025-09-26 16:35:56 -07:00
self . add_to_history ( crate ::status ::new_status_output (
2025-08-05 23:57:52 -07:00
& self . config ,
2025-10-01 11:03:05 -07:00
total_usage ,
context_usage ,
2025-09-07 20:22:25 -07:00
& self . conversation_id ,
2025-09-22 11:13:34 -07:00
self . rate_limit_snapshot . as_ref ( ) ,
2025-08-05 23:57:52 -07:00
) ) ;
}
2025-10-02 12:38:24 -07:00
/// Open a popup to choose the model (stage 1). After selecting a model,
/// a second popup is shown to choose the reasoning effort.
2025-08-19 10:55:07 -07:00
pub ( crate ) fn open_model_popup ( & mut self ) {
let current_model = self . config . model . clone ( ) ;
2025-09-14 21:32:18 -04:00
let auth_mode = self . auth_manager . auth ( ) . map ( | auth | auth . mode ) ;
let presets : Vec < ModelPreset > = builtin_model_presets ( auth_mode ) ;
2025-08-19 10:55:07 -07:00
2025-10-02 20:34:58 -07:00
let mut grouped : Vec < ( & str , Vec < ModelPreset > ) > = Vec ::new ( ) ;
2025-10-02 12:38:24 -07:00
for preset in presets . into_iter ( ) {
2025-10-02 20:34:58 -07:00
if let Some ( ( _ , entries ) ) = grouped . iter_mut ( ) . find ( | ( model , _ ) | * model = = preset . model )
{
entries . push ( preset ) ;
} else {
grouped . push ( ( preset . model , vec! [ preset ] ) ) ;
}
2025-10-02 12:38:24 -07:00
}
let mut items : Vec < SelectionItem > = Vec ::new ( ) ;
for ( model_slug , entries ) in grouped . into_iter ( ) {
let name = model_slug . to_string ( ) ;
let description = Self ::model_description_for ( model_slug )
. map ( std ::string ::ToString ::to_string )
. or_else ( | | {
entries
. iter ( )
. find ( | preset | ! preset . description . is_empty ( ) )
. map ( | preset | preset . description . to_string ( ) )
} )
. or_else ( | | entries . first ( ) . map ( | preset | preset . description . to_string ( ) ) ) ;
let is_current = model_slug = = current_model ;
let model_slug_string = model_slug . to_string ( ) ;
let presets_for_model = entries . clone ( ) ;
let actions : Vec < SelectionAction > = vec! [ Box ::new ( move | tx | {
tx . send ( AppEvent ::OpenReasoningPopup {
model : model_slug_string . clone ( ) ,
presets : presets_for_model . clone ( ) ,
} ) ;
} ) ] ;
items . push ( SelectionItem {
name ,
description ,
is_current ,
actions ,
2025-10-02 21:07:47 -07:00
dismiss_on_select : false ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-10-02 12:38:24 -07:00
} ) ;
}
self . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-03 16:17:30 -07:00
title : Some ( " Select Model and Effort " . to_string ( ) ) ,
2025-10-02 12:38:24 -07:00
subtitle : Some ( " Switch the model for this and future Codex CLI sessions " . to_string ( ) ) ,
2025-10-03 16:17:30 -07:00
footer_hint : Some ( " Press enter to select reasoning effort, or esc to dismiss. " . into ( ) ) ,
2025-10-02 12:38:24 -07:00
items ,
.. Default ::default ( )
} ) ;
}
/// Open a popup to choose the reasoning effort (stage 2) for the given model.
pub ( crate ) fn open_reasoning_popup ( & mut self , model_slug : String , presets : Vec < ModelPreset > ) {
let default_effort = ReasoningEffortConfig ::default ( ) ;
let has_none_choice = presets . iter ( ) . any ( | preset | preset . effort . is_none ( ) ) ;
struct EffortChoice {
stored : Option < ReasoningEffortConfig > ,
display : ReasoningEffortConfig ,
}
let mut choices : Vec < EffortChoice > = Vec ::new ( ) ;
for effort in ReasoningEffortConfig ::iter ( ) {
if presets . iter ( ) . any ( | preset | preset . effort = = Some ( effort ) ) {
choices . push ( EffortChoice {
stored : Some ( effort ) ,
display : effort ,
} ) ;
}
if has_none_choice & & default_effort = = effort {
choices . push ( EffortChoice {
stored : None ,
display : effort ,
} ) ;
}
}
if choices . is_empty ( ) {
choices . push ( EffortChoice {
stored : Some ( default_effort ) ,
display : default_effort ,
} ) ;
}
let default_choice : Option < ReasoningEffortConfig > = if has_none_choice {
None
} else if choices
. iter ( )
. any ( | choice | choice . stored = = Some ( default_effort ) )
{
Some ( default_effort )
} else {
choices
. iter ( )
. find_map ( | choice | choice . stored )
. or ( Some ( default_effort ) )
} ;
let is_current_model = self . config . model = = model_slug ;
let highlight_choice = if is_current_model {
self . config . model_reasoning_effort
} else {
default_choice
} ;
2025-08-19 10:55:07 -07:00
let mut items : Vec < SelectionItem > = Vec ::new ( ) ;
2025-10-02 12:38:24 -07:00
for choice in choices . iter ( ) {
let effort = choice . display ;
let mut effort_label = effort . to_string ( ) ;
if let Some ( first ) = effort_label . get_mut ( 0 .. 1 ) {
first . make_ascii_uppercase ( ) ;
}
if choice . stored = = default_choice {
effort_label . push_str ( " (default) " ) ;
}
let description = presets
. iter ( )
. find ( | preset | preset . effort = = choice . stored & & ! preset . description . is_empty ( ) )
. map ( | preset | preset . description . to_string ( ) )
. or_else ( | | {
presets
. iter ( )
. find ( | preset | preset . effort = = choice . stored )
. map ( | preset | preset . description . to_string ( ) )
} ) ;
let model_for_action = model_slug . clone ( ) ;
let effort_for_action = choice . stored ;
2025-08-19 10:55:07 -07:00
let actions : Vec < SelectionAction > = vec! [ Box ::new ( move | tx | {
tx . send ( AppEvent ::CodexOp ( Op ::OverrideTurnContext {
cwd : None ,
approval_policy : None ,
sandbox_policy : None ,
2025-10-02 12:38:24 -07:00
model : Some ( model_for_action . clone ( ) ) ,
effort : Some ( effort_for_action ) ,
2025-08-19 10:55:07 -07:00
summary : None ,
} ) ) ;
2025-10-02 12:38:24 -07:00
tx . send ( AppEvent ::UpdateModel ( model_for_action . clone ( ) ) ) ;
tx . send ( AppEvent ::UpdateReasoningEffort ( effort_for_action ) ) ;
2025-09-15 00:25:43 +01:00
tx . send ( AppEvent ::PersistModelSelection {
2025-10-02 12:38:24 -07:00
model : model_for_action . clone ( ) ,
effort : effort_for_action ,
2025-09-15 00:25:43 +01:00
} ) ;
2025-09-02 10:59:07 -07:00
tracing ::info! (
2025-10-02 12:38:24 -07:00
" Selected model: {}, Selected effort: {} " ,
model_for_action ,
effort_for_action
. map ( | e | e . to_string ( ) )
. unwrap_or_else ( | | " default " . to_string ( ) )
2025-09-02 10:59:07 -07:00
) ;
2025-08-19 10:55:07 -07:00
} ) ] ;
2025-10-02 12:38:24 -07:00
2025-08-19 10:55:07 -07:00
items . push ( SelectionItem {
2025-10-02 12:38:24 -07:00
name : effort_label ,
2025-08-19 10:55:07 -07:00
description ,
2025-10-02 12:38:24 -07:00
is_current : is_current_model & & choice . stored = = highlight_choice ,
2025-08-19 10:55:07 -07:00
actions ,
2025-09-21 20:18:35 -07:00
dismiss_on_select : true ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-08-19 10:55:07 -07:00
} ) ;
}
2025-09-21 20:18:35 -07:00
self . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-02 12:38:24 -07:00
title : Some ( " Select Reasoning Level " . to_string ( ) ) ,
subtitle : Some ( format! ( " Reasoning for model {model_slug} " ) ) ,
2025-10-02 11:34:47 -07:00
footer_hint : Some ( standard_popup_hint_line ( ) ) ,
2025-08-19 10:55:07 -07:00
items ,
2025-09-21 20:18:35 -07:00
.. Default ::default ( )
} ) ;
2025-08-19 10:55:07 -07:00
}
2025-08-19 22:34:37 -07:00
/// Open a popup to choose the approvals mode (ask for approval policy + sandbox policy).
pub ( crate ) fn open_approvals_popup ( & mut self ) {
let current_approval = self . config . approval_policy ;
let current_sandbox = self . config . sandbox_policy . clone ( ) ;
let mut items : Vec < SelectionItem > = Vec ::new ( ) ;
let presets : Vec < ApprovalPreset > = builtin_approval_presets ( ) ;
for preset in presets . into_iter ( ) {
let is_current =
current_approval = = preset . approval & & current_sandbox = = preset . sandbox ;
let approval = preset . approval ;
let sandbox = preset . sandbox . clone ( ) ;
let name = preset . label . to_string ( ) ;
let description = Some ( preset . description . to_string ( ) ) ;
let actions : Vec < SelectionAction > = vec! [ Box ::new ( move | tx | {
tx . send ( AppEvent ::CodexOp ( Op ::OverrideTurnContext {
cwd : None ,
approval_policy : Some ( approval ) ,
sandbox_policy : Some ( sandbox . clone ( ) ) ,
model : None ,
effort : None ,
summary : None ,
} ) ) ;
tx . send ( AppEvent ::UpdateAskForApprovalPolicy ( approval ) ) ;
tx . send ( AppEvent ::UpdateSandboxPolicy ( sandbox . clone ( ) ) ) ;
} ) ] ;
items . push ( SelectionItem {
name ,
description ,
is_current ,
actions ,
2025-09-21 20:18:35 -07:00
dismiss_on_select : true ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-08-19 22:34:37 -07:00
} ) ;
}
2025-09-21 20:18:35 -07:00
self . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-01 14:29:05 -07:00
title : Some ( " Select Approval Mode " . to_string ( ) ) ,
2025-10-02 11:34:47 -07:00
footer_hint : Some ( standard_popup_hint_line ( ) ) ,
2025-08-19 22:34:37 -07:00
items ,
2025-09-21 20:18:35 -07:00
.. Default ::default ( )
} ) ;
2025-08-19 22:34:37 -07:00
}
/// Set the approval policy in the widget's config copy.
pub ( crate ) fn set_approval_policy ( & mut self , policy : AskForApproval ) {
self . config . approval_policy = policy ;
}
/// Set the sandbox policy in the widget's config copy.
pub ( crate ) fn set_sandbox_policy ( & mut self , policy : SandboxPolicy ) {
self . config . sandbox_policy = policy ;
}
2025-08-19 10:55:07 -07:00
/// Set the reasoning effort in the widget's config copy.
2025-09-12 12:06:33 -07:00
pub ( crate ) fn set_reasoning_effort ( & mut self , effort : Option < ReasoningEffortConfig > ) {
2025-08-19 10:55:07 -07:00
self . config . model_reasoning_effort = effort ;
}
/// Set the model in the widget's config copy.
2025-09-14 20:53:50 -04:00
pub ( crate ) fn set_model ( & mut self , model : & str ) {
self . session_header . set_model ( model ) ;
self . config . model = model . to_string ( ) ;
2025-08-19 10:55:07 -07:00
}
2025-09-12 14:09:31 -07:00
pub ( crate ) fn add_info_message ( & mut self , message : String , hint : Option < String > ) {
self . add_to_history ( history_cell ::new_info_event ( message , hint ) ) ;
2025-09-11 15:04:29 -07:00
self . request_redraw ( ) ;
}
pub ( crate ) fn add_error_message ( & mut self , message : String ) {
self . add_to_history ( history_cell ::new_error_event ( message ) ) ;
self . request_redraw ( ) ;
}
2025-08-19 09:00:31 -07:00
pub ( crate ) fn add_mcp_output ( & mut self ) {
if self . config . mcp_servers . is_empty ( ) {
2025-08-20 17:09:46 -07:00
self . add_to_history ( history_cell ::empty_mcp_output ( ) ) ;
2025-08-19 09:00:31 -07:00
} else {
self . submit_op ( Op ::ListMcpTools ) ;
}
}
feat: add support for @ to do file search (#1401)
Introduces support for `@` to trigger a fuzzy-filename search in the
composer. Under the hood, this leverages
https://crates.io/crates/nucleo-matcher to do the fuzzy matching and
https://crates.io/crates/ignore to build up the list of file candidates
(so that it respects `.gitignore`).
For simplicity (at least for now), we do not do any caching between
searches like VS Code does for its file search:
https://github.com/microsoft/vscode/blob/1d89ed699b2e924d418c856318a3e12bca67ff3a/src/vs/workbench/services/search/node/rawSearchService.ts#L212-L218
Because we do not do any caching, I saw queries take up to three seconds
on large repositories with hundreds of thousands of files. To that end,
we do not perform searches synchronously on each keystroke, but instead
dispatch an event to do the search on a background thread that
asynchronously reports back to the UI when the results are available.
This is largely handled by the `FileSearchManager` introduced in this
PR, which also has logic for debouncing requests so there is at most one
search in flight at a time.
While we could potentially polish and tune this feature further, it may
already be overengineered for how it will be used, in practice, so we
can improve things going forward if it turns out that this is not "good
enough" in the wild.
Note this feature does not work like `@` in the TypeScript CLI, which
was more like directory-based tab completion. In the Rust CLI, `@`
triggers a full-repo fuzzy-filename search.
Fixes https://github.com/openai/codex/issues/1261.
2025-06-28 13:47:42 -07:00
/// Forward file-search results to the bottom pane.
2025-06-28 15:04:23 -07:00
pub ( crate ) fn apply_file_search_result ( & mut self , query : String , matches : Vec < FileMatch > ) {
feat: add support for @ to do file search (#1401)
Introduces support for `@` to trigger a fuzzy-filename search in the
composer. Under the hood, this leverages
https://crates.io/crates/nucleo-matcher to do the fuzzy matching and
https://crates.io/crates/ignore to build up the list of file candidates
(so that it respects `.gitignore`).
For simplicity (at least for now), we do not do any caching between
searches like VS Code does for its file search:
https://github.com/microsoft/vscode/blob/1d89ed699b2e924d418c856318a3e12bca67ff3a/src/vs/workbench/services/search/node/rawSearchService.ts#L212-L218
Because we do not do any caching, I saw queries take up to three seconds
on large repositories with hundreds of thousands of files. To that end,
we do not perform searches synchronously on each keystroke, but instead
dispatch an event to do the search on a background thread that
asynchronously reports back to the UI when the results are available.
This is largely handled by the `FileSearchManager` introduced in this
PR, which also has logic for debouncing requests so there is at most one
search in flight at a time.
While we could potentially polish and tune this feature further, it may
already be overengineered for how it will be used, in practice, so we
can improve things going forward if it turns out that this is not "good
enough" in the wild.
Note this feature does not work like `@` in the TypeScript CLI, which
was more like directory-based tab completion. In the Rust CLI, `@`
triggers a full-repo fuzzy-filename search.
Fixes https://github.com/openai/codex/issues/1261.
2025-06-28 13:47:42 -07:00
self . bottom_pane . on_file_search_result ( query , matches ) ;
}
2025-06-27 13:37:11 -04:00
/// Handle Ctrl-C key press.
2025-08-25 20:15:38 -07:00
fn on_ctrl_c ( & mut self ) {
2025-09-07 20:21:53 -07:00
if self . bottom_pane . on_ctrl_c ( ) = = CancellationEvent ::Handled {
return ;
}
if self . bottom_pane . is_task_running ( ) {
self . bottom_pane . show_ctrl_c_quit_hint ( ) ;
self . submit_op ( Op ::Interrupt ) ;
return ;
2025-06-27 13:37:11 -04:00
}
2025-09-07 20:21:53 -07:00
self . submit_op ( Op ::Shutdown ) ;
2025-06-27 13:37:11 -04:00
}
2025-07-16 08:59:26 -07:00
pub ( crate ) fn composer_is_empty ( & self ) -> bool {
self . bottom_pane . composer_is_empty ( )
}
2025-08-25 15:38:46 -07:00
/// True when the UI is in the regular composer state with no running task,
/// no modal overlay (e.g. approvals or status indicator), and no composer popups.
/// In this state Esc-Esc backtracking is enabled.
pub ( crate ) fn is_normal_backtrack_mode ( & self ) -> bool {
self . bottom_pane . is_normal_backtrack_mode ( )
}
2025-08-12 17:37:28 -07:00
pub ( crate ) fn insert_str ( & mut self , text : & str ) {
self . bottom_pane . insert_str ( text ) ;
}
2025-08-23 23:23:15 -07:00
2025-09-11 11:45:17 -07:00
/// Replace the composer content with the provided text and reset cursor.
pub ( crate ) fn set_composer_text ( & mut self , text : String ) {
self . bottom_pane . set_composer_text ( text ) ;
}
2025-08-23 23:23:15 -07:00
pub ( crate ) fn show_esc_backtrack_hint ( & mut self ) {
self . bottom_pane . show_esc_backtrack_hint ( ) ;
}
pub ( crate ) fn clear_esc_backtrack_hint ( & mut self ) {
self . bottom_pane . clear_esc_backtrack_hint ( ) ;
}
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
/// Forward an `Op` directly to codex.
pub ( crate ) fn submit_op ( & self , op : Op ) {
2025-08-12 17:37:28 -07:00
// Record outbound operation for session replay fidelity.
crate ::session_log ::log_outbound_op ( & op ) ;
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
if let Err ( e ) = self . codex_op_tx . send ( op ) {
tracing ::error! ( " failed to submit op: {e} " ) ;
}
}
2025-07-25 01:56:40 -07:00
2025-08-19 09:00:31 -07:00
fn on_list_mcp_tools ( & mut self , ev : McpListToolsResponseEvent ) {
2025-10-08 14:37:57 -07:00
self . add_to_history ( history_cell ::new_mcp_tools_output (
& self . config ,
ev . tools ,
& ev . auth_statuses ,
) ) ;
2025-08-19 09:00:31 -07:00
}
2025-08-28 19:16:39 -07:00
fn on_list_custom_prompts ( & mut self , ev : ListCustomPromptsResponseEvent ) {
let len = ev . custom_prompts . len ( ) ;
debug! ( " received {len} custom prompts " ) ;
// Forward to bottom pane so the slash popup can show them now.
self . bottom_pane . set_custom_prompts ( ev . custom_prompts ) ;
}
2025-09-21 20:18:35 -07:00
pub ( crate ) fn open_review_popup ( & mut self ) {
let mut items : Vec < SelectionItem > = Vec ::new ( ) ;
2025-09-29 23:03:37 -07:00
items . push ( SelectionItem {
name : " Review against a base branch " . to_string ( ) ,
description : Some ( " (PR Style) " . into ( ) ) ,
actions : vec ! [ Box ::new ( {
let cwd = self . config . cwd . clone ( ) ;
move | tx | {
tx . send ( AppEvent ::OpenReviewBranchPicker ( cwd . clone ( ) ) ) ;
}
} ) ] ,
dismiss_on_select : false ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-29 23:03:37 -07:00
} ) ;
2025-09-21 20:18:35 -07:00
items . push ( SelectionItem {
name : " Review uncommitted changes " . to_string ( ) ,
actions : vec ! [ Box ::new (
move | tx : & AppEventSender | {
tx . send ( AppEvent ::CodexOp ( Op ::Review {
review_request : ReviewRequest {
prompt : " Review the current code changes (staged, unstaged, and untracked files) and provide prioritized findings. " . to_string ( ) ,
user_facing_hint : " current changes " . to_string ( ) ,
} ,
} ) ) ;
} ,
) ] ,
dismiss_on_select : true ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-21 20:18:35 -07:00
} ) ;
// New: Review a specific commit (opens commit picker)
items . push ( SelectionItem {
name : " Review a commit " . to_string ( ) ,
actions : vec ! [ Box ::new ( {
let cwd = self . config . cwd . clone ( ) ;
move | tx | {
tx . send ( AppEvent ::OpenReviewCommitPicker ( cwd . clone ( ) ) ) ;
}
} ) ] ,
dismiss_on_select : false ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-21 20:18:35 -07:00
} ) ;
items . push ( SelectionItem {
name : " Custom review instructions " . to_string ( ) ,
actions : vec ! [ Box ::new ( move | tx | {
tx . send ( AppEvent ::OpenReviewCustomPrompt ) ;
} ) ] ,
dismiss_on_select : false ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-21 20:18:35 -07:00
} ) ;
self . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-01 14:29:05 -07:00
title : Some ( " Select a review preset " . into ( ) ) ,
2025-10-02 11:34:47 -07:00
footer_hint : Some ( standard_popup_hint_line ( ) ) ,
2025-09-21 20:18:35 -07:00
items ,
.. Default ::default ( )
} ) ;
}
pub ( crate ) async fn show_review_branch_picker ( & mut self , cwd : & Path ) {
let branches = local_git_branches ( cwd ) . await ;
let current_branch = current_branch_name ( cwd )
. await
. unwrap_or_else ( | | " (detached HEAD) " . to_string ( ) ) ;
let mut items : Vec < SelectionItem > = Vec ::with_capacity ( branches . len ( ) ) ;
for option in branches {
let branch = option . clone ( ) ;
items . push ( SelectionItem {
name : format ! ( " {current_branch} -> {branch} " ) ,
actions : vec ! [ Box ::new ( move | tx3 : & AppEventSender | {
tx3 . send ( AppEvent ::CodexOp ( Op ::Review {
review_request : ReviewRequest {
prompt : format ! (
2025-09-22 12:34:08 -07:00
" Review the code changes against the base branch '{branch}'. Start by finding the merge diff between the current branch and {branch}'s upstream e.g. (`git merge-base HEAD \" $(git rev-parse --abbrev-ref \" {branch}@{{upstream}} \" ) \" `), then run `git diff` against that SHA to see what changes we would merge into the {branch} branch. Provide prioritized, actionable findings. "
2025-09-21 20:18:35 -07:00
) ,
user_facing_hint : format ! ( " changes against '{branch}' " ) ,
} ,
} ) ) ;
} ) ] ,
dismiss_on_select : true ,
search_value : Some ( option ) ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-21 20:18:35 -07:00
} ) ;
}
self . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-01 14:29:05 -07:00
title : Some ( " Select a base branch " . to_string ( ) ) ,
2025-10-02 11:34:47 -07:00
footer_hint : Some ( standard_popup_hint_line ( ) ) ,
2025-09-21 20:18:35 -07:00
items ,
is_searchable : true ,
search_placeholder : Some ( " Type to search branches " . to_string ( ) ) ,
.. Default ::default ( )
} ) ;
}
pub ( crate ) async fn show_review_commit_picker ( & mut self , cwd : & Path ) {
let commits = codex_core ::git_info ::recent_commits ( cwd , 100 ) . await ;
let mut items : Vec < SelectionItem > = Vec ::with_capacity ( commits . len ( ) ) ;
for entry in commits {
let subject = entry . subject . clone ( ) ;
let sha = entry . sha . clone ( ) ;
let short = sha . chars ( ) . take ( 7 ) . collect ::< String > ( ) ;
let search_val = format! ( " {subject} {sha} " ) ;
items . push ( SelectionItem {
name : subject . clone ( ) ,
actions : vec ! [ Box ::new ( move | tx3 : & AppEventSender | {
let hint = format! ( " commit {short} " ) ;
let prompt = format! (
" Review the code changes introduced by commit {sha} ( \" {subject} \" ). Provide prioritized, actionable findings. "
) ;
tx3 . send ( AppEvent ::CodexOp ( Op ::Review {
review_request : ReviewRequest {
prompt ,
user_facing_hint : hint ,
} ,
} ) ) ;
} ) ] ,
dismiss_on_select : true ,
search_value : Some ( search_val ) ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-21 20:18:35 -07:00
} ) ;
}
self . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-01 14:29:05 -07:00
title : Some ( " Select a commit to review " . to_string ( ) ) ,
2025-10-02 11:34:47 -07:00
footer_hint : Some ( standard_popup_hint_line ( ) ) ,
2025-09-21 20:18:35 -07:00
items ,
is_searchable : true ,
search_placeholder : Some ( " Type to search commits " . to_string ( ) ) ,
.. Default ::default ( )
} ) ;
}
pub ( crate ) fn show_review_custom_prompt ( & mut self ) {
let tx = self . app_event_tx . clone ( ) ;
let view = CustomPromptView ::new (
" Custom review instructions " . to_string ( ) ,
" Type instructions and press Enter " . to_string ( ) ,
None ,
Box ::new ( move | prompt : String | {
let trimmed = prompt . trim ( ) . to_string ( ) ;
if trimmed . is_empty ( ) {
return ;
}
tx . send ( AppEvent ::CodexOp ( Op ::Review {
review_request : ReviewRequest {
prompt : trimmed . clone ( ) ,
user_facing_hint : trimmed ,
} ,
} ) ) ;
} ) ,
) ;
self . bottom_pane . show_view ( Box ::new ( view ) ) ;
}
2025-08-06 09:10:23 -07:00
/// Programmatically submit a user text message as if typed in the
/// composer. The text will be added to conversation history and sent to
/// the agent.
pub ( crate ) fn submit_text_message ( & mut self , text : String ) {
if text . is_empty ( ) {
return ;
}
self . submit_user_message ( text . into ( ) ) ;
}
2025-09-06 08:19:23 -07:00
pub ( crate ) fn token_usage ( & self ) -> TokenUsage {
self . token_info
. as_ref ( )
. map ( | ti | ti . total_token_usage . clone ( ) )
. unwrap_or_default ( )
2025-07-25 01:56:40 -07:00
}
2025-07-31 21:34:32 -07:00
2025-09-07 20:22:25 -07:00
pub ( crate ) fn conversation_id ( & self ) -> Option < ConversationId > {
self . conversation_id
2025-08-23 23:23:15 -07:00
}
/// Return a reference to the widget's current config (includes any
/// runtime overrides applied via TUI, e.g., model or approval policy).
pub ( crate ) fn config_ref ( & self ) -> & Config {
& self . config
}
2025-07-31 21:34:32 -07:00
pub ( crate ) fn clear_token_usage ( & mut self ) {
2025-09-06 08:19:23 -07:00
self . token_info = None ;
2025-07-31 21:34:32 -07:00
}
2025-08-03 11:31:35 -07:00
pub fn cursor_pos ( & self , area : Rect ) -> Option < ( u16 , u16 ) > {
2025-09-14 20:53:50 -04:00
let [ _ , _ , bottom_pane_area ] = self . layout_areas ( area ) ;
2025-08-06 12:03:45 -07:00
self . bottom_pane . cursor_pos ( bottom_pane_area )
2025-08-03 11:31:35 -07:00
}
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-20 13:47:24 -07:00
impl WidgetRef for & ChatWidget {
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
fn render_ref ( & self , area : Rect , buf : & mut Buffer ) {
2025-09-14 20:53:50 -04:00
let [ _ , active_cell_area , bottom_pane_area ] = self . layout_areas ( area ) ;
2025-08-06 12:03:45 -07:00
( & self . bottom_pane ) . render ( bottom_pane_area , buf ) ;
2025-09-04 08:51:02 -07:00
if ! active_cell_area . is_empty ( )
2025-09-24 13:36:01 -07:00
& & let Some ( cell ) = & self . active_cell
2025-09-04 08:51:02 -07:00
{
2025-09-24 13:36:01 -07:00
let mut area = active_cell_area ;
area . y = area . y . saturating_add ( 1 ) ;
area . height = area . height . saturating_sub ( 1 ) ;
if let Some ( exec ) = cell . as_any ( ) . downcast_ref ::< ExecCell > ( ) {
exec . render_ref ( area , buf ) ;
} else if let Some ( tool ) = cell . as_any ( ) . downcast_ref ::< McpToolCallCell > ( ) {
tool . render_ref ( area , buf ) ;
}
2025-08-06 12:03:45 -07:00
}
2025-09-30 16:13:55 -07:00
self . last_rendered_width . set ( Some ( area . width as usize ) ) ;
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
}
}
feat: show number of tokens remaining in UI (#1388)
When using the OpenAI Responses API, we now record the `usage` field for
a `"response.completed"` event, which includes metrics about the number
of tokens consumed. We also introduce `openai_model_info.rs`, which
includes current data about the most common OpenAI models available via
the API (specifically `context_window` and `max_output_tokens`). If
Codex does not recognize the model, you can set `model_context_window`
and `model_max_output_tokens` explicitly in `config.toml`.
When then introduce a new event type to `protocol.rs`, `TokenCount`,
which includes the `TokenUsage` for the most recent turn.
Finally, we update the TUI to record the running sum of tokens used so
the percentage of available context window remaining can be reported via
the placeholder text for the composer:

We could certainly get much fancier with this (such as reporting the
estimated cost of the conversation), but for now, we are just trying to
achieve feature parity with the TypeScript CLI.
Though arguably this improves upon the TypeScript CLI, as the TypeScript
CLI uses heuristics to estimate the number of tokens used rather than
using the `usage` information directly:
https://github.com/openai/codex/blob/296996d74e345b1b05d8c3451a06ace21c5ada96/codex-cli/src/utils/approximate-tokens-used.ts#L3-L16
Fixes https://github.com/openai/codex/issues/1242
2025-06-25 23:31:11 -07:00
2025-09-15 10:22:02 -07:00
enum Notification {
2025-09-17 11:23:46 -07:00
AgentTurnComplete { response : String } ,
2025-09-15 10:22:02 -07:00
ExecApprovalRequested { command : String } ,
EditApprovalRequested { cwd : PathBuf , changes : Vec < PathBuf > } ,
}
impl Notification {
fn display ( & self ) -> String {
match self {
2025-09-17 11:23:46 -07:00
Notification ::AgentTurnComplete { response } = > {
Notification ::agent_turn_preview ( response )
. unwrap_or_else ( | | " Agent turn complete " . to_string ( ) )
}
2025-09-15 10:22:02 -07:00
Notification ::ExecApprovalRequested { command } = > {
format! ( " Approval requested: {} " , truncate_text ( command , 30 ) )
}
Notification ::EditApprovalRequested { cwd , changes } = > {
format! (
" Codex wants to edit {} " ,
if changes . len ( ) = = 1 {
#[ allow(clippy::unwrap_used) ]
display_path_for ( changes . first ( ) . unwrap ( ) , cwd )
} else {
format! ( " {} files " , changes . len ( ) )
}
)
}
}
}
fn type_name ( & self ) -> & str {
match self {
2025-09-17 11:23:46 -07:00
Notification ::AgentTurnComplete { .. } = > " agent-turn-complete " ,
2025-09-15 10:22:02 -07:00
Notification ::ExecApprovalRequested { .. }
| Notification ::EditApprovalRequested { .. } = > " approval-requested " ,
}
}
fn allowed_for ( & self , settings : & Notifications ) -> bool {
match settings {
Notifications ::Enabled ( enabled ) = > * enabled ,
Notifications ::Custom ( allowed ) = > allowed . iter ( ) . any ( | a | a = = self . type_name ( ) ) ,
}
}
2025-09-17 11:23:46 -07:00
fn agent_turn_preview ( response : & str ) -> Option < String > {
let mut normalized = String ::new ( ) ;
for part in response . split_whitespace ( ) {
if ! normalized . is_empty ( ) {
normalized . push ( ' ' ) ;
}
normalized . push_str ( part ) ;
}
let trimmed = normalized . trim ( ) ;
if trimmed . is_empty ( ) {
None
} else {
Some ( truncate_text ( trimmed , AGENT_NOTIFICATION_PREVIEW_GRAPHEMES ) )
}
}
2025-09-15 10:22:02 -07:00
}
2025-09-17 11:23:46 -07:00
const AGENT_NOTIFICATION_PREVIEW_GRAPHEMES : usize = 200 ;
2025-08-15 22:37:10 -04:00
const EXAMPLE_PROMPTS : [ & str ; 6 ] = [
" Explain this codebase " ,
" Summarize recent commits " ,
" Implement {feature} " ,
" Find and fix a bug in @filename " ,
" Write tests for @filename " ,
" Improve documentation in @filename " ,
] ;
2025-08-20 16:58:56 -07:00
// Extract the first bold (Markdown) element in the form **...** from `s`.
// Returns the inner text if found; otherwise `None`.
fn extract_first_bold ( s : & str ) -> Option < String > {
let bytes = s . as_bytes ( ) ;
let mut i = 0 usize ;
while i + 1 < bytes . len ( ) {
if bytes [ i ] = = b '*' & & bytes [ i + 1 ] = = b '*' {
let start = i + 2 ;
let mut j = start ;
while j + 1 < bytes . len ( ) {
if bytes [ j ] = = b '*' & & bytes [ j + 1 ] = = b '*' {
// Found closing **
let inner = & s [ start .. j ] ;
let trimmed = inner . trim ( ) ;
if ! trimmed . is_empty ( ) {
return Some ( trimmed . to_string ( ) ) ;
} else {
return None ;
}
}
j + = 1 ;
}
// No closing; stop searching (wait for more deltas)
return None ;
}
i + = 1 ;
}
None
}
2025-09-21 20:18:35 -07:00
#[ cfg(test) ]
pub ( crate ) fn show_review_commit_picker_with_entries (
chat : & mut ChatWidget ,
entries : Vec < codex_core ::git_info ::CommitLogEntry > ,
) {
let mut items : Vec < SelectionItem > = Vec ::with_capacity ( entries . len ( ) ) ;
for entry in entries {
let subject = entry . subject . clone ( ) ;
let sha = entry . sha . clone ( ) ;
let short = sha . chars ( ) . take ( 7 ) . collect ::< String > ( ) ;
let search_val = format! ( " {subject} {sha} " ) ;
items . push ( SelectionItem {
name : subject . clone ( ) ,
actions : vec ! [ Box ::new ( move | tx3 : & AppEventSender | {
let hint = format! ( " commit {short} " ) ;
let prompt = format! (
" Review the code changes introduced by commit {sha} ( \" {subject} \" ). Provide prioritized, actionable findings. "
) ;
tx3 . send ( AppEvent ::CodexOp ( Op ::Review {
review_request : ReviewRequest {
prompt ,
user_facing_hint : hint ,
} ,
} ) ) ;
} ) ] ,
dismiss_on_select : true ,
search_value : Some ( search_val ) ,
2025-10-02 14:41:29 -07:00
.. Default ::default ( )
2025-09-21 20:18:35 -07:00
} ) ;
}
chat . bottom_pane . show_selection_view ( SelectionViewParams {
2025-10-01 14:29:05 -07:00
title : Some ( " Select a commit to review " . to_string ( ) ) ,
2025-10-02 11:34:47 -07:00
footer_hint : Some ( standard_popup_hint_line ( ) ) ,
2025-09-21 20:18:35 -07:00
items ,
is_searchable : true ,
search_placeholder : Some ( " Type to search commits " . to_string ( ) ) ,
.. Default ::default ( )
} ) ;
}
2025-08-11 12:40:12 -07:00
#[ cfg(test) ]
2025-09-12 10:38:12 -07:00
pub ( crate ) mod tests ;