mirror of
https://github.com/openai/codex.git
synced 2026-11-06 20:20:34 +00:00
Merge branch 'main' into codex/se-7928-otel-best-effort
This commit is contained in:
@@ -78,6 +78,7 @@ jobs:
|
||||
30. automations - Issues involving scheduled automation tasks or heartbeats.
|
||||
31. pets - Issues involving pets avatars and animations.
|
||||
32. agent - Fallback only for core agent loop or agent-related issues that do not fit app-server, connectivity, subagent, session, config, plan, computer-use, browser, memory, imagen, remote, performance, automations, or pets.
|
||||
33. aws-bedrock - Issues involving the Amazon Bedrock model provider or Bedrock Mantle. Do not use for unrelated AWS services or generic AWS mentions.
|
||||
|
||||
Issue number: ${{ github.event.issue.number }}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ curl -fsSL https://chatgpt.com/codex/install.sh | sh
|
||||
|
||||
Run the following on Windows to install Codex CLI:
|
||||
|
||||
```
|
||||
```shell
|
||||
powershell -ExecutionPolicy ByPass -c "irm https://chatgpt.com/codex/install.ps1 | iex"
|
||||
```
|
||||
|
||||
|
||||
@@ -43,7 +43,6 @@ use tracing::debug;
|
||||
use tracing::error;
|
||||
use tracing::info;
|
||||
use tracing::instrument;
|
||||
use tracing::trace;
|
||||
use tungstenite::extensions::ExtensionsConfig;
|
||||
use tungstenite::extensions::compression::deflate::DeflateConfig;
|
||||
use tungstenite::protocol::WebSocketConfig;
|
||||
@@ -782,8 +781,6 @@ async fn send_websocket_request(
|
||||
telemetry: Option<&Arc<dyn WebsocketTelemetry>>,
|
||||
connection_reused: bool,
|
||||
) -> Result<(), ApiError> {
|
||||
trace!("websocket request: {request_text}");
|
||||
|
||||
let request_start = Instant::now();
|
||||
let result = tokio::time::timeout(
|
||||
idle_timeout,
|
||||
|
||||
@@ -169,7 +169,7 @@ pub(crate) struct CompactConversationRequestSettings {
|
||||
|
||||
fn reasoning_effort_for_request(effort: ReasoningEffortConfig) -> ReasoningEffortConfig {
|
||||
match effort {
|
||||
ReasoningEffortConfig::Ultra => ReasoningEffortConfig::Custom("max".to_string()),
|
||||
ReasoningEffortConfig::Ultra => ReasoningEffortConfig::Max,
|
||||
effort => effort,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -292,10 +292,7 @@ fn ultra_reasoning_uses_max_for_requests() {
|
||||
super::reasoning_effort_for_request(ReasoningEffort::Ultra),
|
||||
super::reasoning_effort_for_request(ReasoningEffort::High),
|
||||
),
|
||||
(
|
||||
ReasoningEffort::Custom("max".to_string()),
|
||||
ReasoningEffort::High,
|
||||
)
|
||||
(ReasoningEffort::Max, ReasoningEffort::High,)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2171,7 +2171,7 @@ async fn skills_use_aliases_in_developer_message_under_budget_pressure() {
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread", worker_threads = 2)]
|
||||
async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
|
||||
async fn includes_configured_max_effort_in_request() -> anyhow::Result<()> {
|
||||
skip_if_no_network!(Ok(()));
|
||||
let server = MockServer::start().await;
|
||||
|
||||
@@ -2183,7 +2183,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
|
||||
let TestCodex { codex, .. } = test_codex()
|
||||
.with_model("gpt-5.4")
|
||||
.with_config(|config| {
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::Medium);
|
||||
config.model_reasoning_effort = Some(ReasoningEffort::Max);
|
||||
})
|
||||
.build(&server)
|
||||
.await?;
|
||||
@@ -2212,7 +2212,7 @@ async fn includes_configured_effort_in_request() -> anyhow::Result<()> {
|
||||
.get("reasoning")
|
||||
.and_then(|t| t.get("effort"))
|
||||
.and_then(|v| v.as_str()),
|
||||
Some("medium")
|
||||
Some("max")
|
||||
);
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -329,7 +329,7 @@ async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result
|
||||
/*priority*/ 1_000,
|
||||
TruncationPolicyConfig::bytes(/*limit*/ 10_000),
|
||||
);
|
||||
let custom_reasoning_effort = ReasoningEffort::Custom("max".to_string());
|
||||
let custom_reasoning_effort = ReasoningEffort::Custom("future".to_string());
|
||||
remote_model.default_reasoning_level = Some(custom_reasoning_effort.clone());
|
||||
remote_model.supported_reasoning_levels = vec![
|
||||
ReasoningEffortPreset {
|
||||
@@ -391,7 +391,7 @@ async fn remote_models_long_model_slug_is_sent_with_custom_reasoning() -> Result
|
||||
.and_then(|reasoning| reasoning.get("summary"))
|
||||
.and_then(|value| value.as_str());
|
||||
assert_eq!(body["model"].as_str(), Some(requested_model));
|
||||
assert_eq!(reasoning_effort, Some("max"));
|
||||
assert_eq!(reasoning_effort, Some("future"));
|
||||
assert_eq!(reasoning_summary, Some("detailed"));
|
||||
|
||||
Ok(())
|
||||
|
||||
@@ -274,7 +274,9 @@ impl ExecServerClient {
|
||||
connect_async_with_config(
|
||||
request,
|
||||
Some(noise_relay_websocket_config()),
|
||||
/*disable_nagle*/ false,
|
||||
// Rendezvous sends small, latency-sensitive frames, so avoid Nagle's coalescing delay.
|
||||
/*disable_nagle*/
|
||||
true,
|
||||
),
|
||||
)
|
||||
.await
|
||||
|
||||
@@ -561,7 +561,9 @@ async fn connect_rendezvous(
|
||||
connect_async_with_config(
|
||||
request,
|
||||
Some(noise_relay_websocket_config()),
|
||||
/*disable_nagle*/ false,
|
||||
// Rendezvous sends small, latency-sensitive frames, so avoid Nagle's coalescing delay.
|
||||
/*disable_nagle*/
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.map(|(websocket, _)| websocket)
|
||||
|
||||
@@ -70,7 +70,7 @@ fn gpt_5_6_bedrock_model(bedrock_slug: &str, display_name: &str, priority: i32)
|
||||
model
|
||||
.supported_reasoning_levels
|
||||
.push(ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Custom("max".to_string()),
|
||||
effort: ReasoningEffort::Max,
|
||||
description: "Maximum reasoning depth for the hardest problems".to_string(),
|
||||
});
|
||||
model
|
||||
@@ -148,7 +148,7 @@ mod tests {
|
||||
expected
|
||||
.supported_reasoning_levels
|
||||
.push(ReasoningEffortPreset {
|
||||
effort: ReasoningEffort::Custom("max".to_string()),
|
||||
effort: ReasoningEffort::Max,
|
||||
description: "Maximum reasoning depth for the hardest problems".to_string(),
|
||||
});
|
||||
|
||||
|
||||
@@ -45,6 +45,7 @@ pub enum ReasoningEffort {
|
||||
Medium,
|
||||
High,
|
||||
XHigh,
|
||||
Max,
|
||||
Ultra,
|
||||
/// A model-defined effort value that this client does not know yet.
|
||||
Custom(String),
|
||||
@@ -60,6 +61,7 @@ impl ReasoningEffort {
|
||||
Self::Medium => "medium",
|
||||
Self::High => "high",
|
||||
Self::XHigh => "xhigh",
|
||||
Self::Max => "max",
|
||||
Self::Ultra => "ultra",
|
||||
Self::Custom(effort) => effort,
|
||||
}
|
||||
@@ -125,6 +127,7 @@ impl FromStr for ReasoningEffort {
|
||||
"medium" => Ok(Self::Medium),
|
||||
"high" => Ok(Self::High),
|
||||
"xhigh" => Ok(Self::XHigh),
|
||||
"max" => Ok(Self::Max),
|
||||
"ultra" => Ok(Self::Ultra),
|
||||
"" => Err("reasoning_effort must not be empty".to_string()),
|
||||
effort => Ok(Self::Custom(effort.to_string())),
|
||||
@@ -703,30 +706,35 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reasoning_effort_accepts_known_and_custom_values() {
|
||||
let custom = ReasoningEffort::Custom("max".to_string());
|
||||
let deserialized = from_str::<ReasoningEffort>(r#""max""#)
|
||||
let custom = ReasoningEffort::Custom("future".to_string());
|
||||
let deserialized = from_str::<ReasoningEffort>(r#""future""#)
|
||||
.expect("custom reasoning effort should deserialize");
|
||||
let serialized = to_string(&custom).expect("custom reasoning effort should serialize");
|
||||
let serialized_max = to_string(&ReasoningEffort::Max).expect("Max should serialize");
|
||||
let serialized_ultra = to_string(&ReasoningEffort::Ultra).expect("Ultra should serialize");
|
||||
|
||||
assert_eq!(
|
||||
(
|
||||
"high".parse(),
|
||||
"ultra".parse(),
|
||||
"max".parse(),
|
||||
"ultra".parse(),
|
||||
"future".parse(),
|
||||
deserialized,
|
||||
serialized,
|
||||
serialized_max,
|
||||
serialized_ultra,
|
||||
custom.to_string(),
|
||||
),
|
||||
(
|
||||
Ok(ReasoningEffort::High),
|
||||
Ok(ReasoningEffort::Max),
|
||||
Ok(ReasoningEffort::Ultra),
|
||||
Ok(custom.clone()),
|
||||
custom,
|
||||
r#""future""#.to_string(),
|
||||
r#""max""#.to_string(),
|
||||
r#""ultra""#.to_string(),
|
||||
"max".to_string(),
|
||||
"future".to_string(),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -505,6 +505,7 @@ impl ChatWidget {
|
||||
ReasoningEffortConfig::Medium => "Medium".to_string(),
|
||||
ReasoningEffortConfig::High => "High".to_string(),
|
||||
ReasoningEffortConfig::XHigh => "Extra high".to_string(),
|
||||
ReasoningEffortConfig::Max => "Max".to_string(),
|
||||
ReasoningEffortConfig::Ultra => "Ultra".to_string(),
|
||||
ReasoningEffortConfig::Custom(value) => value.clone(),
|
||||
}
|
||||
|
||||
@@ -225,7 +225,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn next_reasoning_effort_uses_advertised_order_for_custom_levels() {
|
||||
let custom_effort = ReasoningEffortConfig::Custom("max".to_string());
|
||||
let custom_effort = ReasoningEffortConfig::Custom("future".to_string());
|
||||
let choices = vec![
|
||||
ReasoningEffortConfig::High,
|
||||
ReasoningEffortConfig::Low,
|
||||
|
||||
@@ -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 =
|
||||
@@ -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,
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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()
|
||||
});
|
||||
}
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@ expression: popup
|
||||
|
||||
1. Low Fast responses with lighter reasoning
|
||||
2. Medium (default) Balances speed and reasoning depth for everyday tasks
|
||||
3. max Maximum available reasoning
|
||||
3. Max Maximum available reasoning
|
||||
› 4. High (current) Greater reasoning depth for complex problems
|
||||
5. Extra high Extra high reasoning depth for complex problems
|
||||
|
||||
|
||||
+1
@@ -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
|
||||
|
||||
+6
-6
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
@@ -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]
|
||||
|
||||
@@ -3191,7 +3191,7 @@ async fn model_reasoning_selection_popup_snapshot() {
|
||||
preset.supported_reasoning_efforts.insert(
|
||||
2,
|
||||
ReasoningEffortPreset {
|
||||
effort: ReasoningEffortConfig::Custom("max".to_string()),
|
||||
effort: ReasoningEffortConfig::Max,
|
||||
description: "Maximum available reasoning".to_string(),
|
||||
},
|
||||
);
|
||||
@@ -3204,7 +3204,7 @@ async fn model_reasoning_selection_popup_snapshot() {
|
||||
#[tokio::test]
|
||||
async fn model_reasoning_selection_popup_applies_custom_effort() {
|
||||
let (mut chat, mut rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await;
|
||||
let custom_effort = ReasoningEffortConfig::Custom("max".to_string());
|
||||
let custom_effort = ReasoningEffortConfig::Custom("future".to_string());
|
||||
chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh));
|
||||
|
||||
let mut preset = get_available_model(&chat, "gpt-5.4");
|
||||
|
||||
@@ -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.",
|
||||
trusted_access_url: "https://www.openai.com/form/trusted-access-for-biology-research/",
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+4
-3
@@ -4,7 +4,8 @@ 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.
|
||||
Trusted Access: https://www.openai.com/form/trusted-access-for-biology-
|
||||
research/
|
||||
Learn more: https://help.openai.com/en/articles/20001326
|
||||
|
||||
Reference in New Issue
Block a user