2025-08-23 23:23:15 -07:00
|
|
|
use crate::app_backtrack::BacktrackState;
|
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;
|
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::chatwidget::ChatWidget;
|
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
|
|
|
use crate::file_search::FileSearchManager;
|
2025-08-26 17:03:11 -07:00
|
|
|
use crate::pager_overlay::Overlay;
|
2025-09-03 23:20:40 -07:00
|
|
|
use crate::resume_picker::ResumeSelection;
|
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::tui;
|
2025-08-20 13:47:24 -07:00
|
|
|
use crate::tui::TuiEvent;
|
2025-08-22 08:24:13 -07:00
|
|
|
use codex_ansi_escape::ansi_escape_line;
|
2025-09-02 18:36:19 -07: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-04-27 21:47:50 -07:00
|
|
|
use codex_core::config::Config;
|
2025-09-11 15:04:29 -07:00
|
|
|
use codex_core::config::persist_model_selection;
|
|
|
|
|
use codex_core::model_family::find_family_for_model;
|
2025-08-20 13:47:24 -07:00
|
|
|
use codex_core::protocol::TokenUsage;
|
2025-09-12 10:38:12 -07:00
|
|
|
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
|
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 color_eyre::eyre::Result;
|
2025-09-03 23:20:40 -07:00
|
|
|
use color_eyre::eyre::WrapErr;
|
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::KeyCode;
|
|
|
|
|
use crossterm::event::KeyEvent;
|
2025-07-31 17:00:48 -07:00
|
|
|
use crossterm::event::KeyEventKind;
|
2025-07-31 17:30:44 -07:00
|
|
|
use crossterm::terminal::supports_keyboard_enhancement;
|
2025-08-22 08:24:13 -07:00
|
|
|
use ratatui::style::Stylize;
|
2025-08-20 16:57:35 -07:00
|
|
|
use ratatui::text::Line;
|
2025-05-24 08:33:49 -07:00
|
|
|
use std::path::PathBuf;
|
2025-07-17 12:54:55 -07:00
|
|
|
use std::sync::Arc;
|
2025-07-18 11:13:34 -07:00
|
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
|
use std::sync::atomic::Ordering;
|
2025-07-17 12:54:55 -07:00
|
|
|
use std::thread;
|
|
|
|
|
use std::time::Duration;
|
2025-08-20 10:11:09 -07:00
|
|
|
use tokio::select;
|
|
|
|
|
use tokio::sync::mpsc::unbounded_channel;
|
2025-08-23 23:23:15 -07:00
|
|
|
// use uuid::Uuid;
|
2025-07-17 12:54:55 -07:00
|
|
|
|
2025-08-20 13:47:24 -07:00
|
|
|
pub(crate) struct App {
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) server: Arc<ConversationManager>,
|
|
|
|
|
pub(crate) app_event_tx: AppEventSender,
|
|
|
|
|
pub(crate) chat_widget: ChatWidget,
|
2025-05-24 08:33:49 -07:00
|
|
|
|
2025-06-06 16:29:37 -07:00
|
|
|
/// Config is stored here so we can recreate ChatWidgets as needed.
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) config: Config,
|
2025-09-11 15:04:29 -07:00
|
|
|
pub(crate) active_profile: Option<String>,
|
|
|
|
|
model_saved_to_profile: bool,
|
|
|
|
|
model_saved_to_global: bool,
|
2025-06-06 16:29:37 -07:00
|
|
|
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) file_search: FileSearchManager,
|
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
|
|
|
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) transcript_lines: Vec<Line<'static>>,
|
2025-08-20 16:57:35 -07:00
|
|
|
|
2025-08-26 17:03:11 -07:00
|
|
|
// Pager overlay state (Transcript or Static like Diff)
|
|
|
|
|
pub(crate) overlay: Option<Overlay>,
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) deferred_history_lines: Vec<Line<'static>>,
|
2025-09-02 10:29:58 -07:00
|
|
|
has_emitted_history_lines: bool,
|
2025-08-21 11:17:29 -07:00
|
|
|
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) enhanced_keys_supported: bool,
|
2025-08-12 17:37:28 -07:00
|
|
|
|
|
|
|
|
/// Controls the animation thread that sends CommitTick events.
|
2025-08-23 23:23:15 -07:00
|
|
|
pub(crate) commit_anim_running: Arc<AtomicBool>,
|
|
|
|
|
|
|
|
|
|
// Esc-backtracking state grouped
|
|
|
|
|
pub(crate) backtrack: crate::app_backtrack::BacktrackState,
|
2025-05-24 08:33:49 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 13:47:24 -07:00
|
|
|
impl App {
|
|
|
|
|
pub async fn run(
|
|
|
|
|
tui: &mut tui::Tui,
|
2025-08-22 13:10:11 -07:00
|
|
|
auth_manager: Arc<AuthManager>,
|
2025-04-27 21:47:50 -07:00
|
|
|
config: Config,
|
2025-09-11 15:04:29 -07:00
|
|
|
active_profile: Option<String>,
|
feat: initial import of Rust implementation of Codex CLI in codex-rs/ (#629)
As stated in `codex-rs/README.md`:
Today, Codex CLI is written in TypeScript and requires Node.js 22+ to
run it. For a number of users, this runtime requirement inhibits
adoption: they would be better served by a standalone executable. As
maintainers, we want Codex to run efficiently in a wide range of
environments with minimal overhead. We also want to take advantage of
operating system-specific APIs to provide better sandboxing, where
possible.
To that end, we are moving forward with a Rust implementation of Codex
CLI contained in this folder, which has the following benefits:
- The CLI compiles to small, standalone, platform-specific binaries.
- Can make direct, native calls to
[seccomp](https://man7.org/linux/man-pages/man2/seccomp.2.html) and
[landlock](https://man7.org/linux/man-pages/man7/landlock.7.html) in
order to support sandboxing on Linux.
- No runtime garbage collection, resulting in lower memory consumption
and better, more predictable performance.
Currently, the Rust implementation is materially behind the TypeScript
implementation in functionality, so continue to use the TypeScript
implmentation for the time being. We will publish native executables via
GitHub Releases as soon as we feel the Rust version is usable.
2025-04-24 13:31:40 -07:00
|
|
|
initial_prompt: Option<String>,
|
2025-08-20 13:47:24 -07:00
|
|
|
initial_images: Vec<PathBuf>,
|
2025-09-03 23:20:40 -07:00
|
|
|
resume_selection: ResumeSelection,
|
2025-08-20 13:47:24 -07:00
|
|
|
) -> Result<TokenUsage> {
|
|
|
|
|
use tokio_stream::StreamExt;
|
|
|
|
|
let (app_event_tx, mut app_event_rx) = unbounded_channel();
|
2025-05-15 14:50:30 -07:00
|
|
|
let app_event_tx = AppEventSender::new(app_event_tx);
|
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 13:10:11 -07:00
|
|
|
let conversation_manager = Arc::new(ConversationManager::new(auth_manager.clone()));
|
2025-08-20 13:47:24 -07:00
|
|
|
|
2025-07-31 17:30:44 -07:00
|
|
|
let enhanced_keys_supported = supports_keyboard_enhancement().unwrap_or(false);
|
|
|
|
|
|
2025-09-03 23:20:40 -07:00
|
|
|
let chat_widget = match resume_selection {
|
|
|
|
|
ResumeSelection::StartFresh | ResumeSelection::Exit => {
|
|
|
|
|
let init = crate::chatwidget::ChatWidgetInit {
|
|
|
|
|
config: config.clone(),
|
|
|
|
|
frame_requester: tui.frame_requester(),
|
|
|
|
|
app_event_tx: app_event_tx.clone(),
|
|
|
|
|
initial_prompt: initial_prompt.clone(),
|
|
|
|
|
initial_images: initial_images.clone(),
|
|
|
|
|
enhanced_keys_supported,
|
|
|
|
|
};
|
|
|
|
|
ChatWidget::new(init, conversation_manager.clone())
|
|
|
|
|
}
|
|
|
|
|
ResumeSelection::Resume(path) => {
|
|
|
|
|
let resumed = conversation_manager
|
|
|
|
|
.resume_conversation_from_rollout(
|
|
|
|
|
config.clone(),
|
|
|
|
|
path.clone(),
|
|
|
|
|
auth_manager.clone(),
|
|
|
|
|
)
|
|
|
|
|
.await
|
|
|
|
|
.wrap_err_with(|| {
|
|
|
|
|
format!("Failed to resume session from {}", path.display())
|
|
|
|
|
})?;
|
|
|
|
|
let init = crate::chatwidget::ChatWidgetInit {
|
|
|
|
|
config: config.clone(),
|
|
|
|
|
frame_requester: tui.frame_requester(),
|
|
|
|
|
app_event_tx: app_event_tx.clone(),
|
|
|
|
|
initial_prompt: initial_prompt.clone(),
|
|
|
|
|
initial_images: initial_images.clone(),
|
|
|
|
|
enhanced_keys_supported,
|
|
|
|
|
};
|
|
|
|
|
ChatWidget::new_from_existing(
|
|
|
|
|
init,
|
|
|
|
|
resumed.conversation,
|
|
|
|
|
resumed.session_configured,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
};
|
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: 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
|
|
|
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
2025-08-14 16:59:47 -04:00
|
|
|
|
2025-08-20 13:47:24 -07:00
|
|
|
let mut app = Self {
|
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
|
|
|
server: 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
|
|
|
app_event_tx,
|
2025-08-20 13:47:24 -07:00
|
|
|
chat_widget,
|
2025-06-06 16:29:37 -07:00
|
|
|
config,
|
2025-09-11 15:04:29 -07:00
|
|
|
active_profile,
|
|
|
|
|
model_saved_to_profile: false,
|
|
|
|
|
model_saved_to_global: false,
|
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
|
|
|
file_search,
|
2025-07-31 17:30:44 -07:00
|
|
|
enhanced_keys_supported,
|
2025-08-20 16:57:35 -07:00
|
|
|
transcript_lines: Vec::new(),
|
2025-08-26 17:03:11 -07:00
|
|
|
overlay: None,
|
2025-08-21 11:17:29 -07:00
|
|
|
deferred_history_lines: Vec::new(),
|
2025-09-02 10:29:58 -07:00
|
|
|
has_emitted_history_lines: false,
|
2025-08-12 17:37:28 -07:00
|
|
|
commit_anim_running: Arc::new(AtomicBool::new(false)),
|
2025-08-23 23:23:15 -07:00
|
|
|
backtrack: BacktrackState::default(),
|
2025-08-20 13:47:24 -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
|
|
|
let tui_events = tui.event_stream();
|
|
|
|
|
tokio::pin!(tui_events);
|
2025-08-20 10:11:09 -07:00
|
|
|
|
2025-08-20 13:47:24 -07:00
|
|
|
tui.frame_requester().schedule_frame();
|
2025-08-20 10:11:09 -07:00
|
|
|
|
2025-08-20 13:47:24 -07:00
|
|
|
while select! {
|
|
|
|
|
Some(event) = app_event_rx.recv() => {
|
2025-08-23 23:23:15 -07:00
|
|
|
app.handle_event(tui, event).await?
|
2025-08-20 13:47:24 -07:00
|
|
|
}
|
|
|
|
|
Some(event) = tui_events.next() => {
|
|
|
|
|
app.handle_tui_event(tui, event).await?
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
2025-08-20 13:47:24 -07:00
|
|
|
} {}
|
|
|
|
|
tui.terminal.clear()?;
|
|
|
|
|
Ok(app.token_usage())
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
|
2025-08-20 13:47:24 -07:00
|
|
|
pub(crate) async fn handle_tui_event(
|
|
|
|
|
&mut self,
|
|
|
|
|
tui: &mut tui::Tui,
|
|
|
|
|
event: TuiEvent,
|
|
|
|
|
) -> Result<bool> {
|
2025-08-26 17:03:11 -07:00
|
|
|
if self.overlay.is_some() {
|
2025-08-23 23:23:15 -07:00
|
|
|
let _ = self.handle_backtrack_overlay_event(tui, event).await?;
|
2025-08-21 11:17:29 -07:00
|
|
|
} else {
|
|
|
|
|
match event {
|
|
|
|
|
TuiEvent::Key(key_event) => {
|
|
|
|
|
self.handle_key_event(tui, key_event).await;
|
|
|
|
|
}
|
|
|
|
|
TuiEvent::Paste(pasted) => {
|
|
|
|
|
// Many terminals convert newlines to \r when pasting (e.g., iTerm2),
|
|
|
|
|
// but tui-textarea expects \n. Normalize CR to LF.
|
|
|
|
|
// [tui-textarea]: https://github.com/rhysd/tui-textarea/blob/4d18622eeac13b309e0ff6a55a46ac6706da68cf/src/textarea.rs#L782-L783
|
|
|
|
|
// [iTerm2]: https://github.com/gnachman/iTerm2/blob/5d0c0d9f68523cbd0494dad5422998964a2ecd8d/sources/iTermPasteHelper.m#L206-L216
|
|
|
|
|
let pasted = pasted.replace("\r", "\n");
|
|
|
|
|
self.chat_widget.handle_paste(pasted);
|
|
|
|
|
}
|
|
|
|
|
TuiEvent::Draw => {
|
2025-08-28 12:54:12 -07:00
|
|
|
if self
|
|
|
|
|
.chat_widget
|
|
|
|
|
.handle_paste_burst_tick(tui.frame_requester())
|
|
|
|
|
{
|
|
|
|
|
return Ok(true);
|
|
|
|
|
}
|
2025-08-21 11:17:29 -07:00
|
|
|
tui.draw(
|
|
|
|
|
self.chat_widget.desired_height(tui.terminal.size()?.width),
|
|
|
|
|
|frame| {
|
|
|
|
|
frame.render_widget_ref(&self.chat_widget, frame.area());
|
|
|
|
|
if let Some((x, y)) = self.chat_widget.cursor_pos(frame.area()) {
|
|
|
|
|
frame.set_cursor_position((x, y));
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
)?;
|
|
|
|
|
}
|
2025-08-20 13:47:24 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Ok(true)
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-23 23:23:15 -07:00
|
|
|
async fn handle_event(&mut self, tui: &mut tui::Tui, event: AppEvent) -> Result<bool> {
|
2025-08-20 13:47:24 -07:00
|
|
|
match event {
|
2025-08-21 10:36:58 -07:00
|
|
|
AppEvent::NewSession => {
|
2025-09-03 23:20:40 -07:00
|
|
|
let init = crate::chatwidget::ChatWidgetInit {
|
|
|
|
|
config: self.config.clone(),
|
|
|
|
|
frame_requester: tui.frame_requester(),
|
|
|
|
|
app_event_tx: self.app_event_tx.clone(),
|
|
|
|
|
initial_prompt: None,
|
|
|
|
|
initial_images: Vec::new(),
|
|
|
|
|
enhanced_keys_supported: self.enhanced_keys_supported,
|
|
|
|
|
};
|
|
|
|
|
self.chat_widget = ChatWidget::new(init, self.server.clone());
|
2025-08-21 10:36:58 -07:00
|
|
|
tui.frame_requester().schedule_frame();
|
|
|
|
|
}
|
2025-08-20 17:09:46 -07:00
|
|
|
AppEvent::InsertHistoryCell(cell) => {
|
2025-09-02 10:29:58 -07:00
|
|
|
let mut cell_transcript = cell.transcript_lines();
|
|
|
|
|
if !cell.is_stream_continuation() && !self.transcript_lines.is_empty() {
|
|
|
|
|
cell_transcript.insert(0, Line::from(""));
|
|
|
|
|
}
|
2025-08-26 17:03:11 -07:00
|
|
|
if let Some(Overlay::Transcript(t)) = &mut self.overlay {
|
|
|
|
|
t.insert_lines(cell_transcript.clone());
|
2025-08-21 11:17:29 -07:00
|
|
|
tui.frame_requester().schedule_frame();
|
|
|
|
|
}
|
2025-08-26 17:03:11 -07:00
|
|
|
self.transcript_lines.extend(cell_transcript.clone());
|
2025-09-02 10:29:58 -07:00
|
|
|
let mut display = cell.display_lines(tui.terminal.last_known_screen_size.width);
|
2025-08-20 17:09:46 -07:00
|
|
|
if !display.is_empty() {
|
2025-09-02 13:45:51 -07:00
|
|
|
// Only insert a separating blank line for new cells that are not
|
|
|
|
|
// part of an ongoing stream. Streaming continuations should not
|
|
|
|
|
// accrue extra blank lines between chunks.
|
|
|
|
|
if !cell.is_stream_continuation() {
|
|
|
|
|
if self.has_emitted_history_lines {
|
|
|
|
|
display.insert(0, Line::from(""));
|
|
|
|
|
} else {
|
|
|
|
|
self.has_emitted_history_lines = true;
|
|
|
|
|
}
|
2025-09-02 10:29:58 -07:00
|
|
|
}
|
2025-08-26 17:03:11 -07:00
|
|
|
if self.overlay.is_some() {
|
2025-08-21 11:17:29 -07:00
|
|
|
self.deferred_history_lines.extend(display);
|
|
|
|
|
} else {
|
|
|
|
|
tui.insert_history_lines(display);
|
|
|
|
|
}
|
2025-08-20 17:09:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-08-20 10:11:09 -07:00
|
|
|
AppEvent::StartCommitAnimation => {
|
|
|
|
|
if self
|
|
|
|
|
.commit_anim_running
|
|
|
|
|
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
|
|
|
|
|
.is_ok()
|
|
|
|
|
{
|
|
|
|
|
let tx = self.app_event_tx.clone();
|
|
|
|
|
let running = self.commit_anim_running.clone();
|
|
|
|
|
thread::spawn(move || {
|
|
|
|
|
while running.load(Ordering::Relaxed) {
|
|
|
|
|
thread::sleep(Duration::from_millis(50));
|
|
|
|
|
tx.send(AppEvent::CommitTick);
|
2025-07-31 21:34:32 -07:00
|
|
|
}
|
2025-08-20 10:11:09 -07:00
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AppEvent::StopCommitAnimation => {
|
|
|
|
|
self.commit_anim_running.store(false, Ordering::Release);
|
|
|
|
|
}
|
|
|
|
|
AppEvent::CommitTick => {
|
2025-08-20 13:47:24 -07:00
|
|
|
self.chat_widget.on_commit_tick();
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
AppEvent::CodexEvent(event) => {
|
2025-08-20 13:47:24 -07:00
|
|
|
self.chat_widget.handle_codex_event(event);
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
2025-08-23 23:23:15 -07:00
|
|
|
AppEvent::ConversationHistory(ev) => {
|
|
|
|
|
self.on_conversation_history_for_backtrack(tui, ev).await?;
|
|
|
|
|
}
|
2025-08-20 10:11:09 -07:00
|
|
|
AppEvent::ExitRequest => {
|
|
|
|
|
return Ok(false);
|
|
|
|
|
}
|
2025-08-20 13:47:24 -07:00
|
|
|
AppEvent::CodexOp(op) => self.chat_widget.submit_op(op),
|
2025-08-20 10:11:09 -07:00
|
|
|
AppEvent::DiffResult(text) => {
|
2025-08-22 08:24:13 -07:00
|
|
|
// Clear the in-progress state in the bottom pane
|
|
|
|
|
self.chat_widget.on_diff_complete();
|
2025-08-22 09:41:15 -07:00
|
|
|
// Enter alternate screen using TUI helper and build pager lines
|
|
|
|
|
let _ = tui.enter_alt_screen();
|
2025-08-22 08:24:13 -07:00
|
|
|
let pager_lines: Vec<ratatui::text::Line<'static>> = if text.trim().is_empty() {
|
|
|
|
|
vec!["No changes detected.".italic().into()]
|
|
|
|
|
} else {
|
|
|
|
|
text.lines().map(ansi_escape_line).collect()
|
|
|
|
|
};
|
2025-08-26 17:03:11 -07:00
|
|
|
self.overlay = Some(Overlay::new_static_with_title(
|
2025-08-22 08:24:13 -07:00
|
|
|
pager_lines,
|
|
|
|
|
"D I F F".to_string(),
|
|
|
|
|
));
|
|
|
|
|
tui.frame_requester().schedule_frame();
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
AppEvent::StartFileSearch(query) => {
|
|
|
|
|
if !query.is_empty() {
|
|
|
|
|
self.file_search.on_user_query(query);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
AppEvent::FileSearchResult { query, matches } => {
|
2025-08-20 13:47:24 -07:00
|
|
|
self.chat_widget.apply_file_search_result(query, matches);
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
AppEvent::UpdateReasoningEffort(effort) => {
|
2025-09-12 10:38:12 -07:00
|
|
|
self.on_update_reasoning_effort(effort);
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
AppEvent::UpdateModel(model) => {
|
2025-09-11 15:04:29 -07:00
|
|
|
self.chat_widget.set_model(model.clone());
|
|
|
|
|
self.config.model = model.clone();
|
|
|
|
|
if let Some(family) = find_family_for_model(&model) {
|
|
|
|
|
self.config.model_family = family;
|
|
|
|
|
}
|
|
|
|
|
self.model_saved_to_profile = false;
|
|
|
|
|
self.model_saved_to_global = false;
|
|
|
|
|
self.show_model_save_hint();
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
AppEvent::UpdateAskForApprovalPolicy(policy) => {
|
2025-08-20 13:47:24 -07:00
|
|
|
self.chat_widget.set_approval_policy(policy);
|
2025-08-20 10:11:09 -07:00
|
|
|
}
|
|
|
|
|
AppEvent::UpdateSandboxPolicy(policy) => {
|
2025-08-20 13:47:24 -07:00
|
|
|
self.chat_widget.set_sandbox_policy(policy);
|
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 10:11:09 -07:00
|
|
|
Ok(true)
|
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-25 01:56:40 -07:00
|
|
|
pub(crate) fn token_usage(&self) -> codex_core::protocol::TokenUsage {
|
2025-09-11 11:59:37 -07:00
|
|
|
self.chat_widget.token_usage()
|
2025-07-25 01:56:40 -07:00
|
|
|
}
|
|
|
|
|
|
2025-09-11 15:04:29 -07:00
|
|
|
fn show_model_save_hint(&mut self) {
|
|
|
|
|
let model = self.config.model.clone();
|
|
|
|
|
if self.active_profile.is_some() {
|
|
|
|
|
self.chat_widget.add_info_message(format!(
|
|
|
|
|
"Model switched to {model}. Press Ctrl+S to save it for this profile, then press Ctrl+S again to set it as your global default."
|
|
|
|
|
));
|
|
|
|
|
} else {
|
|
|
|
|
self.chat_widget.add_info_message(format!(
|
|
|
|
|
"Model switched to {model}. Press Ctrl+S to save it as your global default."
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-12 12:06:33 -07:00
|
|
|
fn on_update_reasoning_effort(&mut self, effort: Option<ReasoningEffortConfig>) {
|
2025-09-12 10:38:12 -07:00
|
|
|
let changed = self.config.model_reasoning_effort != effort;
|
|
|
|
|
self.chat_widget.set_reasoning_effort(effort);
|
|
|
|
|
self.config.model_reasoning_effort = effort;
|
|
|
|
|
if changed {
|
|
|
|
|
let show_hint = self.model_saved_to_profile || self.model_saved_to_global;
|
|
|
|
|
self.model_saved_to_profile = false;
|
|
|
|
|
self.model_saved_to_global = false;
|
|
|
|
|
if show_hint {
|
|
|
|
|
self.show_model_save_hint();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-11 15:04:29 -07:00
|
|
|
async fn persist_model_shortcut(&mut self) {
|
|
|
|
|
enum SaveScope<'a> {
|
|
|
|
|
Profile(&'a str),
|
|
|
|
|
Global,
|
|
|
|
|
AlreadySaved,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let scope = if let Some(profile) = self
|
|
|
|
|
.active_profile
|
|
|
|
|
.as_deref()
|
|
|
|
|
.filter(|_| !self.model_saved_to_profile)
|
|
|
|
|
{
|
|
|
|
|
SaveScope::Profile(profile)
|
|
|
|
|
} else if !self.model_saved_to_global {
|
|
|
|
|
SaveScope::Global
|
|
|
|
|
} else {
|
|
|
|
|
SaveScope::AlreadySaved
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
let model = self.config.model.clone();
|
|
|
|
|
let effort = self.config.model_reasoning_effort;
|
2025-09-12 12:06:33 -07:00
|
|
|
let effort_label = effort
|
|
|
|
|
.map(|effort| effort.to_string())
|
|
|
|
|
.unwrap_or_else(|| "none".to_string());
|
2025-09-11 15:04:29 -07:00
|
|
|
let codex_home = self.config.codex_home.clone();
|
|
|
|
|
|
|
|
|
|
match scope {
|
|
|
|
|
SaveScope::Profile(profile) => {
|
2025-09-12 12:06:33 -07:00
|
|
|
match persist_model_selection(&codex_home, Some(profile), &model, effort).await {
|
2025-09-11 15:04:29 -07:00
|
|
|
Ok(()) => {
|
|
|
|
|
self.model_saved_to_profile = true;
|
|
|
|
|
self.chat_widget.add_info_message(format!(
|
2025-09-12 12:06:33 -07:00
|
|
|
"Saved model {model} ({effort_label}) for profile `{profile}`. Press Ctrl+S again to make this your global default."
|
2025-09-11 15:04:29 -07:00
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!(
|
|
|
|
|
error = %err,
|
|
|
|
|
"failed to persist model selection via shortcut"
|
|
|
|
|
);
|
|
|
|
|
self.chat_widget.add_error_message(format!(
|
|
|
|
|
"Failed to save model preference for profile `{profile}`: {err}"
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SaveScope::Global => {
|
2025-09-12 12:06:33 -07:00
|
|
|
match persist_model_selection(&codex_home, None, &model, effort).await {
|
2025-09-11 15:04:29 -07:00
|
|
|
Ok(()) => {
|
|
|
|
|
self.model_saved_to_global = true;
|
|
|
|
|
self.chat_widget.add_info_message(format!(
|
2025-09-12 12:06:33 -07:00
|
|
|
"Saved model {model} ({effort_label}) as your global default."
|
2025-09-11 15:04:29 -07:00
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
Err(err) => {
|
|
|
|
|
tracing::error!(
|
|
|
|
|
error = %err,
|
|
|
|
|
"failed to persist global model selection via shortcut"
|
|
|
|
|
);
|
|
|
|
|
self.chat_widget.add_error_message(format!(
|
|
|
|
|
"Failed to save global model preference: {err}"
|
|
|
|
|
));
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
SaveScope::AlreadySaved => {
|
|
|
|
|
self.chat_widget.add_info_message(
|
|
|
|
|
"Model preference already saved globally; no further action needed."
|
|
|
|
|
.to_string(),
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-08-20 16:57:35 -07:00
|
|
|
async fn handle_key_event(&mut self, tui: &mut tui::Tui, key_event: KeyEvent) {
|
2025-08-20 13:47:24 -07:00
|
|
|
match key_event {
|
2025-08-20 16:57:35 -07:00
|
|
|
KeyEvent {
|
|
|
|
|
code: KeyCode::Char('t'),
|
|
|
|
|
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
|
|
|
|
kind: KeyEventKind::Press,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2025-08-21 11:17:29 -07:00
|
|
|
// Enter alternate screen and set viewport to full size.
|
2025-08-22 09:41:15 -07:00
|
|
|
let _ = tui.enter_alt_screen();
|
2025-08-26 17:03:11 -07:00
|
|
|
self.overlay = Some(Overlay::new_transcript(self.transcript_lines.clone()));
|
2025-08-21 11:17:29 -07:00
|
|
|
tui.frame_requester().schedule_frame();
|
2025-08-20 16:57:35 -07:00
|
|
|
}
|
2025-09-11 15:04:29 -07:00
|
|
|
KeyEvent {
|
|
|
|
|
code: KeyCode::Char('s'),
|
|
|
|
|
modifiers: crossterm::event::KeyModifiers::CONTROL,
|
|
|
|
|
kind: KeyEventKind::Press,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
|
|
|
|
self.persist_model_shortcut().await;
|
|
|
|
|
}
|
2025-08-25 15:38:46 -07:00
|
|
|
// Esc primes/advances backtracking only in normal (not working) mode
|
|
|
|
|
// with an empty composer. In any other state, forward Esc so the
|
|
|
|
|
// active UI (e.g. status indicator, modals, popups) handles it.
|
2025-08-23 23:23:15 -07:00
|
|
|
KeyEvent {
|
|
|
|
|
code: KeyCode::Esc,
|
|
|
|
|
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2025-08-25 15:38:46 -07:00
|
|
|
if self.chat_widget.is_normal_backtrack_mode()
|
|
|
|
|
&& self.chat_widget.composer_is_empty()
|
|
|
|
|
{
|
|
|
|
|
self.handle_backtrack_esc_key(tui);
|
|
|
|
|
} else {
|
|
|
|
|
self.chat_widget.handle_key_event(key_event);
|
|
|
|
|
}
|
2025-08-23 23:23:15 -07:00
|
|
|
}
|
|
|
|
|
// Enter confirms backtrack when primed + count > 0. Otherwise pass to widget.
|
|
|
|
|
KeyEvent {
|
|
|
|
|
code: KeyCode::Enter,
|
|
|
|
|
kind: KeyEventKind::Press,
|
|
|
|
|
..
|
|
|
|
|
} if self.backtrack.primed
|
|
|
|
|
&& self.backtrack.count > 0
|
|
|
|
|
&& self.chat_widget.composer_is_empty() =>
|
|
|
|
|
{
|
|
|
|
|
// Delegate to helper for clarity; preserves behavior.
|
|
|
|
|
self.confirm_backtrack_from_main();
|
|
|
|
|
}
|
2025-08-20 13:47:24 -07:00
|
|
|
KeyEvent {
|
|
|
|
|
kind: KeyEventKind::Press | KeyEventKind::Repeat,
|
|
|
|
|
..
|
|
|
|
|
} => {
|
2025-08-23 23:23:15 -07:00
|
|
|
// Any non-Esc key press should cancel a primed backtrack.
|
|
|
|
|
// This avoids stale "Esc-primed" state after the user starts typing
|
|
|
|
|
// (even if they later backspace to empty).
|
|
|
|
|
if key_event.code != KeyCode::Esc && self.backtrack.primed {
|
|
|
|
|
self.reset_backtrack_state();
|
|
|
|
|
}
|
2025-08-20 13:47:24 -07:00
|
|
|
self.chat_widget.handle_key_event(key_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
|
|
|
}
|
2025-08-20 13:47:24 -07:00
|
|
|
_ => {
|
|
|
|
|
// Ignore Release key events.
|
|
|
|
|
}
|
|
|
|
|
};
|
2025-08-18 20:22:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-09-12 10:38:12 -07:00
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
mod tests {
|
|
|
|
|
use super::*;
|
|
|
|
|
use crate::app_backtrack::BacktrackState;
|
|
|
|
|
use crate::chatwidget::tests::make_chatwidget_manual_with_sender;
|
|
|
|
|
use crate::file_search::FileSearchManager;
|
|
|
|
|
use codex_core::CodexAuth;
|
|
|
|
|
use codex_core::ConversationManager;
|
|
|
|
|
use ratatui::text::Line;
|
|
|
|
|
use std::sync::Arc;
|
|
|
|
|
use std::sync::atomic::AtomicBool;
|
|
|
|
|
|
|
|
|
|
fn make_test_app() -> App {
|
|
|
|
|
let (chat_widget, app_event_tx, _rx, _op_rx) = make_chatwidget_manual_with_sender();
|
|
|
|
|
let config = chat_widget.config_ref().clone();
|
|
|
|
|
|
|
|
|
|
let server = Arc::new(ConversationManager::with_auth(CodexAuth::from_api_key(
|
|
|
|
|
"Test API Key",
|
|
|
|
|
)));
|
|
|
|
|
let file_search = FileSearchManager::new(config.cwd.clone(), app_event_tx.clone());
|
|
|
|
|
|
|
|
|
|
App {
|
|
|
|
|
server,
|
|
|
|
|
app_event_tx,
|
|
|
|
|
chat_widget,
|
|
|
|
|
config,
|
|
|
|
|
active_profile: None,
|
|
|
|
|
model_saved_to_profile: false,
|
|
|
|
|
model_saved_to_global: false,
|
|
|
|
|
file_search,
|
|
|
|
|
transcript_lines: Vec::<Line<'static>>::new(),
|
|
|
|
|
overlay: None,
|
|
|
|
|
deferred_history_lines: Vec::new(),
|
|
|
|
|
has_emitted_history_lines: false,
|
|
|
|
|
enhanced_keys_supported: false,
|
|
|
|
|
commit_anim_running: Arc::new(AtomicBool::new(false)),
|
|
|
|
|
backtrack: BacktrackState::default(),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
|
fn update_reasoning_effort_updates_config_and_resets_flags() {
|
|
|
|
|
let mut app = make_test_app();
|
|
|
|
|
app.model_saved_to_profile = true;
|
|
|
|
|
app.model_saved_to_global = true;
|
2025-09-12 12:06:33 -07:00
|
|
|
app.config.model_reasoning_effort = Some(ReasoningEffortConfig::Medium);
|
2025-09-12 10:38:12 -07:00
|
|
|
app.chat_widget
|
2025-09-12 12:06:33 -07:00
|
|
|
.set_reasoning_effort(Some(ReasoningEffortConfig::Medium));
|
2025-09-12 10:38:12 -07:00
|
|
|
|
2025-09-12 12:06:33 -07:00
|
|
|
app.on_update_reasoning_effort(Some(ReasoningEffortConfig::High));
|
2025-09-12 10:38:12 -07:00
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
|
app.config.model_reasoning_effort,
|
2025-09-12 12:06:33 -07:00
|
|
|
Some(ReasoningEffortConfig::High)
|
2025-09-12 10:38:12 -07:00
|
|
|
);
|
|
|
|
|
assert_eq!(
|
|
|
|
|
app.chat_widget.config_ref().model_reasoning_effort,
|
2025-09-12 12:06:33 -07:00
|
|
|
Some(ReasoningEffortConfig::High)
|
2025-09-12 10:38:12 -07:00
|
|
|
);
|
|
|
|
|
assert!(!app.model_saved_to_profile);
|
|
|
|
|
assert!(!app.model_saved_to_global);
|
|
|
|
|
}
|
|
|
|
|
}
|