@@ -261,6 +261,28 @@ func classifyAntigravity429(body []byte) antigravity429Category {
261261 return antigravity429Unknown
262262}
263263
264+ func antigravityHasQuotaResetDelayOrModelInfo (body []byte ) bool {
265+ if len (body ) == 0 {
266+ return false
267+ }
268+ details := gjson .GetBytes (body , "error.details" )
269+ if ! details .Exists () || ! details .IsArray () {
270+ return false
271+ }
272+ for _ , detail := range details .Array () {
273+ if detail .Get ("@type" ).String () != "type.googleapis.com/google.rpc.ErrorInfo" {
274+ continue
275+ }
276+ if strings .TrimSpace (detail .Get ("metadata.quotaResetDelay" ).String ()) != "" {
277+ return true
278+ }
279+ if strings .TrimSpace (detail .Get ("metadata.model" ).String ()) != "" {
280+ return true
281+ }
282+ }
283+ return false
284+ }
285+
264286func antigravityCreditsRetryEnabled (cfg * config.Config ) bool {
265287 return cfg != nil && cfg .QuotaExceeded .AntigravityCredits
266288}
@@ -362,6 +384,12 @@ func shouldMarkAntigravityCreditsExhausted(statusCode int, body []byte, reqErr e
362384 lowerBody := strings .ToLower (string (body ))
363385 for _ , keyword := range antigravityCreditsExhaustedKeywords {
364386 if strings .Contains (lowerBody , keyword ) {
387+ if keyword == "resource has been exhausted" &&
388+ statusCode == http .StatusTooManyRequests &&
389+ classifyAntigravity429 (body ) == antigravity429Unknown &&
390+ ! antigravityHasQuotaResetDelayOrModelInfo (body ) {
391+ return false
392+ }
365393 return true
366394 }
367395 }
@@ -575,6 +603,14 @@ attemptLoop:
575603 log .Debugf ("antigravity executor: rate limited on base url %s, retrying with fallback base url: %s" , baseURL , baseURLs [idx + 1 ])
576604 continue
577605 }
606+ if antigravityShouldRetryTransientResourceExhausted429 (httpResp .StatusCode , bodyBytes ) && attempt + 1 < attempts {
607+ delay := antigravityTransient429RetryDelay (attempt )
608+ log .Debugf ("antigravity executor: transient 429 resource exhausted for model %s, retrying in %s (attempt %d/%d)" , baseModel , delay , attempt + 1 , attempts )
609+ if errWait := antigravityWait (ctx , delay ); errWait != nil {
610+ return resp , errWait
611+ }
612+ continue attemptLoop
613+ }
578614 if antigravityShouldRetryNoCapacity (httpResp .StatusCode , bodyBytes ) {
579615 if idx + 1 < len (baseURLs ) {
580616 log .Debugf ("antigravity executor: no capacity on base url %s, retrying with fallback base url: %s" , baseURL , baseURLs [idx + 1 ])
@@ -742,6 +778,14 @@ attemptLoop:
742778 log .Debugf ("antigravity executor: rate limited on base url %s, retrying with fallback base url: %s" , baseURL , baseURLs [idx + 1 ])
743779 continue
744780 }
781+ if antigravityShouldRetryTransientResourceExhausted429 (httpResp .StatusCode , bodyBytes ) && attempt + 1 < attempts {
782+ delay := antigravityTransient429RetryDelay (attempt )
783+ log .Debugf ("antigravity executor: transient 429 resource exhausted for model %s, retrying in %s (attempt %d/%d)" , baseModel , delay , attempt + 1 , attempts )
784+ if errWait := antigravityWait (ctx , delay ); errWait != nil {
785+ return resp , errWait
786+ }
787+ continue attemptLoop
788+ }
745789 if antigravityShouldRetryNoCapacity (httpResp .StatusCode , bodyBytes ) {
746790 if idx + 1 < len (baseURLs ) {
747791 log .Debugf ("antigravity executor: no capacity on base url %s, retrying with fallback base url: %s" , baseURL , baseURLs [idx + 1 ])
@@ -1158,6 +1202,14 @@ attemptLoop:
11581202 log .Debugf ("antigravity executor: rate limited on base url %s, retrying with fallback base url: %s" , baseURL , baseURLs [idx + 1 ])
11591203 continue
11601204 }
1205+ if antigravityShouldRetryTransientResourceExhausted429 (httpResp .StatusCode , bodyBytes ) && attempt + 1 < attempts {
1206+ delay := antigravityTransient429RetryDelay (attempt )
1207+ log .Debugf ("antigravity executor: transient 429 resource exhausted for model %s, retrying in %s (attempt %d/%d)" , baseModel , delay , attempt + 1 , attempts )
1208+ if errWait := antigravityWait (ctx , delay ); errWait != nil {
1209+ return nil , errWait
1210+ }
1211+ continue attemptLoop
1212+ }
11611213 if antigravityShouldRetryNoCapacity (httpResp .StatusCode , bodyBytes ) {
11621214 if idx + 1 < len (baseURLs ) {
11631215 log .Debugf ("antigravity executor: no capacity on base url %s, retrying with fallback base url: %s" , baseURL , baseURLs [idx + 1 ])
@@ -1774,6 +1826,24 @@ func antigravityShouldRetryNoCapacity(statusCode int, body []byte) bool {
17741826 return strings .Contains (msg , "no capacity available" )
17751827}
17761828
1829+ func antigravityShouldRetryTransientResourceExhausted429 (statusCode int , body []byte ) bool {
1830+ if statusCode != http .StatusTooManyRequests {
1831+ return false
1832+ }
1833+ if len (body ) == 0 {
1834+ return false
1835+ }
1836+ if classifyAntigravity429 (body ) != antigravity429Unknown {
1837+ return false
1838+ }
1839+ status := strings .TrimSpace (gjson .GetBytes (body , "error.status" ).String ())
1840+ if ! strings .EqualFold (status , "RESOURCE_EXHAUSTED" ) {
1841+ return false
1842+ }
1843+ msg := strings .ToLower (string (body ))
1844+ return strings .Contains (msg , "resource has been exhausted" )
1845+ }
1846+
17771847func antigravityNoCapacityRetryDelay (attempt int ) time.Duration {
17781848 if attempt < 0 {
17791849 attempt = 0
@@ -1785,6 +1855,17 @@ func antigravityNoCapacityRetryDelay(attempt int) time.Duration {
17851855 return delay
17861856}
17871857
1858+ func antigravityTransient429RetryDelay (attempt int ) time.Duration {
1859+ if attempt < 0 {
1860+ attempt = 0
1861+ }
1862+ delay := time .Duration (attempt + 1 ) * 100 * time .Millisecond
1863+ if delay > 500 * time .Millisecond {
1864+ delay = 500 * time .Millisecond
1865+ }
1866+ return delay
1867+ }
1868+
17881869func antigravityWait (ctx context.Context , wait time.Duration ) error {
17891870 if wait <= 0 {
17901871 return nil
0 commit comments