Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 44 additions & 38 deletions codex-rs/tui/src/chatwidget/safety_buffering.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use super::*;
use codex_app_server_protocol::ModelSafetyBufferingUpdatedNotification;

const SAFETY_BUFFERING_PROMPT_VIEW_ID: &str = "safety-buffering-prompt";
const SAFETY_BUFFERING_LEARN_MORE_URL: &str = "https://help.openai.com/en/articles/20001326";

const SAFETY_BUFFERING_MESSAGE_WITH_RETRY: &str = "This request requires additional safety checks, which can take extra time. Hang tight or retry with a faster model for a quicker response, though it may be less capable of handling complex requests.";
const SAFETY_BUFFERING_MESSAGE_WITHOUT_RETRY: &str =
Expand All @@ -12,7 +13,7 @@ const SAFETY_BUFFERING_MESSAGE_WITHOUT_RETRY: &str =
#[derive(Debug)]
struct ActiveSafetyBuffering {
turn_id: String,
retry_prompt_shown: bool,
last_prompt_had_retry: bool,
agent_message_started: bool,
}

Expand Down Expand Up @@ -119,22 +120,18 @@ impl ChatWidget {
.map(|(_, turn)| turn.clone());
let thread_id = self.thread_id;
let can_offer_retry = faster_model.is_some() && retry_turn.is_some() && thread_id.is_some();
if !can_offer_retry {
self.bottom_pane
.dismiss_view_by_id(SAFETY_BUFFERING_PROMPT_VIEW_ID);
}
let previous_active = self
.safety_buffering
.active
.as_ref()
.filter(|active| active.turn_id == turn_id);
let retry_prompt_shown = previous_active.is_some_and(|active| active.retry_prompt_shown);
let should_show_retry_prompt = can_offer_retry && !retry_prompt_shown;
let should_show_prompt =
previous_active.is_none_or(|active| active.last_prompt_had_retry != can_offer_retry);
let agent_message_started =
previous_active.is_some_and(|active| active.agent_message_started);
self.safety_buffering.active = Some(ActiveSafetyBuffering {
turn_id: turn_id.clone(),
retry_prompt_shown: retry_prompt_shown || should_show_retry_prompt,
last_prompt_had_retry: can_offer_retry,
agent_message_started,
});

Expand All @@ -151,45 +148,54 @@ impl ChatWidget {
/*details_max_lines*/ 6,
);

let (Some(faster_model), Some(turn), Some(thread_id)) =
(faster_model, retry_turn, thread_id)
else {
return;
};
if !should_show_retry_prompt {
if !should_show_prompt {
return;
}
self.bottom_pane
.dismiss_view_by_id(SAFETY_BUFFERING_PROMPT_VIEW_ID);

let header = ColumnRenderable::with(vec![
Box::new(Line::from("Additional safety checks").bold()) as Box<dyn Renderable>,
Box::new(
Paragraph::new(Line::from(SAFETY_BUFFERING_MESSAGE_WITH_RETRY).dim())
.wrap(Wrap { trim: false }),
),
Box::new(Paragraph::new(Line::from(message).dim()).wrap(Wrap { trim: false })),
]);
let mut items = Vec::new();
if let (Some(faster_model), Some(turn), Some(thread_id)) =
(faster_model, retry_turn, thread_id)
{
items.push(SelectionItem {
name: "Retry with a faster model".to_string(),
actions: vec![Box::new(move |tx| {
tx.send(AppEvent::RetrySafetyBufferedTurn {
thread_id,
turn_id: turn_id.clone(),
model: faster_model.clone(),
turn: turn.clone(),
});
})],
dismiss_on_select: true,
..Default::default()
});
}
items.extend([
SelectionItem {
name: "Keep waiting".to_string(),
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: "Learn more".to_string(),
actions: vec![Box::new(|tx| {
tx.send(AppEvent::OpenUrlInBrowser {
url: SAFETY_BUFFERING_LEARN_MORE_URL.to_string(),
});
})],
..Default::default()
},
]);
self.bottom_pane.show_selection_view(SelectionViewParams {
view_id: Some(SAFETY_BUFFERING_PROMPT_VIEW_ID),
header: Box::new(header),
items: vec![
SelectionItem {
name: "Retry with a faster model".to_string(),
actions: vec![Box::new(move |tx| {
tx.send(AppEvent::RetrySafetyBufferedTurn {
thread_id,
turn_id: turn_id.clone(),
model: faster_model.clone(),
turn: turn.clone(),
});
})],
dismiss_on_select: true,
..Default::default()
},
SelectionItem {
name: "Keep waiting".to_string(),
dismiss_on_select: true,
..Default::default()
},
],
items,
..Default::default()
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ expression: popup

1. Retry with a faster model
2. Keep waiting
3. Learn more

Press enter to confirm or esc to go back
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
---
source: tui/src/chatwidget/tests/app_server.rs
expression: "render_bottom_popup(&chat, 80)"
expression: popup
---
• Working (0s • esc to interrupt)
This request requires additional safety checks, which can take extra time.
Additional safety checks
This request requires additional safety checks, which can take extra time.

› 1. Keep waiting
2. Learn more

› Ask Codex to do anything

gpt-5.5 default · /tmp/project
Press enter to confirm or esc to go back
18 changes: 18 additions & 0 deletions codex-rs/tui/src/chatwidget/tests/app_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,21 @@ async fn safety_buffering_offers_one_retry_with_app_wording() {
let popup = render_bottom_popup(&chat, /*width*/ 80);
assert_chatwidget_snapshot!("safety_buffering_retry_prompt", popup);

chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
chat.handle_key_event(KeyEvent::new(KeyCode::Down, KeyModifiers::NONE));
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
let opened_url = loop {
match rx.try_recv() {
Ok(AppEvent::OpenUrlInBrowser { url }) => break url,
Ok(_) => continue,
Err(err) => panic!("expected learn-more URL event: {err}"),
}
};
assert_eq!(opened_url, "https://help.openai.com/en/articles/20001326");
assert!(render_bottom_popup(&chat, /*width*/ 80).contains("Additional safety checks"));

chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::NONE));
chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
let (event_thread_id, event_turn_id, model, turn) = loop {
match rx.try_recv() {
Expand Down Expand Up @@ -201,6 +216,9 @@ async fn safety_buffering_without_retry_shows_short_app_message() {
Some(ReplayKind::ThreadSnapshot),
);
assert_eq!(render_popup(&chat), popup);

chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::NONE));
assert!(!render_bottom_popup(&chat, /*width*/ 80).contains("Additional safety checks"));
}

#[tokio::test]
Expand Down
4 changes: 2 additions & 2 deletions codex-rs/tui/src/history_cell/notices.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ const SAFETY_ACCESS_BLOCK_LEARN_MORE_URL: &str = "https://help.openai.com/en/art

pub(crate) fn new_safety_access_block_event() -> SafetyAccessBlockCell {
SafetyAccessBlockCell {
body: "We take extra caution with requests involving biological research and applications that could pose safety risks. If you’re a researcher at an approved organization, you may be able to apply for Trusted Access.",
trusted_access_url: "https://openai.com/form/trusted-access-for-life-sciences",
body: "We take extra caution with requests involving biological research and applications that could pose safety risks. Eligible researchers can apply for Trusted Access. If you’re a researcher at an approved organization, you may be able to apply for Trusted Access.",
trusted_access_url: "https://www.openai.com/form/trusted-access-for-biology-research/",
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ expression: rendered
---
ⓘ This content can't be shown
We take extra caution with requests involving biological research and
applications that could pose safety risks. If you’re a researcher at an
approved organization, you may be able to apply for Trusted Access.
Trusted Access: https://openai.com/form/trusted-access-for-life-sciences
applications that could pose safety risks. Eligible researchers can apply
for Trusted Access. If you’re a researcher at an approved organization, you
may be able to apply for Trusted Access.
Trusted Access: https://www.openai.com/form/trusted-access-for-biology-
research/
Learn more: https://help.openai.com/en/articles/20001326
Loading