@@ -24,7 +24,7 @@ import OpenAI, { APIConnectionTimeoutError } from "openai";
2424
2525// Wait time before retrying after rate limit errors (ms).
2626const RATE_LIMIT_RETRY_WAIT_MS = parseInt (
27- process . env [ "OPENAI_RATE_LIMIT_RETRY_WAIT_MS" ] || "15000 " ,
27+ process . env [ "OPENAI_RATE_LIMIT_RETRY_WAIT_MS" ] || "2500 " ,
2828 10 ,
2929) ;
3030
@@ -569,11 +569,6 @@ export class AgentLoop {
569569 ) ;
570570 continue ;
571571 }
572- const isRateLimit =
573- status === 429 ||
574- errCtx . code === "rate_limit_exceeded" ||
575- errCtx . type === "rate_limit_exceeded" ||
576- / r a t e l i m i t / i. test ( errCtx . message ?? "" ) ;
577572
578573 const isTooManyTokensError =
579574 ( errCtx . param === "max_tokens" ||
@@ -597,30 +592,61 @@ export class AgentLoop {
597592 return ;
598593 }
599594
595+ const isRateLimit =
596+ status === 429 ||
597+ errCtx . code === "rate_limit_exceeded" ||
598+ errCtx . type === "rate_limit_exceeded" ||
599+ / r a t e l i m i t / i. test ( errCtx . message ?? "" ) ;
600600 if ( isRateLimit ) {
601601 if ( attempt < MAX_RETRIES ) {
602+ // Exponential backoff: base wait * 2^(attempt-1), or use suggested retry time
603+ // if provided.
604+ let delayMs = RATE_LIMIT_RETRY_WAIT_MS * 2 ** ( attempt - 1 ) ;
605+
606+ // Parse suggested retry time from error message, e.g., "Please try again in 1.3s"
607+ const msg = errCtx ?. message ?? "" ;
608+ const m = / r e t r y a g a i n i n ( [ \d . ] + ) s / i. exec ( msg ) ;
609+ if ( m && m [ 1 ] ) {
610+ const suggested = parseFloat ( m [ 1 ] ) * 1000 ;
611+ if ( ! Number . isNaN ( suggested ) ) {
612+ delayMs = suggested ;
613+ }
614+ }
602615 log (
603- `OpenAI rate limit exceeded (attempt ${ attempt } /${ MAX_RETRIES } ), retrying in ${ RATE_LIMIT_RETRY_WAIT_MS } ms...` ,
616+ `OpenAI rate limit exceeded (attempt ${ attempt } /${ MAX_RETRIES } ), retrying in ${ Math . round (
617+ delayMs ,
618+ ) } ms...`,
604619 ) ;
605620 // eslint-disable-next-line no-await-in-loop
606- await new Promise ( ( resolve ) =>
607- setTimeout ( resolve , RATE_LIMIT_RETRY_WAIT_MS ) ,
608- ) ;
621+ await new Promise ( ( resolve ) => setTimeout ( resolve , delayMs ) ) ;
609622 continue ;
623+ } else {
624+ // We have exhausted all retry attempts. Surface a message so the user understands
625+ // why the request failed and can decide how to proceed (e.g. wait and retry later
626+ // or switch to a different model / account).
627+
628+ const errorDetails = [
629+ `Status: ${ status || "unknown" } ` ,
630+ `Code: ${ errCtx . code || "unknown" } ` ,
631+ `Type: ${ errCtx . type || "unknown" } ` ,
632+ `Message: ${ errCtx . message || "unknown" } ` ,
633+ ] . join ( ", " ) ;
634+
635+ this . onItem ( {
636+ id : `error-${ Date . now ( ) } ` ,
637+ type : "message" ,
638+ role : "system" ,
639+ content : [
640+ {
641+ type : "input_text" ,
642+ text : `⚠️ Rate limit reached. Error details: ${ errorDetails } . Please try again later.` ,
643+ } ,
644+ ] ,
645+ } ) ;
646+
647+ this . onLoading ( false ) ;
648+ return ;
610649 }
611- this . onItem ( {
612- id : `error-${ Date . now ( ) } ` ,
613- type : "message" ,
614- role : "system" ,
615- content : [
616- {
617- type : "input_text" ,
618- text : "⚠️ Rate limit reached while contacting OpenAI. Please try again later." ,
619- } ,
620- ] ,
621- } ) ;
622- this . onLoading ( false ) ;
623- return ;
624650 }
625651
626652 const isClientError =
0 commit comments