mirror of
https://github.com/openai/codex.git
synced 2026-03-25 16:13:56 +00:00
Compare commits
18 Commits
stack/plug
...
change-rea
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b120eb0757 | ||
|
|
05f3ea1c28 | ||
|
|
748d9788aa | ||
|
|
1c5a7cd1ca | ||
|
|
d4385420f4 | ||
|
|
af6ddb7f5c | ||
|
|
4305db2d76 | ||
|
|
4b76e81caa | ||
|
|
ae6c8f036f | ||
|
|
ca60bb7ea5 | ||
|
|
a3c3432fa1 | ||
|
|
8e560cbe6c | ||
|
|
b409771a6d | ||
|
|
c90e87def2 | ||
|
|
e6c2294700 | ||
|
|
813387b707 | ||
|
|
d7c999c130 | ||
|
|
b374dab9bf |
@@ -1,10 +1,11 @@
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use strum_macros::Display;
|
||||
use strum_macros::EnumIter;
|
||||
use ts_rs::TS;
|
||||
|
||||
/// See https://platform.openai.com/docs/guides/reasoning?api-mode=responses#get-started-with-reasoning
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, TS)]
|
||||
#[derive(Debug, Serialize, Deserialize, Default, Clone, Copy, PartialEq, Eq, Display, TS, EnumIter)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum ReasoningEffort {
|
||||
@@ -13,8 +14,6 @@ pub enum ReasoningEffort {
|
||||
#[default]
|
||||
Medium,
|
||||
High,
|
||||
/// Option to disable reasoning.
|
||||
None,
|
||||
}
|
||||
|
||||
/// A summary of the reasoning performed by the model. This can be useful for
|
||||
|
||||
@@ -416,6 +416,13 @@ impl App<'_> {
|
||||
widget.add_status_output();
|
||||
}
|
||||
}
|
||||
SlashCommand::ReasoningEffort => {
|
||||
if self.config.model_family.supports_reasoning_summaries {
|
||||
if let AppState::Chat { widget } = &mut self.app_state {
|
||||
widget.open_reasoning_effort_popup();
|
||||
}
|
||||
}
|
||||
}
|
||||
#[cfg(debug_assertions)]
|
||||
SlashCommand::TestApproval => {
|
||||
use codex_core::protocol::EventMsg;
|
||||
|
||||
@@ -99,6 +99,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
assert_eq!(CancellationEvent::Handled, view.on_ctrl_c(&mut pane));
|
||||
assert!(view.queue.is_empty());
|
||||
|
||||
@@ -21,6 +21,8 @@ use ratatui::widgets::StatefulWidgetRef;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
|
||||
use super::chat_composer_history::ChatComposerHistory;
|
||||
use super::choice_popup::ChoicePayload;
|
||||
use super::choice_popup::ChoicePopup;
|
||||
use super::command_popup::CommandPopup;
|
||||
use super::file_search_popup::FileSearchPopup;
|
||||
|
||||
@@ -28,6 +30,8 @@ use crate::app_event::AppEvent;
|
||||
use crate::app_event_sender::AppEventSender;
|
||||
use crate::bottom_pane::textarea::TextArea;
|
||||
use crate::bottom_pane::textarea::TextAreaState;
|
||||
use codex_core::protocol::Op;
|
||||
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
|
||||
use codex_file_search::FileMatch;
|
||||
use std::cell::RefCell;
|
||||
|
||||
@@ -61,6 +65,7 @@ pub(crate) struct ChatComposer {
|
||||
token_usage_info: Option<TokenUsageInfo>,
|
||||
has_focus: bool,
|
||||
placeholder_text: String,
|
||||
show_reasoning_commands: bool,
|
||||
}
|
||||
|
||||
/// Popup state – at most one can be visible at any time.
|
||||
@@ -68,6 +73,7 @@ enum ActivePopup {
|
||||
None,
|
||||
Command(CommandPopup),
|
||||
File(FileSearchPopup),
|
||||
Choice(ChoicePopup),
|
||||
}
|
||||
|
||||
impl ChatComposer {
|
||||
@@ -76,6 +82,7 @@ impl ChatComposer {
|
||||
app_event_tx: AppEventSender,
|
||||
enhanced_keys_supported: bool,
|
||||
placeholder_text: String,
|
||||
show_reasoning_commands: bool,
|
||||
) -> Self {
|
||||
let use_shift_enter_hint = enhanced_keys_supported;
|
||||
|
||||
@@ -93,6 +100,7 @@ impl ChatComposer {
|
||||
token_usage_info: None,
|
||||
has_focus: has_input_focus,
|
||||
placeholder_text,
|
||||
show_reasoning_commands,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,6 +110,7 @@ impl ChatComposer {
|
||||
ActivePopup::None => 1u16,
|
||||
ActivePopup::Command(c) => c.calculate_required_height(),
|
||||
ActivePopup::File(c) => c.calculate_required_height(),
|
||||
ActivePopup::Choice(c) => c.calculate_required_height(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -109,6 +118,7 @@ impl ChatComposer {
|
||||
let popup_height = match &self.active_popup {
|
||||
ActivePopup::Command(popup) => popup.calculate_required_height(),
|
||||
ActivePopup::File(popup) => popup.calculate_required_height(),
|
||||
ActivePopup::Choice(popup) => popup.calculate_required_height(),
|
||||
ActivePopup::None => 1,
|
||||
};
|
||||
let [textarea_rect, _] =
|
||||
@@ -211,15 +221,22 @@ impl ChatComposer {
|
||||
let result = match &mut self.active_popup {
|
||||
ActivePopup::Command(_) => self.handle_key_event_with_slash_popup(key_event),
|
||||
ActivePopup::File(_) => self.handle_key_event_with_file_popup(key_event),
|
||||
ActivePopup::Choice(_) => self.handle_key_event_with_choice_popup(key_event),
|
||||
ActivePopup::None => self.handle_key_event_without_popup(key_event),
|
||||
};
|
||||
|
||||
// Update (or hide/show) popup after processing the key.
|
||||
self.sync_command_popup();
|
||||
if matches!(self.active_popup, ActivePopup::Command(_)) {
|
||||
self.dismissed_file_popup_token = None;
|
||||
} else {
|
||||
self.sync_file_search_popup();
|
||||
match self.active_popup {
|
||||
ActivePopup::Command(_) => {
|
||||
self.dismissed_file_popup_token = None;
|
||||
}
|
||||
ActivePopup::File(_) | ActivePopup::None => {
|
||||
self.sync_file_search_popup();
|
||||
}
|
||||
ActivePopup::Choice(_) => {
|
||||
// Do not clobber a generic choice popup with file-search sync.
|
||||
}
|
||||
}
|
||||
|
||||
result
|
||||
@@ -335,6 +352,60 @@ impl ChatComposer {
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle key events when a generic choice popup is visible.
|
||||
fn handle_key_event_with_choice_popup(&mut self, key_event: KeyEvent) -> (InputResult, bool) {
|
||||
let ActivePopup::Choice(popup) = &mut self.active_popup else {
|
||||
unreachable!();
|
||||
};
|
||||
|
||||
match key_event {
|
||||
KeyEvent {
|
||||
code: KeyCode::Up, ..
|
||||
} => {
|
||||
popup.move_up();
|
||||
(InputResult::None, true)
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::Down,
|
||||
..
|
||||
} => {
|
||||
popup.move_down();
|
||||
(InputResult::None, true)
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::Esc, ..
|
||||
} => {
|
||||
self.active_popup = ActivePopup::None;
|
||||
(InputResult::None, true)
|
||||
}
|
||||
KeyEvent {
|
||||
code: KeyCode::Enter,
|
||||
modifiers: KeyModifiers::NONE,
|
||||
..
|
||||
} => {
|
||||
if let Some(sel) = popup.selected_payload() {
|
||||
match sel {
|
||||
ChoicePayload::ReasoningEffort(effort) => {
|
||||
self.app_event_tx
|
||||
.send(AppEvent::CodexOp(Op::OverrideTurnContext {
|
||||
cwd: None,
|
||||
approval_policy: None,
|
||||
sandbox_policy: None,
|
||||
model: None,
|
||||
effort: Some(*effort),
|
||||
summary: None,
|
||||
}));
|
||||
}
|
||||
}
|
||||
self.active_popup = ActivePopup::None;
|
||||
return (InputResult::None, true);
|
||||
}
|
||||
(InputResult::None, false)
|
||||
}
|
||||
input => self.handle_input_basic(input),
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the `@token` that the cursor is currently positioned on, if any.
|
||||
///
|
||||
/// The returned string **does not** include the leading `@`.
|
||||
@@ -562,6 +633,7 @@ impl ChatComposer {
|
||||
_ => {
|
||||
if input_starts_with_slash {
|
||||
let mut command_popup = CommandPopup::new();
|
||||
command_popup.filter_for_capabilities(self.show_reasoning_commands);
|
||||
command_popup.on_composer_text_change(first_line.to_string());
|
||||
self.active_popup = ActivePopup::Command(command_popup);
|
||||
}
|
||||
@@ -618,6 +690,12 @@ impl ChatComposer {
|
||||
fn set_has_focus(&mut self, has_focus: bool) {
|
||||
self.has_focus = has_focus;
|
||||
}
|
||||
|
||||
/// Open a choice popup to select a reasoning effort value.
|
||||
pub(crate) fn open_reasoning_effort_popup(&mut self, current: ReasoningEffortConfig) {
|
||||
let popup = ChoicePopup::new_reasoning_effort(current);
|
||||
self.active_popup = ActivePopup::Choice(popup);
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for &ChatComposer {
|
||||
@@ -625,6 +703,7 @@ impl WidgetRef for &ChatComposer {
|
||||
let popup_height = match &self.active_popup {
|
||||
ActivePopup::Command(popup) => popup.calculate_required_height(),
|
||||
ActivePopup::File(popup) => popup.calculate_required_height(),
|
||||
ActivePopup::Choice(popup) => popup.calculate_required_height(),
|
||||
ActivePopup::None => 1,
|
||||
};
|
||||
let [textarea_rect, popup_rect] =
|
||||
@@ -636,6 +715,9 @@ impl WidgetRef for &ChatComposer {
|
||||
ActivePopup::File(popup) => {
|
||||
popup.render_ref(popup_rect, buf);
|
||||
}
|
||||
ActivePopup::Choice(popup) => {
|
||||
popup.render_ref(popup_rect, buf);
|
||||
}
|
||||
ActivePopup::None => {
|
||||
let bottom_line_rect = popup_rect;
|
||||
let key_hint_style = Style::default().fg(Color::Cyan);
|
||||
@@ -887,8 +969,13 @@ mod tests {
|
||||
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
let needs_redraw = composer.handle_paste("hello".to_string());
|
||||
assert!(needs_redraw);
|
||||
@@ -911,8 +998,13 @@ mod tests {
|
||||
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
let large = "x".repeat(LARGE_PASTE_CHAR_THRESHOLD + 10);
|
||||
let needs_redraw = composer.handle_paste(large.clone());
|
||||
@@ -941,8 +1033,13 @@ mod tests {
|
||||
let large = "y".repeat(LARGE_PASTE_CHAR_THRESHOLD + 1);
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
composer.handle_paste(large);
|
||||
assert_eq!(composer.pending_pastes.len(), 1);
|
||||
@@ -983,6 +1080,7 @@ mod tests {
|
||||
sender.clone(),
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
if let Some(text) = input {
|
||||
@@ -1021,8 +1119,13 @@ mod tests {
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Type the slash command.
|
||||
for ch in [
|
||||
@@ -1065,8 +1168,13 @@ mod tests {
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
for ch in ['/', 'm', 'e', 'n', 't', 'i', 'o', 'n'] {
|
||||
let _ = composer.handle_key_event(KeyEvent::new(KeyCode::Char(ch), KeyModifiers::NONE));
|
||||
@@ -1105,8 +1213,13 @@ mod tests {
|
||||
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Define test cases: (paste content, is_large)
|
||||
let test_cases = [
|
||||
@@ -1179,8 +1292,13 @@ mod tests {
|
||||
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Define test cases: (content, is_large)
|
||||
let test_cases = [
|
||||
@@ -1246,8 +1364,13 @@ mod tests {
|
||||
|
||||
let (tx, _rx) = std::sync::mpsc::channel();
|
||||
let sender = AppEventSender::new(tx);
|
||||
let mut composer =
|
||||
ChatComposer::new(true, sender, false, "Ask Codex to do anything".to_string());
|
||||
let mut composer = ChatComposer::new(
|
||||
true,
|
||||
sender,
|
||||
false,
|
||||
"Ask Codex to do anything".to_string(),
|
||||
false,
|
||||
);
|
||||
|
||||
// Define test cases: (cursor_position_from_end, expected_pending_count)
|
||||
let test_cases = [
|
||||
|
||||
93
codex-rs/tui/src/bottom_pane/choice_popup.rs
Normal file
93
codex-rs/tui/src/bottom_pane/choice_popup.rs
Normal file
@@ -0,0 +1,93 @@
|
||||
use codex_core::protocol_config_types::ReasoningEffort as ReasoningEffortConfig;
|
||||
use ratatui::buffer::Buffer;
|
||||
use ratatui::layout::Rect;
|
||||
use ratatui::widgets::WidgetRef;
|
||||
|
||||
use super::popup_consts::MAX_POPUP_ROWS;
|
||||
use super::scroll_state::ScrollState;
|
||||
use super::selection_popup_common::GenericDisplayRow;
|
||||
use super::selection_popup_common::render_rows;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
/// Payload associated with a selected item in a generic choice popup.
|
||||
pub(crate) enum ChoicePayload {
|
||||
ReasoningEffort(ReasoningEffortConfig),
|
||||
}
|
||||
|
||||
pub(crate) struct ChoiceItem {
|
||||
pub name: String,
|
||||
pub is_current: bool,
|
||||
pub description: Option<String>,
|
||||
pub payload: ChoicePayload,
|
||||
}
|
||||
|
||||
/// A simple reusable choice popup that displays a fixed list of items and
|
||||
/// allows the user to select one using Up/Down/Enter.
|
||||
pub(crate) struct ChoicePopup {
|
||||
items: Vec<ChoiceItem>,
|
||||
state: ScrollState,
|
||||
}
|
||||
|
||||
impl ChoicePopup {
|
||||
pub(crate) fn new_reasoning_effort(current: ReasoningEffortConfig) -> Self {
|
||||
let items: Vec<ChoiceItem> = ReasoningEffortConfig::iter()
|
||||
.map(|v| ChoiceItem {
|
||||
name: v.to_string(),
|
||||
is_current: v == current,
|
||||
description: None,
|
||||
payload: ChoicePayload::ReasoningEffort(v),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut state = ScrollState::new();
|
||||
// Default selection to the current value when present
|
||||
if let Some((idx, _)) = items.iter().enumerate().find(|(_, it)| it.is_current) {
|
||||
state.selected_idx = Some(idx);
|
||||
}
|
||||
|
||||
Self { items, state }
|
||||
}
|
||||
|
||||
pub(crate) fn move_up(&mut self) {
|
||||
let len = self.items.len();
|
||||
self.state.move_up_wrap(len);
|
||||
self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS));
|
||||
}
|
||||
|
||||
pub(crate) fn move_down(&mut self) {
|
||||
let len = self.items.len();
|
||||
self.state.move_down_wrap(len);
|
||||
self.state.ensure_visible(len, len.min(MAX_POPUP_ROWS));
|
||||
}
|
||||
|
||||
pub(crate) fn selected_payload(&self) -> Option<&ChoicePayload> {
|
||||
self.state
|
||||
.selected_idx
|
||||
.and_then(|idx| self.items.get(idx))
|
||||
.map(|it| &it.payload)
|
||||
}
|
||||
|
||||
pub(crate) fn calculate_required_height(&self) -> u16 {
|
||||
self.items.len().clamp(1, MAX_POPUP_ROWS) as u16
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for &ChoicePopup {
|
||||
fn render_ref(&self, area: Rect, buf: &mut Buffer) {
|
||||
let rows_all: Vec<GenericDisplayRow> = if self.items.is_empty() {
|
||||
Vec::new()
|
||||
} else {
|
||||
self.items
|
||||
.iter()
|
||||
.map(|item| GenericDisplayRow {
|
||||
name: item.name.clone(),
|
||||
match_indices: None,
|
||||
is_current: item.is_current,
|
||||
description: item.description.clone(),
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
render_rows(area, buf, &rows_all, &self.state, MAX_POPUP_ROWS);
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,15 @@ impl CommandPopup {
|
||||
}
|
||||
}
|
||||
|
||||
/// Filter commands based on runtime capabilities (e.g., hide reasoning-related
|
||||
/// commands when the current model does not support them).
|
||||
pub(crate) fn filter_for_capabilities(&mut self, show_reasoning_commands: bool) {
|
||||
if !show_reasoning_commands {
|
||||
self.all_commands
|
||||
.retain(|(_, c)| !matches!(c, SlashCommand::ReasoningEffort));
|
||||
}
|
||||
}
|
||||
|
||||
/// Update the filter string based on the current composer text. The text
|
||||
/// passed in is expected to start with a leading '/'. Everything after the
|
||||
/// *first* '/" on the *first* line becomes the active filter that is used
|
||||
|
||||
@@ -16,6 +16,7 @@ mod bottom_pane_view;
|
||||
mod chat_composer;
|
||||
mod chat_composer_history;
|
||||
mod command_popup;
|
||||
mod choice_popup;
|
||||
mod file_search_popup;
|
||||
mod popup_consts;
|
||||
mod scroll_state;
|
||||
@@ -59,6 +60,8 @@ pub(crate) struct BottomPaneParams {
|
||||
pub(crate) has_input_focus: bool,
|
||||
pub(crate) enhanced_keys_supported: bool,
|
||||
pub(crate) placeholder_text: String,
|
||||
/// Whether to show reasoning-related slash commands.
|
||||
pub(crate) show_reasoning_commands: bool,
|
||||
}
|
||||
|
||||
impl BottomPane<'_> {
|
||||
@@ -71,6 +74,7 @@ impl BottomPane<'_> {
|
||||
params.app_event_tx.clone(),
|
||||
enhanced_keys_supported,
|
||||
params.placeholder_text,
|
||||
params.show_reasoning_commands,
|
||||
),
|
||||
active_view: None,
|
||||
app_event_tx: params.app_event_tx,
|
||||
@@ -297,6 +301,16 @@ impl BottomPane<'_> {
|
||||
self.composer.on_file_search_result(query, matches);
|
||||
self.request_redraw();
|
||||
}
|
||||
|
||||
/// Open a popup to choose the reasoning effort and apply the chosen
|
||||
/// override on selection.
|
||||
pub(crate) fn open_reasoning_effort_popup(
|
||||
&mut self,
|
||||
current: codex_core::protocol_config_types::ReasoningEffort,
|
||||
) {
|
||||
self.composer.open_reasoning_effort_popup(current);
|
||||
self.request_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
impl WidgetRef for &BottomPane<'_> {
|
||||
@@ -355,6 +369,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
pane.push_approval_request(exec_request());
|
||||
assert_eq!(CancellationEvent::Handled, pane.on_ctrl_c());
|
||||
@@ -373,6 +388,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
|
||||
// Create an approval modal (active view).
|
||||
@@ -402,6 +418,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
|
||||
// Start a running task so the status indicator replaces the composer.
|
||||
@@ -452,6 +469,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
|
||||
// Begin a task: show initial status.
|
||||
@@ -484,6 +502,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
|
||||
// Activate spinner (status view replaces composer) with no live ring.
|
||||
@@ -536,6 +555,7 @@ mod tests {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
|
||||
pane.set_task_running(true);
|
||||
|
||||
@@ -507,6 +507,7 @@ impl ChatWidget<'_> {
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported,
|
||||
placeholder_text: placeholder,
|
||||
show_reasoning_commands: config.model_family.supports_reasoning_summaries,
|
||||
}),
|
||||
active_exec_cell: None,
|
||||
config: config.clone(),
|
||||
@@ -685,6 +686,12 @@ impl ChatWidget<'_> {
|
||||
));
|
||||
}
|
||||
|
||||
/// Open a popup to choose the model reasoning effort.
|
||||
pub(crate) fn open_reasoning_effort_popup(&mut self) {
|
||||
let current = self.config.model_reasoning_effort;
|
||||
self.bottom_pane.open_reasoning_effort_popup(current);
|
||||
}
|
||||
|
||||
/// Forward file-search results to the bottom pane.
|
||||
pub(crate) fn apply_file_search_result(&mut self, query: String, matches: Vec<FileMatch>) {
|
||||
self.bottom_pane.on_file_search_result(query, matches);
|
||||
|
||||
@@ -125,6 +125,7 @@ fn make_chatwidget_manual() -> (
|
||||
has_input_focus: true,
|
||||
enhanced_keys_supported: false,
|
||||
placeholder_text: "Ask Codex to do anything".to_string(),
|
||||
show_reasoning_commands: false,
|
||||
});
|
||||
let widget = ChatWidget {
|
||||
app_event_tx,
|
||||
|
||||
@@ -17,6 +17,7 @@ pub enum SlashCommand {
|
||||
Compact,
|
||||
Diff,
|
||||
Mention,
|
||||
ReasoningEffort,
|
||||
Status,
|
||||
Logout,
|
||||
Quit,
|
||||
@@ -32,6 +33,9 @@ impl SlashCommand {
|
||||
SlashCommand::Init => "create an AGENTS.md file with instructions for Codex",
|
||||
SlashCommand::Compact => "summarize conversation to prevent hitting the context limit",
|
||||
SlashCommand::Quit => "exit Codex",
|
||||
SlashCommand::ReasoningEffort => {
|
||||
"choose model reasoning effort (low/medium/high/minimal)"
|
||||
}
|
||||
SlashCommand::Diff => "show git diff (including untracked files)",
|
||||
SlashCommand::Mention => "mention a file",
|
||||
SlashCommand::Status => "show current session configuration and token usage",
|
||||
|
||||
Reference in New Issue
Block a user