mirror of
https://github.com/openai/codex.git
synced 2026-03-03 13:13:18 +00:00
Compare commits
1 Commits
fix/notify
...
ccy/mcp-se
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0fc0137720 |
@@ -48,6 +48,7 @@ use crate::conversation_history::ConversationHistory;
|
||||
use crate::environment_context::EnvironmentContext;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::error::error_event_from;
|
||||
use crate::exec::ExecToolCallOutput;
|
||||
#[cfg(test)]
|
||||
use crate::exec::StreamOutput;
|
||||
@@ -388,7 +389,10 @@ impl Session {
|
||||
error!("{message}");
|
||||
post_session_configured_error_events.push(Event {
|
||||
id: INITIAL_SUBMIT_ID.to_owned(),
|
||||
msg: EventMsg::Error(ErrorEvent { message }),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message,
|
||||
markdown_message: None,
|
||||
}),
|
||||
});
|
||||
(McpConnectionManager::default(), Default::default())
|
||||
}
|
||||
@@ -401,7 +405,10 @@ impl Session {
|
||||
error!("{message}");
|
||||
post_session_configured_error_events.push(Event {
|
||||
id: INITIAL_SUBMIT_ID.to_owned(),
|
||||
msg: EventMsg::Error(ErrorEvent { message }),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message,
|
||||
markdown_message: None,
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1458,6 +1465,7 @@ async fn submission_loop(
|
||||
id: sub.id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: "Failed to shutdown rollout recorder".to_string(),
|
||||
markdown_message: None,
|
||||
}),
|
||||
};
|
||||
sess.send_event(event).await;
|
||||
@@ -1841,6 +1849,7 @@ pub(crate) async fn run_task(
|
||||
message: format!(
|
||||
"Conversation is still above the token limit after automatic summarization (limit {limit_str}, current {current_tokens}). Please start a new session or trim your input."
|
||||
),
|
||||
markdown_message: None,
|
||||
}),
|
||||
};
|
||||
sess.send_event(event).await;
|
||||
@@ -1871,9 +1880,7 @@ pub(crate) async fn run_task(
|
||||
info!("Turn error: {e:#}");
|
||||
let event = Event {
|
||||
id: sub_id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
msg: EventMsg::Error(error_event_from(&e)),
|
||||
};
|
||||
sess.send_event(event).await;
|
||||
// let the user continue the conversation
|
||||
|
||||
@@ -7,9 +7,9 @@ use crate::Prompt;
|
||||
use crate::client_common::ResponseEvent;
|
||||
use crate::error::CodexErr;
|
||||
use crate::error::Result as CodexResult;
|
||||
use crate::error::error_event_from;
|
||||
use crate::protocol::AgentMessageEvent;
|
||||
use crate::protocol::CompactedItem;
|
||||
use crate::protocol::ErrorEvent;
|
||||
use crate::protocol::Event;
|
||||
use crate::protocol::EventMsg;
|
||||
use crate::protocol::InputItem;
|
||||
@@ -108,9 +108,7 @@ async fn run_compact_task_inner(
|
||||
.await;
|
||||
let event = Event {
|
||||
id: sub_id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
msg: EventMsg::Error(error_event_from(&e)),
|
||||
};
|
||||
sess.send_event(event).await;
|
||||
return;
|
||||
@@ -131,9 +129,7 @@ async fn run_compact_task_inner(
|
||||
} else {
|
||||
let event = Event {
|
||||
id: sub_id.clone(),
|
||||
msg: EventMsg::Error(ErrorEvent {
|
||||
message: e.to_string(),
|
||||
}),
|
||||
msg: EventMsg::Error(error_event_from(&e)),
|
||||
};
|
||||
sess.send_event(event).await;
|
||||
return;
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::exec::ExecToolCallOutput;
|
||||
use crate::token_data::KnownPlan;
|
||||
use crate::token_data::PlanType;
|
||||
use codex_protocol::ConversationId;
|
||||
use codex_protocol::protocol::ErrorEvent;
|
||||
use codex_protocol::protocol::RateLimitSnapshot;
|
||||
use reqwest::StatusCode;
|
||||
use serde_json;
|
||||
@@ -12,6 +13,8 @@ use tokio::task::JoinError;
|
||||
|
||||
pub type Result<T> = std::result::Result<T, CodexErr>;
|
||||
|
||||
const PRICING_URL: &str = "https://openai.com/chatgpt/pricing";
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SandboxErr {
|
||||
/// Error from sandbox execution
|
||||
@@ -197,7 +200,7 @@ impl std::fmt::Display for UsageLimitReachedError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let message = match self.plan_type.as_ref() {
|
||||
Some(PlanType::Known(KnownPlan::Plus)) => format!(
|
||||
"You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing){}",
|
||||
"You've hit your usage limit. Upgrade to Pro ({PRICING_URL}){}",
|
||||
retry_suffix_after_or(self.resets_in_seconds)
|
||||
),
|
||||
Some(PlanType::Known(KnownPlan::Team)) | Some(PlanType::Known(KnownPlan::Business)) => {
|
||||
@@ -207,8 +210,9 @@ impl std::fmt::Display for UsageLimitReachedError {
|
||||
)
|
||||
}
|
||||
Some(PlanType::Known(KnownPlan::Free)) => {
|
||||
"You've hit your usage limit. Upgrade to Plus to continue using Codex (https://openai.com/chatgpt/pricing)."
|
||||
.to_string()
|
||||
format!(
|
||||
"You've hit your usage limit. Upgrade to Plus to continue using Codex ({PRICING_URL})."
|
||||
)
|
||||
}
|
||||
Some(PlanType::Known(KnownPlan::Pro))
|
||||
| Some(PlanType::Known(KnownPlan::Enterprise))
|
||||
@@ -226,6 +230,40 @@ impl std::fmt::Display for UsageLimitReachedError {
|
||||
}
|
||||
}
|
||||
|
||||
impl UsageLimitReachedError {
|
||||
pub(crate) fn markdown_message(&self) -> String {
|
||||
match self.plan_type.as_ref() {
|
||||
Some(PlanType::Known(KnownPlan::Plus)) => format!(
|
||||
"You've hit your usage limit. Upgrade to [Pro]({PRICING_URL}){}",
|
||||
retry_suffix_after_or(self.resets_in_seconds)
|
||||
),
|
||||
Some(PlanType::Known(KnownPlan::Free)) => format!(
|
||||
"You've hit your usage limit. [Upgrade to Plus]({PRICING_URL}) to continue using Codex."
|
||||
),
|
||||
_ => self.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CodexErr {
|
||||
pub(crate) fn markdown_message(&self) -> Option<String> {
|
||||
match self {
|
||||
CodexErr::UsageLimitReached(err) => Some(err.markdown_message()),
|
||||
CodexErr::UsageNotIncluded => Some(format!(
|
||||
"To use Codex with your ChatGPT plan, [upgrade to Plus]({PRICING_URL})."
|
||||
)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn error_event_from(err: &CodexErr) -> ErrorEvent {
|
||||
ErrorEvent {
|
||||
message: err.to_string(),
|
||||
markdown_message: err.markdown_message(),
|
||||
}
|
||||
}
|
||||
|
||||
fn retry_suffix(resets_in_seconds: Option<u64>) -> String {
|
||||
if let Some(secs) = resets_in_seconds {
|
||||
let reset_duration = format_reset_duration(secs);
|
||||
@@ -348,6 +386,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_limit_reached_error_markdown_plus_plan() {
|
||||
let err = UsageLimitReachedError {
|
||||
plan_type: Some(PlanType::Known(KnownPlan::Plus)),
|
||||
resets_in_seconds: Some(60),
|
||||
rate_limits: Some(rate_limit_snapshot()),
|
||||
};
|
||||
assert_eq!(
|
||||
err.markdown_message(),
|
||||
"You've hit your usage limit. Upgrade to [Pro](https://openai.com/chatgpt/pricing) or try again in 1 minute."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_limit_reached_error_formats_free_plan() {
|
||||
let err = UsageLimitReachedError {
|
||||
@@ -361,6 +412,19 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_limit_reached_error_markdown_free_plan() {
|
||||
let err = UsageLimitReachedError {
|
||||
plan_type: Some(PlanType::Known(KnownPlan::Free)),
|
||||
resets_in_seconds: Some(3600),
|
||||
rate_limits: Some(rate_limit_snapshot()),
|
||||
};
|
||||
assert_eq!(
|
||||
err.markdown_message(),
|
||||
"You've hit your usage limit. [Upgrade to Plus](https://openai.com/chatgpt/pricing) to continue using Codex."
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_limit_reached_error_formats_default_when_none() {
|
||||
let err = UsageLimitReachedError {
|
||||
@@ -413,6 +477,31 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_err_usage_not_included_markdown() {
|
||||
let err = CodexErr::UsageNotIncluded;
|
||||
assert_eq!(
|
||||
err.markdown_message(),
|
||||
Some("To use Codex with your ChatGPT plan, [upgrade to Plus](https://openai.com/chatgpt/pricing).".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn codex_err_usage_limit_reached_markdown() {
|
||||
let err = CodexErr::UsageLimitReached(UsageLimitReachedError {
|
||||
plan_type: Some(PlanType::Known(KnownPlan::Plus)),
|
||||
resets_in_seconds: Some(120),
|
||||
rate_limits: Some(rate_limit_snapshot()),
|
||||
});
|
||||
assert_eq!(
|
||||
err.markdown_message(),
|
||||
Some(
|
||||
"You've hit your usage limit. Upgrade to [Pro](https://openai.com/chatgpt/pricing) or try again in 2 minutes."
|
||||
.to_string()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn usage_limit_reached_includes_minutes_when_available() {
|
||||
let err = UsageLimitReachedError {
|
||||
|
||||
@@ -583,7 +583,7 @@ async fn auto_compact_stops_after_failed_attempt() {
|
||||
.unwrap();
|
||||
|
||||
let error_event = wait_for_event(&codex, |ev| matches!(ev, EventMsg::Error(_))).await;
|
||||
let EventMsg::Error(ErrorEvent { message }) = error_event else {
|
||||
let EventMsg::Error(ErrorEvent { message, .. }) = error_event else {
|
||||
panic!("expected error event");
|
||||
};
|
||||
assert!(
|
||||
|
||||
@@ -157,7 +157,7 @@ impl EventProcessor for EventProcessorWithHumanOutput {
|
||||
fn process_event(&mut self, event: Event) -> CodexStatus {
|
||||
let Event { id: _, msg } = event;
|
||||
match msg {
|
||||
EventMsg::Error(ErrorEvent { message }) => {
|
||||
EventMsg::Error(ErrorEvent { message, .. }) => {
|
||||
let prefix = "ERROR:".style(self.red);
|
||||
ts_msg!(self, "{prefix} {message}");
|
||||
}
|
||||
|
||||
@@ -434,6 +434,7 @@ fn error_event_produces_error() {
|
||||
"e1",
|
||||
EventMsg::Error(codex_core::protocol::ErrorEvent {
|
||||
message: "boom".to_string(),
|
||||
markdown_message: None,
|
||||
}),
|
||||
));
|
||||
assert_eq!(
|
||||
@@ -469,6 +470,7 @@ fn error_followed_by_task_complete_produces_turn_failed() {
|
||||
"e1",
|
||||
EventMsg::Error(ErrorEvent {
|
||||
message: "boom".to_string(),
|
||||
markdown_message: None,
|
||||
}),
|
||||
);
|
||||
assert_eq!(
|
||||
|
||||
@@ -15,6 +15,7 @@ use codex_core::NewConversation;
|
||||
use codex_core::config::Config as CodexConfig;
|
||||
use codex_core::protocol::AgentMessageEvent;
|
||||
use codex_core::protocol::ApplyPatchApprovalRequestEvent;
|
||||
use codex_core::protocol::ErrorEvent;
|
||||
use codex_core::protocol::Event;
|
||||
use codex_core::protocol::EventMsg;
|
||||
use codex_core::protocol::ExecApprovalRequestEvent;
|
||||
@@ -194,10 +195,12 @@ async fn run_codex_tool_session_inner(
|
||||
}
|
||||
EventMsg::Error(err_event) => {
|
||||
// Return a response to conclude the tool call when the Codex session reports an error (e.g., interruption).
|
||||
let result = json!({
|
||||
"error": err_event.message,
|
||||
});
|
||||
let result = error_result_json(&err_event);
|
||||
outgoing.send_response(request_id.clone(), result).await;
|
||||
running_requests_id_to_codex_uuid
|
||||
.lock()
|
||||
.await
|
||||
.remove(&request_id);
|
||||
break;
|
||||
}
|
||||
EventMsg::ApplyPatchApprovalRequest(ApplyPatchApprovalRequestEvent {
|
||||
@@ -310,3 +313,41 @@ async fn run_codex_tool_session_inner(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn error_result_json(err_event: &ErrorEvent) -> serde_json::Value {
|
||||
let mut result = json!({
|
||||
"error": err_event.message.clone(),
|
||||
});
|
||||
if let Some(markdown_message) = &err_event.markdown_message {
|
||||
result["errorMarkdown"] = json!(markdown_message);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn error_result_without_markdown() {
|
||||
let event = ErrorEvent {
|
||||
message: "raw".to_string(),
|
||||
markdown_message: None,
|
||||
};
|
||||
assert_eq!(error_result_json(&event), json!({ "error": "raw" }));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_result_with_markdown() {
|
||||
let event = ErrorEvent {
|
||||
message: "raw".to_string(),
|
||||
markdown_message: Some("md".to_string()),
|
||||
};
|
||||
assert_eq!(
|
||||
error_result_json(&event),
|
||||
json!({
|
||||
"error": "raw",
|
||||
"errorMarkdown": "md",
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -534,6 +534,9 @@ pub struct ExitedReviewModeEvent {
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||
pub struct ErrorEvent {
|
||||
pub message: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
#[ts(optional)]
|
||||
pub markdown_message: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, TS)]
|
||||
|
||||
@@ -1395,7 +1395,7 @@ impl ChatWidget {
|
||||
self.set_token_info(ev.info);
|
||||
self.on_rate_limit_snapshot(ev.rate_limits);
|
||||
}
|
||||
EventMsg::Error(ErrorEvent { message }) => self.on_error(message),
|
||||
EventMsg::Error(ErrorEvent { message, .. }) => self.on_error(message),
|
||||
EventMsg::TurnAborted(ev) => match ev.reason {
|
||||
TurnAbortReason::Interrupted => {
|
||||
self.on_interrupted_turn(ev.reason);
|
||||
|
||||
Reference in New Issue
Block a user