Adjust error messages (#1969)
<img width="1378" height="285" alt="image" src="https://github.com/user-attachments/assets/f0283378-f839-4a1f-8331-909694a04b1f" />
This commit is contained in:
@@ -31,6 +31,7 @@ use crate::config_types::ReasoningEffort as ReasoningEffortConfig;
|
|||||||
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
use crate::config_types::ReasoningSummary as ReasoningSummaryConfig;
|
||||||
use crate::error::CodexErr;
|
use crate::error::CodexErr;
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::error::UsageLimitReachedError;
|
||||||
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
use crate::flags::CODEX_RS_SSE_FIXTURE;
|
||||||
use crate::model_provider_info::ModelProviderInfo;
|
use crate::model_provider_info::ModelProviderInfo;
|
||||||
use crate::model_provider_info::WireApi;
|
use crate::model_provider_info::WireApi;
|
||||||
@@ -195,7 +196,7 @@ impl ModelClient {
|
|||||||
|
|
||||||
if let Some(auth) = auth.as_ref()
|
if let Some(auth) = auth.as_ref()
|
||||||
&& auth.mode == AuthMode::ChatGPT
|
&& auth.mode == AuthMode::ChatGPT
|
||||||
&& let Some(account_id) = auth.get_account_id().await
|
&& let Some(account_id) = auth.get_account_id()
|
||||||
{
|
{
|
||||||
req_builder = req_builder.header("chatgpt-account-id", account_id);
|
req_builder = req_builder.header("chatgpt-account-id", account_id);
|
||||||
}
|
}
|
||||||
@@ -263,7 +264,9 @@ impl ModelClient {
|
|||||||
}) = body
|
}) = body
|
||||||
{
|
{
|
||||||
if r#type == "usage_limit_reached" {
|
if r#type == "usage_limit_reached" {
|
||||||
return Err(CodexErr::UsageLimitReached);
|
return Err(CodexErr::UsageLimitReached(UsageLimitReachedError {
|
||||||
|
plan_type: auth.and_then(|a| a.get_plan_type()),
|
||||||
|
}));
|
||||||
} else if r#type == "usage_not_included" {
|
} else if r#type == "usage_not_included" {
|
||||||
return Err(CodexErr::UsageNotIncluded);
|
return Err(CodexErr::UsageNotIncluded);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1290,7 +1290,9 @@ async fn run_turn(
|
|||||||
Ok(output) => return Ok(output),
|
Ok(output) => return Ok(output),
|
||||||
Err(CodexErr::Interrupted) => return Err(CodexErr::Interrupted),
|
Err(CodexErr::Interrupted) => return Err(CodexErr::Interrupted),
|
||||||
Err(CodexErr::EnvVar(var)) => return Err(CodexErr::EnvVar(var)),
|
Err(CodexErr::EnvVar(var)) => return Err(CodexErr::EnvVar(var)),
|
||||||
Err(e @ (CodexErr::UsageLimitReached | CodexErr::UsageNotIncluded)) => return Err(e),
|
Err(e @ (CodexErr::UsageLimitReached(_) | CodexErr::UsageNotIncluded)) => {
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
// Use the configured provider-specific stream retry budget.
|
// Use the configured provider-specific stream retry budget.
|
||||||
let max_retries = sess.client.get_provider().stream_max_retries();
|
let max_retries = sess.client.get_provider().stream_max_retries();
|
||||||
|
|||||||
@@ -62,14 +62,16 @@ pub enum CodexErr {
|
|||||||
#[error("unexpected status {0}: {1}")]
|
#[error("unexpected status {0}: {1}")]
|
||||||
UnexpectedStatus(StatusCode, String),
|
UnexpectedStatus(StatusCode, String),
|
||||||
|
|
||||||
#[error("Usage limit has been reached")]
|
#[error("{0}")]
|
||||||
UsageLimitReached,
|
UsageLimitReached(UsageLimitReachedError),
|
||||||
|
|
||||||
#[error("Usage not included with the plan")]
|
#[error(
|
||||||
|
"To use Codex with your ChatGPT plan, upgrade to Plus: https://openai.com/chatgpt/pricing."
|
||||||
|
)]
|
||||||
UsageNotIncluded,
|
UsageNotIncluded,
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"We’re currently experiencing high demand, which may cause temporary errors. We’re adding capacity in East and West Europe to restore normal service."
|
"We're currently experiencing high demand, which may cause temporary errors. We’re adding capacity in East and West Europe to restore normal service."
|
||||||
)]
|
)]
|
||||||
InternalServerError,
|
InternalServerError,
|
||||||
|
|
||||||
@@ -115,6 +117,30 @@ pub enum CodexErr {
|
|||||||
EnvVar(EnvVarError),
|
EnvVar(EnvVarError),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct UsageLimitReachedError {
|
||||||
|
pub plan_type: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for UsageLimitReachedError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
if let Some(plan_type) = &self.plan_type
|
||||||
|
&& plan_type == "plus"
|
||||||
|
{
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), or wait for limits to reset (every 5h and every week.)."
|
||||||
|
)?;
|
||||||
|
} else {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"You've hit usage your usage limit. Limits reset every 5h and every week."
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EnvVarError {
|
pub struct EnvVarError {
|
||||||
/// Name of the environment variable that is missing.
|
/// Name of the environment variable that is missing.
|
||||||
@@ -150,3 +176,39 @@ pub fn get_error_message_ui(e: &CodexErr) -> String {
|
|||||||
_ => e.to_string(),
|
_ => e.to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn usage_limit_reached_error_formats_plus_plan() {
|
||||||
|
let err = UsageLimitReachedError {
|
||||||
|
plan_type: Some("plus".to_string()),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"You've hit your usage limit. Upgrade to Pro (https://openai.com/chatgpt/pricing), or wait for limits to reset (every 5h and every week.)."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn usage_limit_reached_error_formats_default_when_none() {
|
||||||
|
let err = UsageLimitReachedError { plan_type: None };
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"You've hit usage your usage limit. Limits reset every 5h and every week."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn usage_limit_reached_error_formats_default_for_other_plans() {
|
||||||
|
let err = UsageLimitReachedError {
|
||||||
|
plan_type: Some("pro".to_string()),
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
err.to_string(),
|
||||||
|
"You've hit usage your usage limit. Limits reset every 5h and every week."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -68,8 +68,7 @@ impl CodexAuth {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> {
|
pub async fn get_token_data(&self) -> Result<TokenData, std::io::Error> {
|
||||||
#[expect(clippy::unwrap_used)]
|
let auth_dot_json: Option<AuthDotJson> = self.get_current_auth_json();
|
||||||
let auth_dot_json = self.auth_dot_json.lock().unwrap().clone();
|
|
||||||
match auth_dot_json {
|
match auth_dot_json {
|
||||||
Some(AuthDotJson {
|
Some(AuthDotJson {
|
||||||
tokens: Some(mut tokens),
|
tokens: Some(mut tokens),
|
||||||
@@ -124,15 +123,23 @@ impl CodexAuth {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_account_id(&self) -> Option<String> {
|
pub fn get_account_id(&self) -> Option<String> {
|
||||||
match self.mode {
|
self.get_current_token_data()
|
||||||
AuthMode::ApiKey => None,
|
.and_then(|t| t.account_id.clone())
|
||||||
AuthMode::ChatGPT => {
|
}
|
||||||
let token_data = self.get_token_data().await.ok()?;
|
|
||||||
|
|
||||||
token_data.account_id.clone()
|
pub fn get_plan_type(&self) -> Option<String> {
|
||||||
}
|
self.get_current_token_data()
|
||||||
}
|
.and_then(|t| t.id_token.chatgpt_plan_type.as_ref().map(|p| p.as_string()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_auth_json(&self) -> Option<AuthDotJson> {
|
||||||
|
#[expect(clippy::unwrap_used)]
|
||||||
|
self.auth_dot_json.lock().unwrap().clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_current_token_data(&self) -> Option<TokenData> {
|
||||||
|
self.get_current_auth_json().and_then(|t| t.tokens.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Consider this private to integration tests.
|
/// Consider this private to integration tests.
|
||||||
|
|||||||
@@ -67,6 +67,13 @@ impl PlanType {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn as_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
Self::Known(known) => format!("{known:?}").to_lowercase(),
|
||||||
|
Self::Unknown(s) => s.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
|||||||
Reference in New Issue
Block a user